cd /news/ai-tools/what-claude-code-keeps-of-your-skill… · home › topics › ai-tools › article
[ARTICLE · art-146065] src=pub.towardsai.net ↗ pub= topic=ai-tools verified=true sentiment=· neutral

What Claude Code Keeps of Your Skills After Compaction

Claude Code re-attaches only the most recent invocation of each skill after auto-compaction, keeping the first 5,000 tokens of each skill within a combined 25,000-token budget filled from the most recently invoked skill, according to the tool's skills documentation. The 1,536-character skill listing is truncated per entry, and skills beyond the token caps are dropped by recency rather than importance, so a 500-line SKILL.md of roughly 7,500 tokens can exceed the compaction cap by half. The official guidance is to re-invoke a skill if Claude stops following it partway through a session.

by read8 min views22 publishedOct 6, 2026

Three hours into a session, Claude Code is midway through a database migration. Early on you invoked a db-migration-review skill that requires every schema change to ship with a rollback script. For the first forty minutes, every change came with one. Then auto-compaction ran. The next migration arrives without a rollback, and nothing in the terminal says why.

The skill wasn’t deleted. Part of it was kept, part was cut, and the part that was kept no longer has the weight it had when it was the most recent instruction in the conversation. If you build skills for other people to use, you need to know exactly which part is which.

This article covers documented behaviour as of Claude Code’s current docs, with community-reported behaviour labelled as such. The numbers below are implementation details and have changed before, so treat them as values to re-check on upgrades, not constants.

A skill is a directory with a SKILL.md file and optional supporting files. At session start, Claude Code loads a listing: one line per available skill, built from the description and when_to_use frontmatter. Each combined entry is truncated at 1,536 characters. Skills marked disable-model-invocation: true stay out of the listing entirely until someone types /name.

When a skill is invoked, by Claude through the Skill tool or by you with /name, Claude Code renders SKILL.md and appends it to the conversation as a single message. Several properties follow from that:

The skill’s text lives in conversation history. Compaction rewrites conversation history. Everything that follows comes from that.

When context approaches its limit, or when you run /compact, Claude Code replaces the conversation with a structured summary. According to the context window documentation, the summary keeps your requests and intent, key technical concepts, files examined or modified with important snippets, errors and their fixes, pending tasks, and current work. Full tool outputs and intermediate reasoning are discarded.

Content that lives outside the message history is then reloaded:

Content that lived inside message history is handled case by case:

The skills documentation states the rule precisely. After the summary, Claude Code re-attaches the most recent invocation of each skill, keeping the first 5,000 tokens of each. All re-attached skills share a combined budget of 25,000 tokens, filled starting from the most recently invoked skill. Older skills can be dropped entirely.

Three details matter in practice.

Truncation keeps the head. Whatever sits beyond roughly the 5,000th token of the rendered skill is gone. A skill that puts its workflow first and its constraints in a closing “Important rules” section loses the constraints first.

“Under 500 lines” is not the same as “under 5,000 tokens.” The docs recommend keeping SKILL.md under 500 lines. Markdown prose runs about 4 characters per token, so a 500-line skill at an average of 60 characters per line is around 30,000 characters, or about 7,500 tokens. That fits the authoring guideline and still exceeds the compaction cap by half. Rendered !command output counts too: a skill that injects a large git diff can blow its own budget before you write a line of instructions.

Eviction is by recency, not importance. If a session invoked a 4,000-token governance skill early and then five 5,000-token productivity skills later, the governance skill is the one that falls off. The budget has no concept of priority.

The official guidance for the first two is short: if Claude stops following a skill partway through a session, invoke it again.

The obvious workaround is a SessionStart hook with the compact matcher that runs cat on SKILL.md. Plain stdout from a SessionStart hook is added to context, so this looks like a full reload.

It isn’t. The hooks reference caps hook output at 10,000 characters per field (plain stdout, additionalContext, systemMessage and initialUserMessage are measured separately). Above the cap, Claude Code writes the output to a file and injects the path plus a preview of up to 2,000 characters. Claude can read the file but isn't told to.

10,000 characters is about 2,500 tokens. The built-in re-attachment already keeps 5,000. For any skill large enough to be truncated by compaction, a cat hook restores less than the platform already does, and it does so as plain text: frontmatter, $ARGUMENTS substitution, !command execution and allowed-tools are all skipped.

Printing text from a hook is useful for a short rules card. It isn’t a skill reload.

This is the cheapest control and the one with the widest effect.

A layout that applies the above:

markdown

---name: db-migration-reviewdescription: Review and author database schema migrations. Use when creating, editing, or reviewing files under migrations/ or any DDL change.---
## Rules (kept at the top so they survive truncation)1. Every migration ships with a tested rollback script.2. No destructive DDL (DROP, column type narrowing) without an explicit expand/contract plan.3. Run scripts/lint_migration.py before proposing a change; do not hand-check.
## Workflow...
## References (read when needed)- Expand/contract patterns: [patterns.md](patterns.md)- Lock and timeout guidance per engine: [engines.md](engines.md)

