Skip to content

[Bug] Preset composition fails under isolated installs: scripts run on system python3 but PyYAML lives in the CLI venv #4443

Description

@tiojimbo

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

  1. 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
    
  2. That script reaches common.py, which does import yaml (line 391 in v1.0.4) and raises TemplateResolutionError("PyYAML is required to resolve preset template composition") when a preset.yml manifest exists.
  3. pyyaml>=6.0 is declared in pyproject.toml and is correctly installed — but inside the tool venv created by uv tool install / pipx.
  4. 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 composition

Same 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-cli and pipx install specify-cli

Why the usual workarounds don't apply

  • --script py doesn't help: the Python variant is invoked by the same bare python3.
  • There is no environment variable to point at another interpreter; _python3_command() only checks PATH.
  • Installing pyyaml into the system interpreter is refused on Homebrew/Debian Python under PEP 668 (externally-managed-environment). uv pip install --python "$(which python3)" pyyaml is refused for the same reason and suggests a venv — but a venv doesn't help, because the skills invoke bare python3 with 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 None

So 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:

  1. Have the scripts resolve the interpreter that ships with spec-kit (record its path at init time, similar to how some tools pin the interpreter into generated hooks) instead of assuming bare python3.
  2. Honor an override such as SPECIFY_PYTHON in _python3_command().
  3. Vendor a minimal YAML parser for manifest reading, or parse the small, well-known preset.yml schema without the dependency.
  4. 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

  1. changed the title [-]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[/+] on Sep 4, 2026
  2. changed the title [-][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[/+] on Sep 4, 2026
  3. github-actions commented on Sep 4, 2026

    @github-actions
    Contributor

    Bug assessment — isolated-preset-composition: Valid · severity medium


    Bug Assessment: Preset composition fails under isolated installs

    Report (summarized)

    Reporter @tiojimbo describes a failure in Spec Kit 1.0.4 when it is installed with the recommended isolated-install paths (uv tool install or pipx). The CLI process has PyYAML in its isolated environment, but generated command skills and the Bash resolver invoke a bare system python3; when an installed preset has a preset.yml manifest requiring composition, the runtime reports PyYAML 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 isolated specify CLI. 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

    1. Install the CLI with uv tool install specify-cli or pipx install specify-cli.
    2. Initialize a project with an integration and the Python script type: specify init myproj --integration claude --script py.
    3. Add a preset containing a preset.yml manifest that declares a composing template layer. The issue shows specify preset add without a preset identifier; [NEEDS CLARIFICATION: the exact preset identifier or source used in this step is omitted].
    4. From the project, run specify preset resolve plan-template; the report states that this CLI path works.
    5. Run python3 .specify/scripts/python/resolve_template.py plan-template --json; the report states that it fails with ERROR: PyYAML is required to resolve preset template composition.
    6. Exercise the Bash equivalent, .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() imports yaml only when resolving a preset manifest and raises TemplateResolutionError("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 converts TemplateResolutionError into the reported stderr error and exit code 1.
    • scripts/bash/common.sh:401-414 — _python3_command() selects python3, then python, then py -3 based on interpreter availability/version only; it does not verify that the selected interpreter can import PyYAML or that it is the interpreter running specify.
    • scripts/bash/common.sh:604-759 — resolve_template_content() uses the selected command to parse each preset.yml; the embedded Python parser exits with a PyYAML-specific error when import yaml fails.
    • 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, including scripts/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 PATH python3/python, and only then sys.executable. During an isolated CLI install, a system python3 can therefore win over the CLI's PyYAML-enabled interpreter.
    • src/specify_cli/integrations/base.py:1075-1080,1762-1766 — standard command and skills setup pass project_root into process_template(), so generated py invocations receive the project-venv/PATH-first behavior above but no recorded CLI-venv location.
    • src/specify_cli/presets/__init__.py:25 and src/specify_cli/presets/_commands.py:470-517 — the CLI-side preset implementation imports PyYAML directly and exposes the working specify preset resolve path, explaining the difference between CLI and generated-script behavior.
    • docs/installation.md:21-51 — uv tool and pipx are 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 specify itself runs from the package manager's isolated environment. The Python resolver has a partial project-venv improvement, but it still prefers a system python3 whenever one exists and does not carry the CLI interpreter or a supported override into the generated project. Consequently, import yaml succeeds 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 running specify command, and support an explicit SPECIFY_PYTHON override. Make the generated Python invocation and the Bash/PowerShell/Python resolver helpers consume that same contract instead of selecting a PATH python3 solely 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:

    • Add only a SPECIFY_PYTHON override 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.
    • Remove the runtime PyYAML dependency by parsing the narrow provides.templates subset 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.py
    • scripts/python/common.py
    • scripts/bash/common.sh
    • scripts/powershell/common.ps1
    • Initialization/shared-infrastructure code that persists or exposes the selected runtime interpreter
    • tests/integrations/test_base.py
    • tests/test_resolve_template_python_parity.py and corresponding shell/PowerShell parity coverage
    • docs/installation.md and/or extensions/agent-context/README.md

    Tests to add or update:

    • Verify generated --script py commands prefer the project-local environment, then the configured/recorded interpreter, and do not choose a PATH python3 that lacks PyYAML when a suitable CLI interpreter is available.
    • Run Python, Bash, and PowerShell template composition with a PATH python3 shim/environment that lacks PyYAML while the selected Spec Kit interpreter can import it; assert identical composed output and no false PyYAML error.
    • Verify SPECIFY_PYTHON precedence, invalid/unusable override errors, interpreter paths containing spaces, and behavior when no manifest is present.
    • Preserve existing malformed-manifest, missing-template, and cross-runtime parity behavior.

    Risks & Considerations

    • A persisted absolute path to a uv/pipx environment can become stale after tool upgrades, reinstallations, machine moves, or project copies; project-local interpreters and explicit overrides should take precedence where appropriate.
    • Interpreter paths need correct POSIX, PowerShell, and Windows quoting, and the three runtime variants must remain behaviorally equivalent.
    • Changing resolver precedence could alter which Python installation runs user-editable scripts; the selected interpreter should be validated and failures should be explicit rather than silently falling back to a different environment.
    • Any parser replacement or CLI delegation could change accepted YAML syntax or introduce performance and subprocess error-handling concerns.
    • The report involves dependency availability only; no data-loss or security impact is evident from the supplied evidence.

    Open Questions

    • [NEEDS CLARIFICATION: What exact preset identifier/source was passed to the omitted specify preset add command?]
    • [NEEDS CLARIFICATION: Should projects remain fully portable when initialized by an isolated CLI on one machine and then cloned or moved to another machine?]
    • [NEEDS CLARIFICATION: Should the fix cover only Python/Bash paths named in the report, or also the PowerShell twin and the agent-context extension scripts?]

    Generated by 🐛 Assess Bug from Labeled Issue for issue #4443 · 171.7 AIC · ⌖ 11.1 AIC · ⊞ 28.3K · ◷

  4. added
    triage-nice-to-haveVerdict: evidence-backed fix or greenlit feature — land after review
    on Sep 8, 2026
  5. EnocFlores commented on Sep 22, 2026

    @EnocFlores

    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_PYTHON and 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 python

    This allows manifest-based preset composition to work after an isolated uv tool or pipx installation 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 PYTHONPATH and working-directory module shadowing. Local validation currently reports 7720 passed, 16 skipped, and initialized sample projects produced identical composed output from the Python, Bash, and PowerShell resolvers.

  6. added a commit that references this issue on Sep 28, 2026
    88fd17a
  7. added 2 commits that reference this issue on Oct 5, 2026
    88ddb3e
    aef2d7b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions