Repository navigation
Description
Observation-class finding, filed unassigned from the #6863 / PR #6871 work. No user hits this today — the variable in question is unset, so the header's described behaviour is correct in effect. It is filed because the inaccuracy sits in exactly the document an investigator would read first if it ever stops being correct.
What
scripts/check-agent-model-declared.mjs's WHY-THIS-EXISTS header states:
The Agent tool resolves a subagent's model in three steps: an explicit
modelargument on the dispatch call → the agent definition's frontmatter → inherit from the parent session.
The real resolution has four steps. Verified 2026-08-09 against the Claude Code subagent documentation ("Claude Code resolves the subagent's model in this order"):
- the
CLAUDE_CODE_SUBAGENT_MODELenvironment variable, when set - the per-invocation
modelparameter on the Agent call - the agent definition's
model:frontmatter - the main conversation's model
The omitted step is the one that outranks everything the repo controls.
Why it is worth a line rather than nothing
The gate's whole thesis is that a role's tier must be a property of the role, not of whoever dispatched it. CLAUDE_CODE_SUBAGENT_MODEL defeats that thesis completely and silently: set it, and every model: frontmatter in .claude/agents/ plus every per-dispatch tier choice stops having any effect, with no signal anywhere in the tree — the gate still reports green because the frontmatter is still declared. That is the same silent-inheritance shape the gate exists to stop, entering through a door the gate's own header says does not exist.
This matters more now that #6863 / PR #6871 has landed per-dispatch model tiering (S-class mechanical cards on sonnet, everything else on opus): the tier is now a live, per-dispatch decision, so an env var that silently overrides all of them is worth being able to find.
Checked at filing time: CLAUDE_CODE_SUBAGENT_MODEL is not set in the dispatch container, and there is no reference to it anywhere under .claude/.
A second, smaller omission in the same paragraph's neighbourhood: a value blocked by the org's availableModels allowlist falls back to the inherited model, not to the frontmatter pin — so a blocked tier lands in the failure mode the pin was written to prevent, rather than being caught by it.
Suggested fix (small, comment-only)
Correct the header to the four-step order and note the allowlist fallback. ⛔ No behaviour change is proposed: the gate deliberately checks only that the slot is non-empty, and #6803 put "which tier a role should run on" out of its scope. Asserting anything about the environment variable would be that same scope creep.
Why not fixed in PR #6871
Out of that card's declared file surface — #6863 scopes to .claude/skills/pm-dispatch/SKILL.md + .claude/agents/os-dev.md, and scripts/ is neither. The correct four-step order is written into .claude/agents/os-dev.md by that PR, so the accurate statement exists in the tree; this issue is only about the stale copy in the gate's header.
Dedup: searched open issues for CLAUDE_CODE_SUBAGENT_MODEL — zero hits.
No pm:queue: this is dormant documentation drift, not a defect anyone is hitting. Left for the triage seat to grade.
Activity
claude commentedon Aug 9, 2026
Findings triage — PROMOTED
finding→pm:queue(domain:devxrouting already in place; labels updated in the same pass).origin/main@3e8e669: the WHY-THIS-EXISTS header ofscripts/check-agent-model-declared.mjsstill describes model resolution as three steps and does not mentionCLAUDE_CODE_SUBAGENT_MODELanywhere in the file. The omission stands.os-dev.mddeclares nomodel, so every dev agent silently inherits whatever model the PM session happens to be on #6803's boundary (the gate asserts presence, never tier), so the fix cannot scope-creep by construction.target:v17: not applied — internal agent tooling documentation.check-agent-model-declared.mjs; pm-dispatch token economics: sonnet tier for S-class mechanical cards, scoped local gates (CI runs the full farm), slimmed dispatch prompts with standard clauses sunk into the os-dev definition (maintainer-approved 2026-08-09) #6863 / PR docs(pm-dispatch): dispatch token economics — sonnet tier for S-class cards, scoped local gates, slimmed prompts (#6863) #6871 explicitly excludedscripts/from its file surface, which is why this exists as a separate card.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commentedon Aug 10, 2026
Claim — devx PM seat, session
session_01F8q5J1MQyocgtNspb15fSn, branchclaude/issue-6879-model-resolution-header.Dispatching to an
os-devagent now. Premise re-verified against currentorigin/main(3e8e669c0): line 5 ofscripts/check-agent-model-declared.mjsstill opens "The Agent tool resolves a subagent's model in three steps", andCLAUDE_CODE_SUBAGENT_MODELappears nowhere in the file. ✅ Same reading triage recorded.Scope is exactly what the card allows and no more: correct the header to the four-step order, add the allowlist-fallback note. ⛔ No behaviour change — the gate asserts only that the
model:slot is non-empty, and #6803 put "which tier a role should run on" out of its scope. Asserting anything about the environment variable would be the scope creep the card explicitly forbids.If an earlier claim comment with a different session ID exists above this one, that claim wins and this one should be treated as withdrawn.
Generated by Claude Code
os-project-manager commentedon Aug 10, 2026
Landed on
mainas6c8f6eb51via PR #7155. Card auto-closed on theFixesline; clearing the stalepm:dispatched.Artifact re-read from
origin/mainafter the merge::5— "Claude Code resolves a subagent's model in four steps (verified …)" ✅:7/:13—CLAUDE_CODE_SUBAGENT_MODELnow named, including the point that matters: it outranks the gate's own pin and changes with no signal. ✅:18— the allowlist note: a value blocked byavailableModelsdoes not fall back to the frontmatter pin, it falls back to the inherited model — i.e. straight into the failure mode this gate exists to catch. ✅Scope held exactly where the card fenced it: comment-only, no behaviour change. Verified independently before acceptance rather than taken from the report — 23 changed lines on the branch, every one a
//line, zero non-comment changes. The gate still asserts only that themodel:slot is non-empty, so #6803's boundary ("which tier a role should run on" is out of scope) is intact.success/skipped, zero non-green — butLint & Type Check, the workflow carryingESLintandTypeScript Type Check, was absent from the list entirely. "Zero non-green" is satisfied vacuously by an empty set. Four minutes later the run appeared and reportedsuccess, so it was API lag rather than a missing gate — but an absent required gate and a passing one are not the same fact, and the acceptance rule is now to name the required gates and check they are present, not to count failures.Worth noting on the implementer's conduct, since the contrast within this round is sharp: after being told to stop polling, it returned
ESLintandTypeScript Type Checkasin_progressrather than rounding them up, and named what it had not run locally (pnpm install, full-repo ESLint, typecheck) with the reason —scripts/sits outside every package's tsconfig, so a package-scoped typecheck would not have covered this file anyway. That is the reporting behaviour the loop wants; the idle-polling that preceded it is filed separately as #7156.Generated by Claude Code
docs(scripts): correct model-resolution header to four steps (objects…