The SessionStart compact matcher is the right trigger. The payload should be an instruction to call the Skill tool, because a real invocation re-renders from disk: full body, fresh !command output, and re-applied allowed-tools.

json

{  "hooks": {    "SessionStart": [      {        "matcher": "compact",        "hooks": [          {            "type": "command",            "command": "echo 'Context was just compacted. Before any other action, re-invoke the db-migration-review skill with the Skill tool, then continue the current task under its rules.'",            "timeout": 10          }        ]      }    ]  }}

The imperative phrasing is deliberate. It matches the pattern reported in #95745 as being honoured after compaction when static policy text was not. The trigger is deterministic; whether Claude complies is still the model’s decision, so test it.

Hard-coding skill names works for one critical skill. For sessions that use several, the hook can work out which skills were active. Every hook receives transcript_path in its input, and the transcript is JSONL. The script below collects skills invoked through the Skill tool and through typed slash commands, keeps them in order of last use, and asks Claude to re-invoke the most recent few.

python

#!/usr/bin/env python3# SessionStart(compact) hook: re-invoke skills used earlier in the session.# The transcript schema is not a documented contract; re-test on upgrades.import json, re, sysMAX_SKILLS = 3ALLOWLIST = None  # e.g. {"db-migration-review", "secrets-guard"}data = json.load(sys.stdin)order = []def note(name):    name = name.strip().lstrip("/")    if not name or (ALLOWLIST and name not in ALLOWLIST):        return    if name in order:        order.remove(name)    order.append(name)try:    with open(data.get("transcript_path"), encoding="utf-8") as f:        for line in f:            try:                content = (json.loads(line).get("message") or {}).get("content")            except ValueError:                continue            if isinstance(content, list):                for b in content:                    if isinstance(b, dict) and b.get("type") == "tool_use" and b.get("name") == "Skill":                        note(str((b.get("input") or {}).get("skill", "")))            for m in re.finditer(r"<command-name>/?([\w:.-]+)</command-name>", json.dumps(content or "")):                note(m.group(1))except (OSError, TypeError):    sys.exit(0)  # never fail the session over a reminderif order:    names = ", ".join(order[-MAX_SKILLS:])    print(json.dumps({"hookSpecificOutput": {        "hookEventName": "SessionStart",        "additionalContext": f"Context was just compacted. Skills active before compaction: {names}. "                             "Re-invoke each with the Skill tool, most recent last, then resume the task."    }}))

Register it with "command": "python3 "$CLAUDE_PROJECT_DIR/.claude/hooks/reinvoke-skills.py"" under the same compact matcher. MAX_SKILLS keeps re-invocations within roughly the budget the platform would have used anyway. The allowlist lets you restrict automatic re-invocation to skills that need it, so a session doesn't reload every helper it touched. The docs note that the transcript is written asynchronously and can lag the in-memory conversation; that doesn't matter here, because the skills of interest were invoked long before compaction.

Re-invoking restores bodies. It doesn’t restore routing for skills that were installed but never used. For large skill catalogues, keep a short SKILL_CATALOG.md (skill name plus the one trigger phrase that should route to it, comfortably under 10,000 characters) and print it from the same compact hook. Issue #82017 also reports /reload-skills and /reload-plugins as manual workarounds; confirm they exist in your version before documenting them for a team.

{  "hooks": {    "PostCompact": [      {        "matcher": "manual|auto",        "hooks": [          {            "type": "command",            "command": "jq -c '{ts: now|todate, trigger, tokens_before, tokens_after}' >> \"$HOME/.claude/compaction.log\"",            "timeout": 10          }        ]      }    ]  }}

All of the above makes skills more durable. None of it makes them enforceable. A skill is text the model is asked to follow, and the evidence in #95745 shows that text in context can lose weight after compaction even when it is present in full.

Anything that must never happen belongs in a layer that doesn’t depend on the model’s attention:

The split to aim for: skills carry judgement and procedure, hooks and permissions carry invariants. A skill that says “never drop a column without a migration plan” is useful guidance. A PreToolUse hook that rejects DROP COLUMN in an unreviewed migration file is the control.

Once skills are shared, compaction behaviour becomes a platform concern rather than an individual one.

Compaction keeps the head of each recently used skill, up to 5,000 tokens each and 25,000 in total, and drops the index of everything else. Write skills so the head is what matters. Restore with a hook that asks for a real re-invocation, not one that pastes text past a 10,000-character cap. Put the rules that cannot bend into permissions and PreToolUse hooks, where compaction can't reach them.

What Claude Code Keeps of Your Skills After Compaction was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.

── more in #ai-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/what-claude-code-kee…] indexed:0 read:8min 2026-10-06 · —