Invoked skill not re-attached after manual /compact; only the summary's paraphrase survives

Status Open
Reported on v2.1.270
Maintainer reply None cached
Activity 3 comments · opened Sep 15, 2026

Summary

The skills docs say compaction re-attaches the most recent invocation of each skill after the summary (first 5,000 tokens each, 25,000 combined). In my session that did not happen after a manual /compact. The only trace of the skill that survived was the compaction summary's paraphrase of it, and the paraphrase lost a detail that mattered.

Environment

  • Claude Code 2.1.270, macOS, model claude-fable-5-1
  • Session running as a Claude Code background job
  • One project skill, .claude/skills/<name>/SKILL.md, 5,279 bytes (about 1,300 tokens), invoked by me as a slash command at the start of the session

What happened

  1. I invoked the skill. It ran a long code walkthrough for a few hours.
  2. I ran /compact (transcript metadata: trigger "manual", preTokens 213,828, postTokens 8,106).
  3. After the summary, no invoked_skills block or skill content was present. The transcript has no such block at any point after the compaction boundary.
  4. The model continued from the summary's paraphrase of the skill. The skill says to use "a background subagent" for silent file writes. The paraphrase said "background subagents". The model then spawned a new subagent for every write and ended the session with ten idle ones.

Expected

Per https://code.claude.com/docs/en/skills ("Auto-compaction carries invoked skills forward within a token budget"), the skill should have been re-attached after the summary. The skill is small and was the only one invoked, so the budget was not a factor.

Questions and suggestions

  • Is re-attach meant to apply to manual /compact as well as auto-compaction? The doc sentence names only auto-compaction. If manual is excluded on purpose, the doc could say so.
  • Does session kind matter? This was a background job session.
  • Whatever the cause, a skill the user invoked is standing instruction for the session, like CLAUDE.md. A summary's paraphrase is not a safe substitute. If re-attaching the body is unwanted in some modes, at least listing the active skills by path in the summary so the model can re-read them would avoid this class of drift.

Related: #82144, #83306, #85138 (all about how replay behaves once it happens), #74990 (catalog reminder dropped after compaction).

View original on GitHub ↗

3 Comments

John-Rood · 28 days ago

The manual/auto ambiguity you flagged looks real: the skills page names auto-compaction for skill re-attach, while the context window table lists skill re-injection with no trigger qualifier. You can get deterministic behavior today either way. What the current docs say survives compaction:

  • Invoked skill bodies: re-injected, capped at 5,000 tokens per skill and 25,000 tokens total, oldest dropped first.
  • SessionStart hooks matching compact: re-run on both manual and automatic compaction, output added to the compacted context.
  • Project-root CLAUDE.md: re-read from disk and re-injected.
  • Instructions given only in conversation: summarized with everything else, which is where the paraphrase drift comes from.

Three moves, smallest first.

  1. After a manual /compact, re-invoke the skill. That is the documented recovery when a skill stops influencing behavior or gets dropped by the budget, and it restores the full body in one line.
  1. Put the load-bearing sentence in CLAUDE.md. The memory docs cover this exact symptom under "Instructions seem lost after /compact": conversation-only instructions are the ones that disappear, file content is re-read from disk. "A background subagent" vs "background subagents" is exactly the kind of distinction that should not depend on a summary.
  1. Re-inject the manifest at every compaction boundary. The hooks guide documents this pattern:
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Standing instructions: <skill-name> skill. Re-read .claude/skills/<skill-name>/SKILL.md before spawning any subagent. Silent writes go through one background subagent, not one per write.'"
          }
        ]
      }
    ]
  }
}

Stdout lands in context after the summary, so it survives the boundary that ate the skill body. Swap the echo for a script if you want the list generated from disk, and the same hook can live in the skill's frontmatter instead of settings if you want it tied to the skill; skill-defined hooks stay registered for the rest of the session once invoked.

To verify on 2.1.270: /context after a compaction should show the re-attached skill block, and an InstructionsLoaded hook with matcher compact logs the instruction reload when it fires.

AlphaGator1 · 11 days ago

<!-- Post as a COMMENT on https://github.com/anthropics/claude-code/issues/94564
(same symptom: "Invoked skill not re-attached after manual /compact").
If you would rather open a new issue, use the title below and paste the same body. -->

