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. 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 https://code.claude.com/docs/en/context-window , 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 https://code.claude.com/docs/en/skills 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 https://code.claude.com/docs/en/hooks 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 bash /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"