anthropics / anthropics/claude-code

Plugin cache: stale .in_use PID markers in orphaned version dirs cause intermittent resolution to outdated plugin content

Open
#95,420 0 comments 0 reactions 0 assignees View on GitHub
area:plugins bug platform:macos
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.