<!-- Title, only if filed as a new issue:
Invoked skills are not re-attached after compaction when they were invoked more than ~6-9 hours earlier (manual and auto) -->

Same symptom here, with data that may narrow it down. Elapsed time since the skill was invoked looks like the deciding factor. The trigger (manual vs auto) and the 25,000-token budget do not.

Environment: Claude Code 2.1.219 and 2.1.280–2.1.286, Claude desktop app (Code tab), Windows 11. Project skills plus a few plugin skills. I have not tested 2.1.288+. Its changelog has "Fixed --resume sometimes dropping files and other context that a compaction had just restored", which may or may not be related.

Method: I scanned the session .jsonl transcripts under ~/.claude/projects with a script. For every compaction I listed:

  • each skill invoked earlier in the session (Skill tool calls and /skill commands), with its age;
  • whether an invoked_skills attachment after the compact_boundary record carried it.

That covers 7 sessions and 15 compactions that had at least one skill invoked earlier.

Result, counting each skill at the first compaction after its invocation:

| Skill invoked this long before the compaction | Re-attached |
|---|---|
| 0.2–5.6 hours | 21 of 21 |
| 8.7–26.5 hours | 0 of 12 |

  • No exception in either direction.
  • Both manual /compact and auto-compaction appear on both sides. That answers the question in this issue: manual compaction does re-attach, when the skills are recent.
  • The budget is not the cause. The compactions that dropped skills re-attached nothing, and the largest re-attached total I saw was 63,560 characters (four skills).
  • In one compaction, two skills invoked 1.4 h and 5.2 h earlier came back. Four skills invoked 5–8 days earlier, which would easily have fit the budget, did not.

Steps to reproduce (derived from the data above, not yet run as a clean test):

  1. In a project with one small skill, start a session and invoke it.
  2. Leave the session for more than ~9 hours. My long gaps were overnight, so they probably also include the desktop app being closed or updated and the session resumed.
  3. Run /compact.
  4. Open the session's .jsonl. There is no invoked_skills attachment after the new compact_boundary, and the skill's instructions are gone from context.

Control: invoke, then /compact within an hour. The invoked_skills attachment is present with the skill.

Expected: per the docs (code.claude.com/docs/en/skills, "Skill content lifecycle"), the most recent invocation of each skill is re-attached within the 25,000-token budget, and only the budget drops older skills. Two possibilities:

  • It's a bug, possibly the invoked-skill list living only in memory and not being rebuilt when a session is resumed. Then please persist or rebuild it.
  • It's an intended age cutoff. Then please document it, and ideally have the compaction summary name the skills it did not restore, so the model knows to re-read them.

Today the drop is silent: the model carries on from the summary's paraphrase, which is exactly the drift described in this issue.

Side note on the per-skill limit: the "first 5,000 tokens of each" cut lands at exactly 20,000 characters. Two different skills were each cut at 20,000. So it's an estimate of about 4 characters per token, which for markdown-heavy skills is closer to 10,000 real tokens. That's fine, but worth knowing if you place must-follow instructions near the top of a long skill.

Workaround we use: a SessionStart hook with matcher compact. It reads the transcript, lists the skills the session invoked, and re-injects each skill's short "must-obey" section as additionalContext (capped under 10,000 characters). It makes the re-attachment question moot for the parts that matter.

John-Rood · 10 days ago

The age split in that table has a plainer cause than an age cutoff, and the overnight gaps with the app closed or updated and the session resumed are the tell.

In 2.1.284 the list of invoked skills that compaction re-attaches lives in process memory. Invoking a skill, by slash command or the Skill tool, writes the body into that registry. When a session is resumed, the registry is rebuilt from the transcript, but only from invoked_skills attachments that an earlier compaction already wrote. The original invocation records are not scanned. So a skill invoked before any compaction, with a restart in between, is not in the registry when the first compaction runs, and that compaction re-attaches nothing. That fits 0 of 12 on the long gaps, "re-attached nothing" rather than trimmed to budget, and 21 of 21 inside one process. A skill an earlier compaction had already restored does survive a resume, which is why a second compaction in a resumed session can look fine.

Manual versus auto never mattered; resume did. Until resume rebuilds the registry from the invocation records, two workarounds: re-invoke the skill right after --resume, which rewrites the entry, or the SessionStart compact hook above, which does not depend on the registry. The 2.1.288 resume fix covers files and context a compaction had already restored, a different path.