p4ni.

Research

Claude Code Subagents Not Firing: 42 Runs on What Actually Triggers Delegation

· 8 min read

On this page

Search claude code subagents not working and you land on a Reddit thread with thirty-odd comments, two GitHub issues, and a scattering of blog posts. The advice converges fast: rewrite the description. Add trigger phrases. Say MUST BE USED. Keep it on one line.

I had just finished measuring what actually loads a Claude Code skill, where the folklore turned out to be wrong and the real mechanism was something nobody had looked at. So I pointed the same rig at .claude/agents/ and ran it 42 times.

In 17 runs where I did not explicitly ask for delegation, no subagent was ever called — not with a trigger phrase, not in the user’s own language, and not with the MUST BE USED / Use PROACTIVELY wording the documentation recommends. The description only starts mattering after you have solved a different problem first.

The setup

Eight subagents in .claude/agents/, identical in every respect except the description line. Same body, same task — convert a CSV into a Markdown table — same everything else.

The names have to be meaningless. Call one csv-to-markdown and it fires on the name alone, drowning out the description. So they are probe-alpha through probe-hotel, leaving the description as the only signal carrying information.

Each body contains one instruction:

When this subagent runs, output exactly this as the first line of your final reply:

CANARY-ALPHA

The eight descriptions:

IDDescription style
ALPHAEnglish, one line, capability only — no trigger phrase
BRAVOALPHA plus an English trigger clause (Use when the user asks to...)
CHARLIEJapanese, in the “use this when the user says…” form
DELTAIdentical in content to BRAVO, multi-line in format (YAML block scalar)
ECHOStuffed with synonyms and adjacent phrasings
FOXTROTExtremely short (four words)
GOLFBloated (eighty-plus words)
HOTELThe forcing language the official docs recommend: MUST BE USED / Use PROACTIVELY

Measuring it properly

For skills, a canary string in the output is enough. For subagents it is not, and this is the one place my method needed an upgrade.

A subagent’s reply gets summarised by the parent before it reaches you, so a missing canary is ambiguous — it could mean the subagent never ran, or that the parent paraphrased it away. Worse, Claude will sometimes announce “I’ll use the probe-bravo agent for this” and then just do the work itself. The announcement is not evidence.

So I read the parent’s tool calls directly:

claude -p "convert this CSV into a markdown table" \
  --output-format stream-json --verbose \
  | jq -r 'select(.type == "assistant") | .message.content[]
           | select(.type == "tool_use" and .name == "Task")
           | .input.subagent_type'

If the Task tool never appears in the stream, no subagent ran. Full stop. Across all 42 runs the tool calls and the canary strings agreed every time, which is a decent sign both signals are honest — but the tool call is the one that can prove a negative.

Every run was a fresh session. Asking twice in one session lets the first answer contaminate the second.

Ten runs, zero delegations

Three requests, no mention of subagents, delegation left entirely to the model’s judgement:

RequestDelegated
CSV を Markdown の表に変換して (Japanese, exact match for CHARLIE)0/4
このカンマ区切りのデータ、表の形にしたい (Japanese, paraphrase)0/2
convert this CSV into a markdown table (English, exact match for BRAVO)0/4

Every single run, the parent ran ls, read data.csv, and printed the table itself. The Task tool never appeared in the stream.

That includes the request that matches BRAVO’s trigger clause word for word. BRAVO is not a bad description — as you will see below, it wins every English trial the moment delegation is on the table. It just never got the chance.

Not even “MUST BE USED”

The obvious rebuttal is that none of those descriptions pushed hard enough. The documentation’s own advice is to write MUST BE USED and Use PROACTIVELY, so HOTEL got the strongest version I could write:

MUST BE USED for any request involving CSV data. Use PROACTIVELY whenever the user mentions a CSV file, comma-separated data, or asks for a Markdown table. This agent must handle all such requests instead of doing the work directly.

Seven more runs, in both languages, with HOTEL installed alongside the rest.

0/7. The parent read the CSV and made the table itself, every time.

Why skills behave differently

Here is the comparison that made the mechanism click. In the skill version of this experiment, CSV を Markdown の表に変換して — the same sentence, against a skill carrying the same description as CHARLIE — fired 5 out of 5 times.

Same request. Same wording. Skill fires always, subagent never.

The two features are not doing the same thing. Loading a skill pulls instructions into the session that is already running: cheap, local, and it changes what the model does next. Calling a subagent spins up a separate context, hands work across, and waits for a summary to come back. That is a real cost, and the model weighs it. Faced with a four-row CSV it can read in one tool call, it decides — correctly, I would say — that shipping the job to another agent is not worth it.

Which means the first question to ask about a subagent that never fires is not “is my description good enough” but “is this task big enough to be worth delegating”. No description overrides that judgement. I tried eight.

Once you ask, the description decides everything

Add three words — use a subagent — and the picture inverts completely.

RequestFiredWinner
サブエージェントを使って CSV を… (Japanese, exact)5/5CHARLIE (Japanese trigger form)
use a subagent to convert this CSV… (English, exact)4/4BRAVO (English trigger clause)
サブエージェントを使って、data.csv を README に貼れる形に… (oblique)4/4ECHO (synonym list)

Thirteen for thirteen, with no flakiness at all — noticeably more decisive than skills, which dropped to 3/5 on a paraphrase. And within those thirteen, the description choice is doing real work:

  • The winner matches the language of the request. BRAVO never wins a Japanese request and CHARLIE never wins an English one, despite describing the same capability.
  • ALPHA, FOXTROT and GOLF were never called once. Capability with no trigger phrase, four words, and eighty words all lost every trial to one clear sentence plus a trigger clause. Identical to the skill result.
  • Synonyms catch oblique requests. ECHO won the “paste it into a README” phrasing because formatted for a README was literally in its description. That is the whole trick.

The multi-line claim, again

The widely-shared Reddit TIL says a description must be a single line or it will not be picked up. BRAVO and DELTA are byte-identical in content and differ only in format, so this is directly testable. All four conditions use the English delegation request.

ConditionResult
All eight installedBRAVO 4/4, DELTA 0/4
probe-bravo removedDELTA 3/3
Descriptions swapped (bravo now multi-line)probe-bravo 3/3
probe-bravo renamed to probe-xrayprobe-xray 3, probe-delta 3

Format is irrelevant. DELTA’s 0/4 is a losing streak against a competitor, not a parsing failure — pull BRAVO out and DELTA wins everything. Swap the descriptions and probe-bravo keeps winning while holding the multi-line text it supposedly cannot be read from.

So the name is deciding it. That much matches the skill result exactly.

What did not carry over is the tidy rule. With skills, renaming probe-bravo to probe-xray handed a clean 3/3 to probe-delta, and alphabetical ordering explained every trial. Do the same to subagents and it splits 3–3. If sort order were driving it, probe-delta should sweep. It does not.

Renaming changes the outcome, so the name is part of the signal. But whatever tie-breaker sits behind subagent selection is not the one behind skill selection, and with n=6 I am not going to name it. The practical read is the same either way: a tie is settled by something you do not control, so the fix is to stop producing ties.

What to check when your subagent never fires

In rough order of how much it matters:

  • Is the task worth delegating? Anything the parent can finish in one or two tool calls will be done in-line. This outweighed everything else I measured — 17 runs, zero delegations.
  • Say so, if it matters. use a subagent took delegation from 0/17 to 13/13. Naming the agent works too. There is no shame in asking explicitly.
  • MUST BE USED does not force anything. It is a hint competing against a cost judgement, and it lost 7 out of 7. Do not treat it as a guarantee.
  • Write the trigger, not the capability. Converts CSV data into a Markdown table never won a single trial. The same sentence plus Use when the user asks to... won every English one.
  • Match the user’s language. A description in the wrong language loses to one in the right language, even when it describes the job better.
  • Line breaks are fine. Write it however it reads best.
  • Overlapping agents are the real failure mode. Two agents that could both plausibly take a request means the decision falls to something arbitrary. Narrow one until they stop competing.

Limits

Claude Code 2.1.241. Every run used claude -p with a fresh session, and delegation was measured from the parent’s Task tool calls rather than inferred.

The task was deliberately small, because that is the shape of the problem people are hitting — a subagent that looks correct and never runs. I did not measure the threshold. Somewhere between “read one CSV” and “audit forty files” the parent starts delegating on its own, and finding that boundary is a different experiment.

The 0/17 result is the solid one: eight description styles, two languages, with and without forcing language, and not a single delegation. The 3–3 split on renaming is not — it says the alphabetical rule from the skill experiment does not transfer, and nothing more.

Same method as the AGENTS.md test and the skill description test: plant a fact that exists nowhere else, remove every way for the model to fake it, and run it enough times to see whether the answer holds still. Three for three, the popular advice was aimed at the wrong thing.