Repository navigation
[Bug] Preset composition fails under isolated installs: scripts run on system python3 but PyYAML lives in the CLI venv #4443
Description
Summary
Preset template composition fails with PyYAML is required to resolve preset template composition whenever the CLI is installed the recommended way (uv tool install or pipx), because the automation scripts run under the system python3 while pyyaml lives in the CLI's isolated venv.
The better the installation isolation, the more reliably the bug shows up.
The chain
- Command skills instruct the agent to run the script, not the CLI. From
speckit-plan:Run `python3 .specify/scripts/python/setup_plan.py --json` from repo root - That script reaches
common.py, which doesimport yaml(line 391 in v1.0.4) and raisesTemplateResolutionError("PyYAML is required to resolve preset template composition")when apreset.ymlmanifest exists. pyyaml>=6.0is declared inpyproject.tomland is correctly installed — but inside the tool venv created byuv tool install/pipx.- The script is invoked as bare
python3, which resolves to the system interpreter that cannot see that venv.
Reproduction
uv tool install specify-cli
specify init myproj --integration claude --script py
cd myproj
specify preset add <any preset with a preset.yml>
# CLI path — works (runs inside the tool venv):
specify preset resolve plan-template
# Script path — fails (runs under system python3):
python3 .specify/scripts/python/resolve_template.py plan-template --json
# ERROR: PyYAML is required to resolve preset template compositionSame failure with the bash variant (.specify/scripts/bash/resolve-template.sh), since _python3_command() in common.sh only probes python3 on PATH.
Environment
- spec-kit
1.0.4 - macOS 15 (arm64), Homebrew Python 3.14.6
- Reproduced with both
uv tool install specify-cliandpipx install specify-cli
Why the usual workarounds don't apply
--script pydoesn't help: the Python variant is invoked by the same barepython3.- There is no environment variable to point at another interpreter;
_python3_command()only checks PATH. - Installing
pyyamlinto the system interpreter is refused on Homebrew/Debian Python under PEP 668 (externally-managed-environment).uv pip install --python "$(which python3)" pyyamlis refused for the same reason and suggests a venv — but a venv doesn't help, because the skills invoke barepython3with no activation.
The only way out today is bypassing PEP 668 (pip install --user --break-system-packages pyyaml), which asks users to disable a safety mechanism to run a documented feature.
Impact
Any project using a preset with a manifest — i.e. any preset that needs append, prepend or wrap, since the manifest-less path is hardcoded to replace:
except ImportError as exc:
if manifest_path.is_file():
raise TemplateResolutionError("PyYAML is required ...")
return (conventional, "replace") if conventional is not None else NoneSo composition strategies are unavailable precisely for users who installed the CLI in an isolated environment, which is what the docs recommend.
Suggested fixes
Any one of these would close it:
- Have the scripts resolve the interpreter that ships with spec-kit (record its path at
inittime, similar to how some tools pin the interpreter into generated hooks) instead of assuming barepython3. - Honor an override such as
SPECIFY_PYTHONin_python3_command(). - Vendor a minimal YAML parser for manifest reading, or parse the small, well-known
preset.ymlschema without the dependency. - If none of the above, document the requirement explicitly in the installation guide, since today it surfaces only as a runtime error.
Happy to send a PR for option 2 if that's the direction you'd prefer.
Activity
[-]Preset composition fails under isolated installs: scripts run on system python3 but PyYAML lives in the CLI venv[/-][+][BUG] Preset composition fails under isolated installs: scripts run on system python3 but PyYAML lives in the CLI venv[/+][-][BUG] Preset composition fails under isolated installs: scripts run on system python3 but PyYAML lives in the CLI venv[/-][+][Bug] Preset composition fails under isolated installs: scripts run on system python3 but PyYAML lives in the CLI venv[/+]github-actions commentedon Sep 4, 2026
Bug assessment — isolated-preset-composition: Valid · severity medium
Bug Assessment: Preset composition fails under isolated installs
@mnriemReport (summarized)
Reporter @tiojimbo describes a failure in Spec Kit 1.0.4 when it is installed with the recommended isolated-install paths (
uv tool installorpipx). The CLI process has PyYAML in its isolated environment, but generated command skills and the Bash resolver invoke a bare systempython3; when an installed preset has apreset.ymlmanifest requiring composition, the runtime reportsPyYAML is required to resolve preset template composition.The report says the direct CLI path (
specify preset resolve plan-template) succeeds, while the generated Python and Bash script paths fail. The reported environment is macOS 15 arm64 with Homebrew Python 3.14.6. No additional comments supplied reproduction details.The report links PEP 668 for the externally-managed-environment behavior.
[UNVERIFIED — fetch skipped: host not on safe list: peps.python.org]The link was not fetched; the issue body was treated as the primary source.Symptom
When a project contains a preset manifest and a generated workflow invokes template composition through system
python3, resolution fails because that interpreter cannot import PyYAML, even though PyYAML is installed with the isolatedspecifyCLI. The expected behavior is for the script path to use the same usable Python environment as the CLI, or otherwise provide a supported interpreter configuration, so preset composition works after the documented installation.Reproduction
uv tool install specify-cliorpipx install specify-cli.specify init myproj --integration claude --script py.preset.ymlmanifest that declares a composing template layer. The issue showsspecify preset addwithout a preset identifier;[NEEDS CLARIFICATION: the exact preset identifier or source used in this step is omitted].specify preset resolve plan-template; the report states that this CLI path works.python3 .specify/scripts/python/resolve_template.py plan-template --json; the report states that it fails withERROR: PyYAML is required to resolve preset template composition..specify/scripts/bash/resolve-template.sh plan-template --json; the report states that it fails for the same dependency-environment reason.The failure requires a manifest-bearing preset and a selected interpreter that does not have PyYAML. Manifest-less replacement resolution is not affected by the specific guarded import described in the report.
Suspected Code Paths
scripts/python/common.py:383-442—_preset_template_layer()importsyamlonly when resolving a preset manifest and raisesTemplateResolutionError("PyYAML is required to resolve preset template composition")when the import is unavailable.scripts/python/resolve_template.py:11-36— imports the runtime resolver and convertsTemplateResolutionErrorinto the reported stderr error and exit code 1.scripts/bash/common.sh:401-414—_python3_command()selectspython3, thenpython, thenpy -3based on interpreter availability/version only; it does not verify that the selected interpreter can import PyYAML or that it is the interpreter runningspecify.scripts/bash/common.sh:604-759—resolve_template_content()uses the selected command to parse eachpreset.yml; the embedded Python parser exits with a PyYAML-specific error whenimport yamlfails.scripts/bash/resolve-template.sh:5-6,37-43— sources the shared Bash resolver and returns its failure when resolving command-time template content.scripts/powershell/common.ps1:320-332,594-666— the PowerShell twin likewise selects a PATH interpreter and uses it to import PyYAML while parsing preset manifests; it is exposed to the same environment mismatch on Windows.templates/commands/constitution.md:7-10— the core constitution workflow explicitly directs agents to the installed runtime resolver variants, includingscripts/python/resolve_template.py; the other core command templates similarly reference installed Python helpers.src/specify_cli/integrations/base.py:656-717—resolve_python_interpreter()prefers a project-local.venv, then PATHpython3/python, and only thensys.executable. During an isolated CLI install, a systempython3can therefore win over the CLI's PyYAML-enabled interpreter.src/specify_cli/integrations/base.py:1075-1080,1762-1766— standard command and skills setup passproject_rootintoprocess_template(), so generatedpyinvocations receive the project-venv/PATH-first behavior above but no recorded CLI-venv location.src/specify_cli/presets/__init__.py:25andsrc/specify_cli/presets/_commands.py:470-517— the CLI-side preset implementation imports PyYAML directly and exposes the workingspecify preset resolvepath, explaining the difference between CLI and generated-script behavior.docs/installation.md:21-51—uv toolandpipxare documented persistent installation methods, so the affected isolation model is an officially supported setup rather than an unusual deployment.extensions/agent-context/README.md:49-59— existing documentation acknowledges that script-side PyYAML availability depends on the interpreter selected by PATH and currently advises installing PyYAML into that interpreter, consistent with the reported failure mode.Root Cause Hypothesis
The generated runtime scripts are treated as independent shell/Python/PowerShell programs and resolve their own interpreter from PATH, while
specifyitself runs from the package manager's isolated environment. The Python resolver has a partial project-venv improvement, but it still prefers a systempython3whenever one exists and does not carry the CLI interpreter or a supported override into the generated project. Consequently,import yamlsucceeds in the CLI process but fails in the script process. Confidence: high.Proposed Remediation
Preferred: Establish one shared, explicit runtime-interpreter contract for generated scripts. During initialization, prefer a usable project-local
.venv; otherwise record or otherwise expose the interpreter used by the runningspecifycommand, and support an explicitSPECIFY_PYTHONoverride. Make the generated Python invocation and the Bash/PowerShell/Python resolver helpers consume that same contract instead of selecting a PATHpython3solely because it exists. Validate the selected interpreter for Python 3 and PyYAML before using it for manifest composition, and emit a clear actionable error if no suitable interpreter is available. The implementation must preserve shell-specific quoting and avoid embedding a non-portable absolute path when a project-local interpreter is available.Alternatives:
SPECIFY_PYTHONoverride to all runtime variants and document it. This is a small, low-risk change but still requires users to discover and configure the override; it does not make the documented isolated-install path work automatically.provides.templatessubset without PyYAML or by delegating manifest resolution back to a CLI-owned resolver. This avoids interpreter mismatches but risks YAML semantic drift, duplicate parser behavior, or a new subprocess/CLI coupling.Files likely to change:
src/specify_cli/integrations/base.pyscripts/python/common.pyscripts/bash/common.shscripts/powershell/common.ps1tests/integrations/test_base.pytests/test_resolve_template_python_parity.pyand corresponding shell/PowerShell parity coveragedocs/installation.mdand/orextensions/agent-context/README.mdTests to add or update:
--script pycommands prefer the project-local environment, then the configured/recorded interpreter, and do not choose a PATHpython3that lacks PyYAML when a suitable CLI interpreter is available.python3shim/environment that lacks PyYAML while the selected Spec Kit interpreter can import it; assert identical composed output and no false PyYAML error.SPECIFY_PYTHONprecedence, invalid/unusable override errors, interpreter paths containing spaces, and behavior when no manifest is present.Risks & Considerations
uv/pipxenvironment can become stale after tool upgrades, reinstallations, machine moves, or project copies; project-local interpreters and explicit overrides should take precedence where appropriate.Open Questions
specify preset addcommand?]EnocFlores commentedon Sep 22, 2026
Update: draft PR #4674 now implements an automatic fallback for this issue and incorporates the work from #4445 by @chelsealong.
The generated Bash, PowerShell, and Python resolvers retain
SPECKIT_PYTHONand normal interpreter discovery. If none of those interpreters can import PyYAML, they fall back to:uv run --isolated --no-project --with pyyaml==6.0.3 pythonThis allows manifest-based preset composition to work after an isolated
uv toolorpipxinstallation without installing PyYAML into the system interpreter or bypassing PEP 668.The fallback is lazy, cached once per resolution, argv-safe, and isolated from inherited
PYTHONPATHand working-directory module shadowing. Local validation currently reports7720 passed, 16 skipped, and initialized sample projects produced identical composed output from the Python, Bash, and PowerShell resolvers.fix(speckit-pro): check that SpecKit can parse preset manifests
fix(speckit-pro): catch SpecKit skills calling options their scripts …
fix(scripts): honor SPECKIT_PYTHON override for preset manifest parsi…
Tie the Spec Kit python override to its upstream fix, and report its end
Make Spec Kit the only development process, with a test-first preset (#…