Skip to content

ai-clis: specifyCli's uv-managed Python is undiscoverable, so Spec Kit's agent-context hook always skips #77

Description

@pofallon

Summary

ai-clis installs Specify CLI with uv tool install (src/ai-clis/install.sh:148), which places it in an isolated uv venv. Spec Kit's bundled agent-context extension then can't find a Python that can import yaml, so the hook silently no-ops in every container this feature builds.

Root cause

Spec Kit's own docs (.specify/extensions/agent-context/README.md) state the assumption:

PyYAML ships with the specify CLI and is normally available via the same python3 interpreter.

That holds when specify is pip-installed into the system Python. It does not hold for uv tool install, which creates an isolated venv — PyYAML lands where the system python3 can never see it.

The hook script (.specify/extensions/agent-context/scripts/bash/update-agent-context.sh) probes $SPECKIT_PYTHON, then python3, then python, requiring import yaml to succeed. None match, so it prints Python 3 with PyYAML not found on PATH; skipping update and exits 0. Because it exits 0, nothing surfaces as a failure — CLAUDE.md / agent context files just never get refreshed after /speckit-specify or /speckit-plan.

Adding a Python feature dependency does not fix this

Worth stating up front, since it's the intuitive fix. A container built from node-agentic plus ghcr.io/devcontainers/features/python:1 and get2knowio/devcontainer-features/python-tools:

$ command -v python3
/usr/local/python/current/bin/python3
$ python3 -c "import yaml"
ModuleNotFoundError: No module named 'yaml'

The uv venv is isolated regardless of what else is installed. A dependsOn/installsAfter on a Python feature adds build time and changes nothing. The interpreter isn't missing — uv already provides one with PyYAML 6.0.3 in it. It's just not discoverable.

Suggested fix

Two pieces in src/ai-clis:

1. Wrapper script, written by install.sh inside the if [ "${SPECIFYCLI}" = "true" ] block, after uv tool install succeeds:

# Expose the uv-managed interpreter (which has PyYAML) under a fixed path so
# Spec Kit's agent-context hook can find it. Must be an exec wrapper, NOT a
# symlink -- see below.
cat > /usr/local/bin/speckit-python <<WRAPPER
#!/bin/sh
exec "$_REMOTE_USER_HOME/.local/share/uv/tools/specify-cli/bin/python3" "\$@"
WRAPPER
chmod 755 /usr/local/bin/speckit-python

2. Static containerEnv in devcontainer-feature.json:

"containerEnv": { "SPECKIT_PYTHON": "/usr/local/bin/speckit-python" }

A fixed path avoids needing $HOME interpolation in the manifest, which isn't possible there. The name matches Spec Kit's own docs, which already reference /path/to/speckit-python.

It must be a wrapper, not a symlink

This is the non-obvious part. A symlink is the natural first attempt and it breaks — CPython resolves the link chain to the real interpreter and loses the venv prefix:

$ ln -s ~/.local/share/uv/tools/specify-cli/bin/python3 ./speckit-python
$ ./speckit-python -c "import yaml"
ModuleNotFoundError: No module named 'yaml'

The exec wrapper preserves it:

$ ./speckit-python -c "import yaml,sys; print(yaml.__version__, sys.prefix)"
6.0.3 /home/vscode/.local/share/uv/tools/specify-cli

Both verified in a live node-agentic container. Worth a comment in install.sh so nobody "simplifies" it to a symlink later.

Safety when specifyCli is omitted

Setting containerEnv unconditionally is fine. The hook probes with command -v, so a path that doesn't exist falls through to python3/python and then skips cleanly — same behavior as today, no regression.

Verification

With SPECKIT_PYTHON pointed at the venv interpreter, the hook goes from skipping to:

$ bash .specify/extensions/agent-context/scripts/bash/update-agent-context.sh
agent-context: updated CLAUDE.md

Impact

Every container using ai-clis with specifyCli and a Spec Kit project. The failure is silent and exits 0, so it reads as "nothing to do" rather than "broken" — projects have been running with a dead hook without knowing.

Downstream workaround currently in get2knowio/running-man (.devcontainer/devcontainer.json sets SPECKIT_PYTHON to the venv path by hand); that can be dropped once this ships.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions