fix(discovery): correct nested-spawn availability claims to the current default - #1574
Conversation
…nt default Four places asserted the harness "filters Agent out of every non-fork subagent unless CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH is set" — accurate only for Claude Code 2.1.217 through 2.1.218. Verified 2026-07-26 against the byte-exact release changelog: nesting shipped at a fixed five layers (v2.1.172), went off by default (v2.1.217), and returned at v2.1.219 — "Subagents can now spawn nested subagents up to depth 3 by default (was 1); set CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=1 to disable nesting." Confirmed behaviorally on 2.1.220: a non-fork general-purpose subagent held a fully-schema'd Agent tool with the variable unset. The polarity flip is what makes the stale wording actively misleading rather than merely dated: the variable now lowers the ceiling as readily as it raises one, so "absent" no longer implies "off" — and setup's dispatch-capability row was recommending people set it on exactly that inference. The necessary-not-sufficient framing and the check-the-tool-is-actually-there instruction in the agent definitions were already right and are kept; only the reason changes, plus a caution that a denied spawn is a permission verdict rather than a depth one, since spawns are classifier-evaluated before launch. Claims are now version-pinned so the next default move reads as drift rather than as settled fact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 49s —— View job PR Review
Assessment: ApproveThis is a clean, well-scoped documentation correction. The factual claim that nested spawning was disabled by default (requiring
|
|
Claude finished @kyle-sexton's task in 2m 38s —— View job Security Review
Scope reviewed: 6 files in All changed files are Markdown instruction files for AI agents and supporting metadata (version bump, changelog). There are no GitHub Actions workflows, shell scripts, code files, or credential-handling paths in this diff. Prompt injection surface (no new risk)Both Capability claims and spawn depth (no security regression)The PR corrects the documented polarity of Write-destination controls (unchanged, correct)
Fork / non-fork capability boundaryThe old setup/SKILL.md dispatch-capability rowThe old text recommended operators set No security findings. The changes are factual corrections to AI agent instructions with no execution surfaces, no credential handling, no authorization logic, no injection vectors, and no GitHub Actions modifications. The prompt-injection defenses that guard against repository content are preserved intact. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e248a484ad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ow in setup Review finding on #1574: the rewritten dispatch-capability row collapsed everything below 2.1.217 into "absent meant available," which is wrong for 2.1.171 and older — nested spawning did not exist before 2.1.172, so an unset variable there buys nothing and reporting it as available reproduces the exact false capability report this change set out to remove. The check explicitly supports versions below its 2.1.219 floor, so that range is reachable. Now stated as four windows against the observed version: <2.1.172 unavailable, 2.1.172-2.1.216 available at a fixed five, 2.1.217-2.1.218 off (the only window where setting the variable turned nesting on), 2.1.219+ available at three. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Right, and it is the same class of error the PR is fixing. Corrected in 6471a75. I had collapsed everything below 2.1.217 into "absent meant available," which is false for 2.1.171 and older — nested spawning did not exist before 2.1.172, so an unset variable there buys nothing. Since the check explicitly supports versions under its 2.1.219 floor, that range is reachable in practice. Now stated as four windows against the observed version:
|
|
Claude finished @kyle-sexton's task in 1m 22s —— View job PR Review (updated commit
|
| Window | Behavior |
|---|---|
< 2.1.172 |
Nesting does not exist; variable buys nothing |
2.1.172–2.1.216 |
Absent means available at a fixed five |
2.1.217–2.1.218 |
Absent means off; the only window where setting it turned nesting on |
2.1.219+ |
Absent means available at three |
The fix is isolated to one file (setup/SKILL.md). The second commit touches nothing else, which is appropriate — the agents and research-deep skill were correctly updated in the first commit and don't share the same <2.1.172 gap (they use durable "observe, don't derive" framing rather than reciting window-specific states).
CHANGELOG.md note: No new entry was added for 6471a75. Since both commits land together as version 0.9.1, this is correct — the changelog is a version-level summary, not a commit log. The existing 0.9.1 entry's description of "names which window each reading belongs to" now accurately describes the four-window result rather than the three-window intermediate.
No issues. The text reads cleanly, the four-window enumeration is internally consistent, and the transition phrase "Report absent as INFO in every window" (replacing "either way") correctly generalizes across all four cases.
|
Claude finished @kyle-sexton's task in 1m 33s —— View job Security Review
Scope: 6 files in Prompt-injection defenses — unchanged, correctBoth
Write-destination controls — unchanged, correctBoth agent definitions constrain Capability escalation — not presentThe corrected text in the agent "Tool honesty" paragraphs tells agents to observe whether Env var polarity — correction removes misconfiguration riskThe old Four-window breakdown (
|
|
Claude finished @kyle-sexton's task in 1m 28s —— View job PR Review
Assessment: ApproveThe latest push ( Commit summary
|
| Window | Reading |
|---|---|
< 2.1.172 |
Nesting does not exist; variable buys nothing |
2.1.172–2.1.216 |
Absent means available at a fixed five |
2.1.217–2.1.218 |
Absent means off — only window where setting it turned nesting on |
2.1.219+ |
Absent means available at three |
The "report absent as INFO in every window" instruction correctly generalizes across all four cases, removing any prescriptive recommendation that assumed a specific default.
agents/explorer.md and agents/researcher.md
Both "Tool honesty" paragraphs updated identically. Polarity-flip is explicit ("lowers the ceiling as readily as it raises one"). The permission-classification caution ("A spawn that comes back denied is not an answer about depth") is a genuine improvement over the prior text. The load-bearing instruction — check whether the tool is actually there — is preserved.
skills/research-deep/SKILL.md
Two corrections, both correct:
- Multi-topic check text now uses the durable framing ("a default that has moved three times") rather than restating the current window, which is more stable against further harness changes.
- Gotchas — the old "errors even inside a fork" is replaced by the precise invariant: "inside a fork, cannot spawn a further fork at all."
plugin.json / CHANGELOG.md
Version bump 0.9.0 → 0.9.1 is appropriate for a prose/documentation fix. The CHANGELOG [0.9.1] entry accurately describes all per-file changes at version granularity. The entry has no date, consistent with some (not all) prior entries — style inconsistency, not an error.
Two minor cosmetic notes (non-blocking)
-
setup/SKILL.mdL73 — "nesting buys" falls at the end of a line with "throughput" starting the next. Renders correctly in Markdown, but reads awkwardly in the raw source that agents consume. The meaning is unambiguous. -
research-deep/SKILL.mdL86–88 — "— the two things\n it exists for." is a hanging phrase carried over from the original. Not wrong; slightly choppy in raw form.
Neither issue affects behavior or correctness.
No blocking issues. The full diff is internally consistent, the four-window breakdown resolves the only open finding, and the merge commit introduces no regression.
|
Claude finished @kyle-sexton's task in 1m 28s —— View job Security Review
Scope: 6 files in Prompt-injection defenses — unchanged and correctBoth
Write-destination controls — unchanged and correct
Capability escalation — not presentThe corrected "Tool honesty" paragraphs in both agent definitions tell agents to observe whether Env var polarity — correction removes misconfiguration riskThe old Four-window breakdown (
|
…the changelog (#1572) Closes #1570 Closes #1571 Works the `plugin-quality:audit` handoff for `session-flow:orchestrate` (audit filed against #1479). **The audit's ranked top finding is refuted.** The rest of the ranked set lands. ## The headline finding was wrong, and that is the main result The audit reported, as HIGH: `SKILL.md`'s "a configurable default of three" is factually wrong, nesting is off by default, and a worker following imperative 5 hits a missing-tool condition. It is not wrong. Verified this session against the **byte-exact raw changelog** — deliberately not a summarizing fetch, because the audit itself recorded that this page reads inconsistently through a summarizer and asked whoever remediated to re-fetch it directly: | Version | Verbatim | |---|---| | v2.1.172 | "Sub-agents can now spawn their own sub-agents (up to 5 levels deep)" | | v2.1.217 | "Changed subagents to no longer spawn nested subagents by default; set `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` to allow deeper nesting" | | **v2.1.219** | **"Subagents can now spawn nested subagents up to depth 3 by default (was 1); set `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=1` to disable nesting"** | | v2.1.220 | "Bug fixes and reliability improvements" — does not revert it | Installed harness is **2.1.220**. The skill's three-state history is exactly right. Cross-checked behaviorally rather than only by reading: a non-fork `general-purpose` subagent one layer below a subagent held a fully-schema'd `Agent` tool with `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` unset in its environment. The prose page's account forbids that outright. (The exact live ceiling was not pinned — the probe that would have measured it was denied by the auto-mode permission classifier, a different gate. Recorded as unverified rather than asserted.) **Where the audit went wrong.** It sourced the finding from <https://code.claude.com/docs/en/sub-agents>, which still reads "By default, a subagent can't spawn subagents of its own… While nesting is off, Claude Code withholds the `Agent` tool from every subagent except a fork." That page has not absorbed v2.1.219 — it is one release behind. Its own version Note ("From v2.1.172 through v2.1.216…") is self-consistent with the superseded state, which is what made the stale reading look confirmed. The audit's supporting empirical reproduction is also explainable without nesting being off: a restricted agent type whose `tools` list omits `Agent` produces "the `Agent` tool was entirely absent" regardless of the nesting default. Per the fresh-docs mandate, both pages were fetched this session and the disagreement is recorded rather than silently resolved. Every behavioral claim below cites a page fetched 2026-07-26. ## What actually needed fixing `context/sources.md:70-84` **was** genuinely stale — just not for the filed reason. Its quotes "a background subagent at depth five does not receive the Agent tool" and "The limit is fixed and not configurable" appear nowhere in the current page and are contradicted by the env var existing at all. Rewritten to current text: version-pinned changelog quotes for the depth default, the current tool-list gating sentence (which now carries a "once you allow nested spawning" qualifier the old quote lacked), all three separately-overridable caps with defaults (200/session v2.1.212+, 20 concurrent v2.1.217+, depth), the fork-is-a-leaf constraint, and an explicit **drift note** naming which surface to believe for what. `SKILL.md`'s depth paragraph now attributes the depth-3 default to the changelog that carries it rather than the page that currently contradicts it, pins each state to its version, and adds a rule the audit's own failure mode argues for directly: **confirm nesting from behavior, not from one page** — dispatch one worker, check whether it holds `Agent`, before committing a design to a second layer. A tree authored from either source alone can be wrong in *both* directions. It also records that a denied spawn is a permission verdict, not a depth verdict (spawns are classifier-evaluated before launch, v2.1.178) — a distinction that cost this session its depth measurement. ## The rest of the ranked set - **F1.1** — a non-binding size anchor for imperative 7, placed in the Tiered-delegation section, which is already export-omitted, so the pasted brief stays model- and tool-agnostic. Cites the workflow size guideline and the 25-agent `Large workflow` flag. - **F1.2** — `${CLAUDE_EFFORT}` read into the **priming addendum only**, matching the skill's existing lane for machine-specific reads. Verified from the skills page fetched this session. - **F5.2** — two eval cases that test judgment rather than formatting: a small ask that must stay single-agent, and a wide fan-out that must default the fleet to a cheaper tier while keeping the adjudication stage at parent tier. - **F5.5** — `context/gotchas.md`, clearing the `skill-quality:check` warning. - **`docs/OFFICIAL-DOCS.md`** — the changelog and workflows pages are now indexed. `orchestrate` cited both as load-bearing evidence and neither was in the indexed set, which put those citations outside the fresh-docs mandate's own index. The changelog row carries handling rules: fetch raw markdown for byte-exact quotes, always pin the version, pair with the topic page rather than replacing it. ## Deliberately not done - **F4.1 (handoff-relay convention)** — deferred. #1476 is OPEN, labeled `needs-human`, and its body says "Before implementation" with its outcome set explicitly not yet derived. The audit itself scoped this "once #1476 locks its exact wording." Writing imperative-4 qualifying text against unlocked wording risks contradicting the final convention. - **F1.2 remediation 2 (`context-guard` wiring)** — deferred; #1479 scoped it assess-only. The audit's own blindspot note applies: zero `context-guard` references exist anywhere under `plugins/session-flow/`, so this is a plugin-wide decision, not an `orchestrate` one. - **F5.3** — declined. Softening one trigger phrase would convert a prose change into a frontmatter contract change for a NIT the audit calls "optional strengthening only." - **F2.3, F1.3** — informational / residual risk by the audit's own verdict; no change implied. ## Verification - `check-skill.sh orchestrate` — **PASS, 0 errors, 0 warnings** (was 1 warning: no gotchas surface). All 7 base-ref trigger phrases preserved; frontmatter untouched. - `check-changelog-parity.sh --check-bump origin/main` — passes; `session-flow` 0.17.13 → 0.17.14. - `evals.json` validates against `plugins/skill-quality/reference/evals.schema.json` (7 cases). ## Related - #1574 — the inverted sub-claim, split out. The audit cites `plugins/discovery/`'s files as "already state the current, correct fact"; they state the superseded one. Different plugin, own version and changelog, so it is a separate PR rather than part of this one. - #1573 — the issue #1574 closes. - #1479 — the read-only work item the audit fulfilled. Not closed by this PR. - #1476 — handoff-relay convention. F4.1 depends on its wording locking; deferred, not closed here. - #1475 — companion context-guard zone-crossing hooks issue, referenced by #1476. Untouched. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Closes #1573
Split out of the
session-flow:orchestrateaudit work (#1570 / #1572) because it is a differentplugin with its own version and changelog.
What this corrects
Four places asserted the harness "filters
Agentout of every non-fork subagent unlessCLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTHis set." That was accurate for exactly two releases,2.1.217–2.1.218.
Verified 2026-07-26 against the byte-exact raw changelog:
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTHto allow deeper nesting"CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=1to disable nesting"Confirmed behaviorally on 2.1.220: a non-fork
general-purposesubagent held a fully-schema'dAgenttool with the variable unset.Why it mattered enough to fix
The variable's polarity flipped. It used to be the opt-in that turned nesting on; it is now also
the knob that turns nesting off (
=1). So "absent" no longer implies "off" — andskills/setup/SKILL.md's dispatch-capability row was recommending operators set it on exactly thatinference, reporting a capability gap that does not exist and telling them to configure something
already on by default.
The agent definitions were less harmful: their operative instruction — "check whether the tool is
actually there rather than treating the flag as a guarantee" — is correct and survives untouched.
Only the stated reason was wrong. That instruction is, if anything, better supported now: the
default has moved three times in seven weeks, which is the argument for observing rather than
deriving.
Note on provenance
The upstream
sub-agentsprose page still documents the off-by-default state and has not absorbedv2.1.219. That is how both this plugin and the audit that reviewed it landed on the same stale
reading — and it is why the fix pins claims to versions and points at behavior rather than restating
a page that is itself lagging.
Changes
agents/explorer.md,agents/researcher.md— same paragraph in both. Reason restated; added thata denied spawn is a permission verdict rather than a depth one, since spawns are
classifier-evaluated before launch (v2.1.178) and the error text is what tells them apart.
skills/setup/SKILL.md— the dispatch-capability row now reports the value against the runningversion and names which window each reading belongs to, instead of recommending the variable on a
false premise. Still INFO, never a gate.
skills/research-deep/SKILL.md— two spots restated as availability to be observed. The claimthat
Agent"errors even inside a fork" is replaced by the invariant that actually holds: a forkcannot spawn a further fork.
Verification
check-changed-skills.sh origin/main— 2 skills checked, 0 failed.setupPASS 0 warnings;research-deepPASS with 2 warnings confirmed pre-existing (same count on the unmodified fileat base — they are fresh-eyes proximity warnings against a single very long paragraph, untouched
by this change).
check-changelog-parity.sh --check-bump origin/main— passes;discovery0.9.0 → 0.9.1.Related
session-flow:orchestrateside of the same upstream divergence. That PR refutes theaudit's top finding; this one resolves the inversion in the audit's supporting claim, which had
cited these
discoveryfiles as the correct copies.