Plugin hook Python subprocess appears to cache module state, causing stale import errors that resolve spontaneously
Summary
Plugin hooks experience stale Python module state that persists after the underlying code/paths have been corrected. Errors resolve spontaneously after several minutes without any code or config changes. This suggests Claude Code's hook execution environment caches Python module lookups across tool calls within a session.
I don't have a minimal reproduction case. I've been trying to isolate one for ~2 weeks without success — the behavior is intermittent and tied to module caching state that's difficult to control externally. I'm filing this anyway because the Claude Code team likely has knowledge of the internal hook execution architecture that would make reproducing this straightforward.
Environment
- Claude Code CLI (latest as of 2026-01-31)
- Ubuntu Linux
- Multiple plugins enabled via
enabledPluginsinsettings.json - Plugins sourced from a local marketplace directory
Observed behavior
1. Import errors persist after source fix
A hook imported from lib.yaml_config_cache import .... The module existed at the correct path in the plugin's python/lib/ directory. A direct Python test confirmed the import worked:
python3 -c "import sys; sys.path.insert(0, '<plugin-cache-path>/python'); from lib.yaml_config_cache import load_resource_gating_config; print('OK')"
# OK
Yet inside Claude Code, the hook continued to fail with No module named 'lib.yaml_config_cache' for ~5-10 minutes, then resolved spontaneously. No code or config changes were made between the last failure and first success.
2. UnboundLocalError: cannot access local variable 'json'
Five identical errors appear on every tool call:
unknown (unknown): cannot access local variable 'json' where it is not associated with a value
The source is listed as unknown — the hook crashes before it can identify itself. No plugin hook file contains a json = ... variable assignment. The error may originate in Claude Code's own hook error-handling code (e.g., a json import shadowed by a local variable in an exception path). These errors also appear and disappear spontaneously across tool calls.
3. Errors shift without intervention
Within a single session, without any user changes:
- Errors appear → persist through config changes → disappear spontaneously
- New, different errors appear from different hooks → then also disappear
- The pattern is consistent with cached state expiring over time
What we've tried
- Restarted Claude Code — errors reappear in the new session
- Deleted the entire plugin cache directory (
~/.claude/plugins/cache/), restarted Claude Code, let it regenerate — same errors - Deleted all
.pycfiles and__pycache__directories in the plugin cache - Verified correct file contents in the regenerated cache via grep
- Confirmed imports work in fresh Python processes outside Claude Code
- Switched from local marketplace to
--plugin-dir— same errors - Converted local marketplace to a GitHub-hosted marketplace — same errors
- None of these actions resolved the errors — they resolved on their own after time elapsed
Hypothesis
Claude Code's hook execution environment (the Python subprocess that runs hook .py files) caches module lookups or sys.path state across invocations within a session. When a module path changes (e.g., a sys.path entry is removed or a module file is updated), the cached state becomes stale. The stale state persists until some internal TTL or cache invalidation occurs, at which point errors resolve without intervention.
This would explain why:
- Direct Python tests always work (fresh process, no cached state)
- Hooks fail inside Claude Code (reuses cached state)
- Errors resolve spontaneously (cache expires)
- Deleting
.pycfiles and the plugin cache doesn't help (the cache is in the running process, not on disk) - Switching plugin source (local dir,
--plugin-dir, GitHub marketplace) doesn't help — the caching is in the execution environment, not the source
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