anthropics / anthropics/claude-code
Plugin cache: stale .in_use PID markers in orphaned version dirs cause intermittent resolution to outdated plugin content
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
## Plugin cache: stale `.in_use` PID markers left in orphaned version directories cause intermittent resolution to outdated plugin content
### Summary
When a plugin is updated to a new version, Claude Code marks the old version directory under `~/.claude/plugins/cache////` with an `.orphaned_at` timestamp file. Each version directory also has an `.in_use` directory containing one marker file per Claude Code process (named by PID, containing `{"pid":..., "procStart":...}`) that has read from that version.
**The bug: a process's `.in_use` marker is never removed from an old version directory once that process starts reading a newer version instead.** Markers accumulate indefinitely across every version a long-lived process has ever touched, rather than being migrated or cleared. We believe this stale `.in_use` occupancy is why some tool dispatches (`Skill()`/`Agent()` calls) intermittently resolve to an old, orphaned plugin version instead of the current one — confirmed independently on two separate machines.
### Evidence
On two different developers' machines (independently, not shared state), the same pattern appeared: a single process's PID had `.in_use` marker files in **multiple different version directories** for the same plugin, including versions marked `.orphaned_at` days earlier.
Example from one machine (`support-workstation` plugin, current version `0.8.13`):
```
0.5.2/.in_use/55971 → {"pid":55971,"procStart":"Fri Sep 18 15:04:39 2026"}
0.8.13/.in_use/55971 → {"pid":55971,"procStart":"Fri Sep 18 15:04:39 2026"}
```
Same PID, same `procStart` (confirming it's the same process instance), present in both an `.orphaned_at`-marked version and the current one. This pattern repeated across multiple PID/version pairs on the same machine, and independently on a second machine with a different set of stale versions (`0.7.0`/`0.3.4` in that case).
On a real, live casework session (transcript available on request — contains customer data, not attached here), we confirmed via direct string search over the session's raw JSONL transcript that **92 references resolved to the current plugin version path, but 28 references resolved to a version 9 releases old** (`0.3.4`) in the same session — including core reference/config files (`config/taxonomy.yaml`, `reference/salesforce.md`, `teams/channel.md`), not just one skill.
### Impact
This is not just a performance/caching curiosity — it changes actual runtime behavior non-deterministically within a single session. In the case we traced, a Salesforce write was rejected with `INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST` for a value that ~40 other live records already carried successfully — plausibly explained by the config file for that dispatch having silently resolved to a 9-version-old snapshot rather than the current one, rather than being a genuine data/picklist issue.
### Suspected root cause
The `.in_use` marker appears to be written whenever a process reads from a plugin version (plausibly during automatic plugin/agent/skill discovery, not only on an explicit invocation), but nothing removes a process's marker from a version once that process has moved on to a newer one. If any part of version resolution or cache eviction treats "does this orphaned directory have any `.in_use` entries" as a signal that it's still needed, this stale residue would explain both:
1. Orphaned directories never getting fully evicted even when nothing is really using them.
2. Occasional resolution back to an orphaned version instead of the current one.
### Suggested fix direction
- When a process transitions to reading a newer version of a plugin, remove (or migrate) its own `.in_use` marker from any older version directories for that same plugin.
- Version resolution for a `Skill()`/`Agent()` dispatch should resolve strictly from whatever the plugin manifest/lockfile declares as current, never influenced by `.in_use` occupancy of an alternate version directory.
### Workaround (used ourselves in the meantime)
Manually removing every version directory with an `.orphaned_at` file present (keeping only the version with no `.orphaned_at` marker) clears the stale state. This is safe as a one-time manual cleanup but isn't a fix — the same accumulation will recur as the plugin is updated further, since the underlying marker-cleanup logic is what's missing.
### Environment
- Claude Code version: 2.1.275 (macOS)
- Reproduced independently on two machines
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by tracing marker creation, cleanup, and version resolution around ~/.claude/plugins/cache////.in_use and .orphaned_at. Confirm whether a process can leave markers in multiple versions, then verify that resolution follows the current plugin manifest and that old markers no longer keep orphaned versions active.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100