fix: installer permission error on macOS

This commit addresses issue #4005 where the one-line installer was
failing on macOS with a PermissionError when attempting to create
a directory named "None".

Changes:

1. Fix external_providers_dir None handling (src/llama_stack/cli/stack/run.py)
   - Changed from truthy check to explicit 'is not None' check
   - Use Path objects directly instead of str() conversion to avoid
     converting None to the string "None"
   - Added proper error handling with descriptive messages for
     permission errors
   - Added logging when creating external providers directory

2. Improve type conversion documentation (src/llama_stack/core/stack.py)
   - Added comments explaining that empty strings from env var defaults
     are converted to None
   - Documented that code must check for None explicitly to avoid
     str(None) creating the literal string "None"

Testing performed:
- All unit tests pass (8/8 in test_stack_config.py)
- All pre-commit hooks pass
- Manual testing on Apple M4 (ARM64) with Podman
- Validated that None values are handled properly without string conversion

Fixes #4005
This commit is contained in:
Roy Belio 2025-11-06 15:36:18 +02:00
parent 392e01dc79
commit 9e74f450cc
2 changed files with 14 additions and 2 deletions

View file

@ -176,8 +176,17 @@ class StackRun(Subcommand):
try: try:
config = parse_and_maybe_upgrade_config(config_dict) config = parse_and_maybe_upgrade_config(config_dict)
# Create external_providers_dir if it's specified and doesn't exist # Create external_providers_dir if it's specified and doesn't exist
if config.external_providers_dir and not os.path.exists(str(config.external_providers_dir)): if config.external_providers_dir is not None:
os.makedirs(str(config.external_providers_dir), exist_ok=True) ext_dir = Path(config.external_providers_dir)
if not ext_dir.exists():
try:
logger.info(f"Creating external providers directory: {ext_dir}")
ext_dir.mkdir(parents=True, exist_ok=True)
except (OSError, PermissionError) as e:
self.parser.error(
f"Failed to create external_providers_dir '{ext_dir}': {e}\n"
f"Please ensure you have write permissions or specify a different path."
)
except AttributeError as e: except AttributeError as e:
self.parser.error(f"failed to parse config file '{config_file}':\n {e}") self.parser.error(f"failed to parse config file '{config_file}':\n {e}")

View file

@ -309,6 +309,9 @@ def _convert_string_to_proper_type(value: str) -> Any:
# providers config should be typed this way. # providers config should be typed this way.
# TODO: we could try to load the config class and see if the config has a field with type 'str | None' # TODO: we could try to load the config class and see if the config has a field with type 'str | None'
# and then convert the empty string to None or not # and then convert the empty string to None or not
# NOTE: Empty strings from env var defaults (e.g., ${VAR:=}) are converted to None.
# Code that uses these values MUST check for None explicitly (not just truthiness)
# to avoid converting None to the string "None" later.
if value == "": if value == "":
return None return None