anthropics / anthropics/claude-code

[BUG] Skill re-invocation dedupe is keyed on the RENDERED content, so changing only the arguments re-appends the entire SKILL.md — composing skills costs N × body

Open
#95,340 1 comment 0 reactions 0 assignees View on GitHub
area:skills enhancement
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code

### What's Wrong?

**Title:** Skill re-invocation dedupe is keyed on the RENDERED content, so changing only the arguments re-appends the entire SKILL.md — composing skills costs N × body

**Type:** design flaw / enhancement (the behaviour matches the docs; the docs describe a design that defeats skill composition)

## Environment

- Claude Code 2.1.276 (CLI), Linux (WSL2)
- Project skills under `.claude/skills/*/SKILL.md`, invoked by the model through the `Skill` tool

## What happens

The skills docs (§ *Skill content lifecycle*) state:

> When Claude re-invokes a skill whose rendered content is identical to the copy already in context, Claude Code adds a short note that the skill is already loaded rather than a second copy of the content. When the rendered content differs, because the arguments changed […], Claude Code appends the full content again.

So the dedupe key is the skill body **after** argument substitution. Any skill that takes an argument — i.e. any skill designed to be *called* — renders differently on every call with a new argument, and the full body is appended again each time.

## Why this is worth fixing

The control shows Claude Code **already has** the right rendering: instructions once, arguments delivered per call. It only takes that path when the arguments did *not* change — the one case where re-invoking is least useful. It is a loader that hashes a function together with its call frame, so every distinct call looks like a new function.

The re-invocation header even reads as though a delta was intended (*"the arguments or dynamic output below are new"*), but what follows is the whole body, not just the new arguments.

Practical cost: an orchestrating skill that calls a worker skill once per item (per file, per ticket, per finding) pays the worker's full body per item. That pushes authors away from small, composable, argument-taking skills toward either monolithic skills, or passing the subject *implicitly* through conversation state purely to keep the args string constant — ambient state, which is worse engineering and fails silently when mis-resolved.

### What Should Happen?

## Proposed behaviour

Key the dedupe on the skill's **template** (pre-substitution body + resolved file identity), not the rendered output:

- template unchanged, args changed → emit the short note **plus the new `Arguments:`** — the exact shape the identical-args path already produces;
- template unchanged, a dynamic-context command's output changed → emit the note plus **only the changed dynamic output**;
- template changed on disk → append the full content (today's behaviour).

If a skill interpolates `$ARGUMENTS` mid-body, the note can carry the arguments once; the model already holds the body that says where they apply.

### Error Messages/Logs

```shell

```

### Steps to Reproduce

## Repro

Use any reasonably large project skill that accepts arguments — say `.claude/skills/process-item/SKILL.md` (~6–7K tokens), whose body refers to its arguments (via `$ARGUMENTS`, or the auto-appended `ARGUMENTS:` trailer).

1. Have the model call `Skill(skill="process-item", args="item-1")` → the full body enters context. Expected.
2. Have it call `Skill(skill="process-item", args="item-2")`, then `item-3`, then `item-4`.
3. Each of those calls is answered with

```
(Re-invocation of /process-item — the skill instructions were previously loaded; the arguments or dynamic output below are new.)
```

**followed by the complete SKILL.md body again.** Four calls ≈ 20K+ tokens of byte-identical instructions; only the one argument token differs between them.

**Control** — re-invoke a skill with **byte-identical** args. The entire response is one line:

```
Skill /process-item is already loaded above; instructions unchanged. Arguments: item-1
```

### Claude Model

Opus

### Is this a regression?

No, this never worked

### Last Working Version

_No response_

### Claude Code Version

2.1.276

### Platform

Anthropic API

### Operating System

Ubuntu/Debian Linux

### Terminal/Shell

WSL (Windows Subsystem for Linux)

### Additional Information

## Not the same as

- #95177 — duplication of a user's *pasted argument text* across stacked slash-skills in one prompt (a different surface and a different duplicate).
- #21891 — closed as duplicate on the premise that dedupe exists; it does, but only for identical renders.
- #87514 — reload-plugins re-injects full skill roster without evicting the session-start copy (+~7.5k tokens per reload)
- #93987 - /reload-skills reports "no changes" for a skill added to disk mid-session (Desktop app); a fresh process sees it

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the documented “Skill content lifecycle” and reproduce the behavior using `.claude/skills/process-item/SKILL.md` and repeated `Skill` calls with different arguments. Trace the implementation that handles skill loading and re-invocation; done means unchanged skill templates avoid repeating their full body while new arguments or dynamic output remain available.

Written by the indexing model from the issue text.

Assessment

Tech stack
cli
Domain
cli, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.