Skip to content

amplifier update leaves stale editable .pth after cache hash rotation, causing silent "missing module" provider failures #298

@Joi

Description

@Joi

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

  1. 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).

  2. 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
    
  3. 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".

  4. 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):

  1. 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.
  2. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions