Summary
When amplifier update re-clones a module into a new content-hashed cache directory (e.g. ~/.amplifier/cache/amplifier-module-provider-github-copilot-<newhash>/), the existing uv pip editable install .pth file in the tool venv is not refreshed to point at the new hash. Later, when the old hash directory is removed (by amplifier reset --remove cache, manual cleanup, or any orphan-pruning pass), the .pth becomes a dangling pointer. The Python import machinery silently treats the package as not installed. Amplifier reports it as Partial provider failure: X/Y loaded. Missing: {'<module>': 1}.
This is a latent bug present across all editable-installed modules. It manifested for me on provider-github-copilot because that module has a pinned pre-release dep (github-copilot-sdk==1.0.0b4) which appears to have caused its older hash dir to be cleaned earlier than the others.
Repro / Evidence
-
Symptom at startup:
Partial provider failure: 3/4 loaded. Missing: {'github-copilot': 1}. Loaded: ['anthropic', 'openai', 'gemini']. Session continuing with available providers.
Even with the env var the provider config references (COPILOT_GITHUB_TOKEN) correctly populated and validated (the token had copilot scope, verified against the GitHub REST API).
-
Why "missing" is misleading: find_spec returns None.
$ ~/.local/share/uv/tools/amplifier/bin/python3 -c \
"import importlib.util as u; print(u.find_spec('amplifier_module_provider_github_copilot'))"
None
But the cache directory did exist on disk:
$ ls ~/.amplifier/cache/amplifier-module-provider-github-copilot-*
/Users/joi/.amplifier/cache/amplifier-module-provider-github-copilot-9df0a2570f3b5eb6
-
The .pth file pointed at a deleted directory. Running uv pip install -e <current cache dir> produced this transition output:
Uninstalled 1 package in 0.81ms
Installed 1 package in 0.91ms
- amplifier-module-provider-github-copilot==2.1.1 (from file:///Users/joi/.amplifier/cache/amplifier-module-provider-github-copilot-ed66ecbd21ea6054)
+ amplifier-module-provider-github-copilot==2.2.0 (from file:///Users/joi/.amplifier/cache/amplifier-module-provider-github-copilot-9df0a2570f3b5eb6)
The old hash ed66ecbd21ea6054 does not exist on disk — confirmed via ls -d ~/.amplifier/cache/amplifier-module-provider-github-copilot-ed66ecbd21ea6054 → "No such file or directory".
-
Latent on other modules. Multiple provider modules have two cache hash directories present simultaneously, indicating a previous rotation that left the older hash unpruned (and the .pth happens to point at a still-existing dir, so they're working). Example:
$ ls -dt ~/.amplifier/cache/amplifier-module-provider-anthropic-*
/Users/joi/.amplifier/cache/amplifier-module-provider-anthropic-290ff7b699138a0b
/Users/joi/.amplifier/cache/amplifier-module-provider-anthropic-5181591dcf06d076
$ cat ~/.local/share/uv/tools/amplifier/lib/python3.12/site-packages/_editable_impl_amplifier_module_provider_anthropic.pth
/Users/joi/.amplifier/cache/amplifier-module-provider-anthropic-5181591dcf06d076
If a future cleanup prunes 5181591dcf06d076 while leaving the .pth pointing at it, the same failure mode reproduces.
Manual fix that worked
uv pip install \
--python ~/.local/share/uv/tools/amplifier/bin/python3 \
--prerelease=allow \
-e ~/.amplifier/cache/amplifier-module-provider-github-copilot-<current-hash>
After this, find_spec returns the new origin and the provider loads cleanly on next session start.
Suggested fix
Either (or both):
- After re-cloning a module into a new hash dir, always re-run
uv pip install -e <new-path> to update the corresponding .pth. Don't assume the old editable still resolves.
- When pruning old cache hash directories, scan editable
.pth files in the tool venv and invalidate/refresh any that pointed at the pruned path.
Option 1 is the simpler invariant: cache-hash change ⇒ editable-install refresh.
Why this is hard to diagnose from the user side
The failure surfaces as a missing-provider banner that names the provider and lists what's loaded. The natural reading is "config or auth problem" — the user goes hunting for an empty env var, a wrong API key, a missing scope. The actual ModuleNotFoundError is invisible because the kernel catches it during provider mount and converts to the friendly "Missing: {...: 1}" banner. A previous session spent considerable time fixing a real-but-unrelated env-var bridging issue before realizing the provider package itself was uninstalled.
A clearer log line at provider mount failure (e.g. provider <name> failed to import: <ModuleNotFoundError ...>) would shave a lot of debugging time. Even better: an amplifier doctor-style command that audits each editable .pth against its target.
Environment
- macOS 26.4.1 (arm64)
- Amplifier installed via
uv tool install
- Python 3.12.10 in tool venv at
~/.local/share/uv/tools/amplifier/
- Module versions:
amplifier-module-provider-github-copilot==2.1.1 → 2.2.0 (the rotation that triggered this)
Summary
When
amplifier updatere-clones a module into a new content-hashed cache directory (e.g.~/.amplifier/cache/amplifier-module-provider-github-copilot-<newhash>/), the existinguv pipeditable install.pthfile in the tool venv is not refreshed to point at the new hash. Later, when the old hash directory is removed (byamplifier reset --remove cache, manual cleanup, or any orphan-pruning pass), the.pthbecomes a dangling pointer. The Python import machinery silently treats the package as not installed. Amplifier reports it asPartial provider failure: X/Y loaded. Missing: {'<module>': 1}.This is a latent bug present across all editable-installed modules. It manifested for me on
provider-github-copilotbecause that module has a pinned pre-release dep (github-copilot-sdk==1.0.0b4) which appears to have caused its older hash dir to be cleaned earlier than the others.Repro / Evidence
Symptom at startup:
Even with the env var the provider config references (
COPILOT_GITHUB_TOKEN) correctly populated and validated (the token hadcopilotscope, verified against the GitHub REST API).Why "missing" is misleading:
find_specreturns None.But the cache directory did exist on disk:
The
.pthfile pointed at a deleted directory. Runninguv pip install -e <current cache dir>produced this transition output:The old hash
ed66ecbd21ea6054does not exist on disk — confirmed vials -d ~/.amplifier/cache/amplifier-module-provider-github-copilot-ed66ecbd21ea6054→ "No such file or directory".Latent on other modules. Multiple provider modules have two cache hash directories present simultaneously, indicating a previous rotation that left the older hash unpruned (and the
.pthhappens to point at a still-existing dir, so they're working). Example:If a future cleanup prunes
5181591dcf06d076while leaving the.pthpointing at it, the same failure mode reproduces.Manual fix that worked
After this,
find_specreturns the new origin and the provider loads cleanly on next session start.Suggested fix
Either (or both):
uv pip install -e <new-path>to update the corresponding.pth. Don't assume the old editable still resolves..pthfiles in the tool venv and invalidate/refresh any that pointed at the pruned path.Option 1 is the simpler invariant: cache-hash change ⇒ editable-install refresh.
Why this is hard to diagnose from the user side
The failure surfaces as a missing-provider banner that names the provider and lists what's loaded. The natural reading is "config or auth problem" — the user goes hunting for an empty env var, a wrong API key, a missing scope. The actual
ModuleNotFoundErroris invisible because the kernel catches it during provider mount and converts to the friendly "Missing: {...: 1}" banner. A previous session spent considerable time fixing a real-but-unrelated env-var bridging issue before realizing the provider package itself was uninstalled.A clearer log line at provider mount failure (e.g.
provider <name> failed to import: <ModuleNotFoundError ...>) would shave a lot of debugging time. Even better: anamplifier doctor-style command that audits each editable.pthagainst its target.Environment
uv tool install~/.local/share/uv/tools/amplifier/amplifier-module-provider-github-copilot==2.1.1 → 2.2.0(the rotation that triggered this)