Skip to content

ci(agents): gate that every .claude/agents/ definition declares a model (#6803) - #6836

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-6803-os-dev-model-declared
Aug 9, 2026
Merged

ci(agents): gate that every .claude/agents/ definition declares a model (#6803)#6836
os-project-manager merged 1 commit into
mainfrom
claude/issue-6803-os-dev-model-declared

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #6803

What this PR does, and what it deliberately does not

The card listed three fixes. Two of them were already in the tree before this PR started, and the audit found nothing to fix. So the only thing this PR builds is the item the card flagged as "worth considering": the gate.

Card item State on main at e120a5a1d This PR
1. Declare model in os-dev.md Already landedmodel: opus at .claude/agents/os-dev.md:9, by PR #6688 for #6686, merged 2c57837ec at 13:09:56Z verified, untouched
2. Audit sibling definitions .claude/agents/ holds exactly one file audited (below), and the gate makes it permanent
3. A line in the pm-dispatch SKILL Already present.claude/skills/pm-dispatch/SKILL.md:1370-1373 verified, untouched (removed from dispatch; PR #6799 is in flight on that file)
The gate question nothing reads .claude/agents/ at all built

The premise had already expired when the card was filed

This is worth stating plainly because it changes how the card should be read. #6803 was filed at 18:16Z. Its item 1 landed at 13:09Z — five hours earlier — via PR #6688, closing #6686, which was filed at 12:46Z by the spec-tooling seat after the actual incident. The services-lane cross-link on this card correctly caught that item 3 was already done, and correctly identified #6685 as a duplicate, but neither seat noticed that #6686 had already landed item 1. Three issues were filed on one subject inside six hours; the first one fixed it.

So os-dev.md needed no edit, and this PR makes none to it.

Item 2 — the audit, in full

.claude/agents/ contains exactly one file: os-dev.md, which declares model: opus. There are no siblings to fix, and none that intentionally inherit — so there is no "documented inherit" to write. I checked the adjacent surfaces too: .claude/workflows/ holds one JS file (not an agent), and neither .claude/settings.json nor .claude/launch.json carries model configuration.

A one-file audit is a weak deliverable on its own — it is true today and says nothing about tomorrow. That is precisely why the gate is the real answer to item 2: it converts a point-in-time observation into a standing assertion, so the next definition added by copying this one and trimming the frontmatter is caught at authoring time.

The gate

scripts/check-agent-model-declared.mjs, wired as pnpm check:agent-model-declared and run in the TypeScript Type Check job beside check:skill-frame-sync and check:skill-compatibility — the family the card pointed at. Neither lint job carries a paths filter, so it cannot go dormant on exactly the PR that breaks it.

Why the assertion belongs on the definition, not at the call site. The caller-side half already existed and did not hold. SKILL.md §5 has carried the model: "opus" clause since before the incident, and per the services-lane comment on this card, a seat that had read it still dispatched twelve os-dev agents in a row without passing model, with no signal that anything was wrong. Prose in the caller is a thing to remember; the frontmatter holds regardless of caller compliance.

It asserts presence, never which tier. This is the one design decision worth challenging, so I will state the reasoning rather than bury it. Encoding "os-dev must be opus" in the gate would mean a legitimate maintainer policy change has to edit a check script to land. The defect #6803 exists for is the empty slot, which is mechanical and objective; the tier is a decision, and #6803 puts changing it explicitly out of scope. The value is documented where it belongs — in the file's own frontmatter comment, which already explains the pin.

model: inherit stays legal, but only in writing. A role that genuinely should follow its caller can say so, but an unexplained inherit is indistinguishable in effect from the bug. So inherit is accepted only for files listed in INHERIT_JUSTIFIED, each with a stated reason and a rationale regex that must still match the live file — edit the justification away and the entry dies with it. The list is empty today; it exists so the first role that wants to inherit has a way to record it. This is the card's own distinction ("a silent inherit is the defect; a documented one is a decision") made mechanical, reusing the self-invalidating-exemption shape from check:skill-compatibility.

Every absence is red, never a skip (#4690). Missing directory, no .md files, no frontmatter, unterminated frontmatter, model: present but empty, a value that is neither a tier alias nor a claude- model id, an unjustified inherit, a justification whose reason is gone, one naming an unscanned file, and one on a file that now pins a tier.

Verification

pnpm check:agent-model-declared — 18 self-test cases, then the real tree:

✓ R1 — frontmatter with name+description and NO model: → RED
✓ R6 — `model: inherit` with nothing justifying it → RED
✓ R7 — a justified inherit whose written reason was edited away → RED (self-invalidating)
✓ R10 — an empty scan → RED, never a green skip (#4690, the whole point)
✓ an indented `model:` inside description prose does NOT satisfy the rule → RED
✓ check-agent-model-declared self-test: 18 cases pass.
✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
  os-dev.md → opus
  0 justified inherit(s); no definition leaves its tier to the dispatching session.

Reverse verification — predicted RED, and red is what happened. The gate asserts exactly the presence of the line, so deleting model: opus from os-dev.md reproduces its own pre-#6688 state. It exits 1 and names the defect:

✗ check-agent-model-declared: 1 problem(s).
  • .claude/agents/os-dev.md
    declares no model: frontmatter has no `model:` key
    An agent definition with no `model:` INHERITS the dispatching session's model,
    so the role's tier becomes a property of whoever dispatched it and when (#6803).

os-dev.md was then restored byte-identical — it does not appear in this PR's diff.

Also green, to confirm no regression in the family: check:skill-frame-sync (12 cases, 4 copies isomorphic), check:skill-compatibility (18 cases, 11 SKILL.md files), check:doc-authoring (373 files), check:nul-bytes (6367 tracked files), and eslint on the new script. lint.yml re-parsed as YAML after editing.

No changeset: this PR touches only .claude/-adjacent tooling, scripts/, CI wiring and a root package.json script. It releases nothing, so it carries skip-changeset.

The trade-off the card asked me to check

Whether any caller legitimately wants an os-dev on a different tier. The trade-off is real but bounded, and it is already paid. A frontmatter pin does override inheritance for every caller — but not the explicit argument: the Agent tool resolves explicit model on the dispatch → frontmatter → inherit, so a caller that genuinely needs a different tier passes model and still wins. os-dev.md's own comment says exactly this ("that override takes precedence over this line, so pinning here costs nothing"). I found no dispatch site in the repo requesting a non-opus os-dev. Note also that this PR does not create that trade-off — PR #6688 did, five hours before the card was filed; this PR only stops the pin from being removable in silence.

One judgement the card did not settle

The card told me to write explanatory prose in Chinese "per repo convention". The repo's binding rule now says the opposite: AGENTS.md §Communication carries the maintainer ruling of 2026-08-08 (#6692), quoted verbatim and untranslated —

issue 和 PR 必须用英文,在 claude code 中和我讨论可以用中文。

— and os-dev.md:182-186 restates it as "title and explanatory prose in English". So this body is English. The dispatch prompt carried the pre-#6742 rule, which is itself an instance of the failure class #6803 cites in its own "Related" line (#6801: a rule that lives in the wrong file reaches the wrong readers).


Generated by Claude Code

…el (#6803)

An agent definition with no `model:` in its frontmatter INHERITS the dispatching
session's model, so the role's tier becomes a property of whoever dispatched it
and when, rather than a property of the role. `os-dev.md` already carries
`model: opus` (#6686 / PR #6688) — this adds the mechanism that keeps it there
and covers the next definition someone adds.

Why the assertion belongs on the definition rather than at the call site: the
caller-side half already existed and did not hold. `.claude/skills/pm-dispatch/
SKILL.md` §5 has said "pass `model: \"opus\"` on every dev dispatch" since before
the incident, and a seat that had read it still dispatched twelve os-dev agents
in a row without passing it, with no signal. The frontmatter holds regardless of
caller compliance.

The gate asserts PRESENCE, never which tier. Which model a role runs on is
maintainer policy (#6803 puts changing it out of scope), and a policy change
should not have to edit a gate to land. `model: inherit` stays legal for a role
that genuinely follows its caller, but only with a written justification the
script re-checks against the live file — so a deliberate inherit is a recorded
decision and a silent one is red.

Every absence is red, never a skip (#4690): missing directory, no .md files, no
frontmatter, empty value, unresolvable value, unjustified inherit, and stale or
dead INHERIT_JUSTIFIED entries. 18 self-test cases pin those red paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 11:41pm

Request Review

@github-actions github-actions Bot added the size/l label Aug 8, 2026
@os-project-manager os-project-manager added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/l labels Aug 8, 2026 — with Claude
@github-actions github-actions Bot added ci/cd dependencies Pull requests that update a dependency file labels Aug 8, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 9, 2026 01:13
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 6595262 Aug 9, 2026
31 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-6803-os-dev-model-declared branch August 9, 2026 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[agents] os-dev.md declares no model, so every dev agent silently inherits whatever model the PM session happens to be on

2 participants