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.
Summary
ai-clisinstalls Specify CLI withuv tool install(src/ai-clis/install.sh:148), which places it in an isolated uv venv. Spec Kit's bundledagent-contextextension then can't find a Python that canimport 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:That holds when
specifyis pip-installed into the system Python. It does not hold foruv tool install, which creates an isolated venv — PyYAML lands where the systempython3can never see it.The hook script (
.specify/extensions/agent-context/scripts/bash/update-agent-context.sh) probes$SPECKIT_PYTHON, thenpython3, thenpython, requiringimport yamlto succeed. None match, so it printsPython 3 with PyYAML not found on PATH; skipping updateand exits 0. Because it exits 0, nothing surfaces as a failure —CLAUDE.md/ agent context files just never get refreshed after/speckit-specifyor/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-agenticplusghcr.io/devcontainers/features/python:1andget2knowio/devcontainer-features/python-tools:The uv venv is isolated regardless of what else is installed. A
dependsOn/installsAfteron 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.shinside theif [ "${SPECIFYCLI}" = "true" ]block, afteruv tool installsucceeds:2. Static
containerEnvindevcontainer-feature.json:A fixed path avoids needing
$HOMEinterpolation 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:
The
execwrapper preserves it:Both verified in a live
node-agenticcontainer. Worth a comment ininstall.shso nobody "simplifies" it to a symlink later.Safety when specifyCli is omitted
Setting
containerEnvunconditionally is fine. The hook probes withcommand -v, so a path that doesn't exist falls through topython3/pythonand then skips cleanly — same behavior as today, no regression.Verification
With
SPECKIT_PYTHONpointed at the venv interpreter, the hook goes from skipping to:Impact
Every container using
ai-cliswithspecifyCliand 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.jsonsetsSPECKIT_PYTHONto the venv path by hand); that can be dropped once this ships.