anthropics / anthropics/claude-code

[BUG] Claude Code sends the argument text of a stacked-skill prompt two times per skill (six copies of a pasted document for three skills)

Open
#95,177 1 comment 0 reactions 0 assignees View on GitHub
area:cost area:skills bug has repro platform:macos
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

### Environment

Claude Code 2.1.274 (CLI) on macOS 14.6 (Darwin 23.6.0), with model `claude-fable-5`. The oldest case in my transcripts has `version` 2.1.201; v2.1.199 added stacked skill invocation.

### Summary

Since v2.1.199, Claude Code expands stacked skill invocations like `/skill-a /skill-b do XYZ`. The skills docs say that `/write-tests /fix-issue 123` "loads both skills and passes the trailing text `123` as `$ARGUMENTS` to each of them"; when the trailing text is a short value, the extra copies add almost no tokens.

When the trailing text is a pasted document, Claude Code writes the full document into the request two times for each skill. My prompt had three stacked project commands (`.claude/commands/*.md`, no `$ARGUMENTS` placeholder) and a meeting transcript of 61,716 characters; the first API request of the session contained six copies of the transcript and used 188,065 input tokens.

A second session with the same prompt structure and a longer transcript used 203,614 input tokens in the first request, which exceeds a 200K context window. By the same mechanism, six stacked skills (the maximum) send the text twelve times.

This is the prompt as `~/.claude/history.jsonl` stores it, with the pasted content removed.

```
/pm:sprint-overview /pm:bulk-anti-slop-issues-linear /discord:bot-ops

[Pasted text #18 +493 lines]

(about 1,400 characters of notes)
```

### Mechanism

For each stacked skill, Claude Code writes a group of entries to the session JSONL, and every group contains the full argument string two times. All groups have the same `promptId`; Claude Code wrote the three groups within 36 ms, before the first API request.

1. A user message with `` and ``, and the `` element contains the full argument string.
2. An `isMeta` user message with the skill body. The body has no `$ARGUMENTS` placeholder, so Claude Code appends `ARGUMENTS: ` to it (as the docs describe).
3. Eight attachments (`environment`, `model`, `output_style_instructions`, `deferred_tools_delta`, `agent_listing_delta`, `mcp_instructions_delta`, `output_style`, `command_permissions`). The three groups contain identical copies of these attachments; I compared their hashes.

The docs give a reason for the `ARGUMENTS:` append ("so Claude still sees what you typed"). The `` message of the same skill already contains that text, so the append adds a second copy also when the prompt has only one skill.

The following excerpt shows the session JSONL entries before the first assistant entry.

```
line entry promptId chars transcript copies
4 user /pm:sprint-overview e3c70f5d 63,245 1
5 isMeta skill body + ARGUMENTS e3c70f5d 76,838 1
6-14 9 attachments (the 8 in the list + skill_listing)
15 user /pm:bulk-anti-slop-issues-linear e3c70f5d 63,271 1
16 isMeta skill body + ARGUMENTS e3c70f5d 66,677 1
17-24 8 attachments (identical to the 8 in lines 6-14)
25 user /discord:bot-ops e3c70f5d 63,239 1
26 isMeta skill body + ARGUMENTS e3c70f5d 75,055 1
27-34 8 attachments (identical to the 8 in lines 6-14)
35-39 hook_success, instructions, session_context, date, prompt_snapshot
44 assistant (input_tokens 2, cache_creation_input_tokens 165,195, cache_read_input_tokens 22,868)
```

### Expected

Claude receives the body of each stacked skill (up to 6) and the rest of the message one time. A skill body contains a copy of the message text only where the skill author wrote a `$ARGUMENTS` placeholder.

### Suggested fix

- Write the argument string one time per prompt (for example, only in the `` message of the first skill) and do not copy it into the entries of the other skills.
- Do not append `ARGUMENTS: ` to a skill body when a `` message of the same prompt already contains the value.
- Write the per-turn attachments one time per prompt, not one time per stacked skill.

### Workaround

Write the long text to a file and give the file path as the argument; Claude then reads the file one time with the Read tool. As an alternative, send the skill that needs the long text in its own prompt, and send the other skills in a second prompt with a short argument.

### Not a duplicate

The open issues about stacked commands report autocomplete defects (#89398, #89522, #91337) or ask for stacked built-in commands such as `/model opus /effort xhigh` (#93076). None of them reports the argument copies or their token cost.

#56082 requested this composition and asked Claude Code to "pass the surrounding text as shared arguments". The implementation in v2.1.199 copies the text into each skill, and it does not share one copy.

The author of a comment in #89398 scanned JSONL files for turns with two or more `` blocks and found none. In the CLI (2.1.274), each stacked skill is a separate user entry with the same `promptId`, so that count does not detect stacked invocations.

Contributor guide

No contributing guide indexed for this repository

Research direction

Reproduce the stacked invocation using three entries from .claude/commands/*.md and a long argument, then inspect ~/.claude/history.jsonl before the first API request. Compare the repeated , isMeta skill bodies, and per-turn attachments sharing one promptId; done means the argument and attachments occur once per prompt while each skill body still loads.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.