Skip to content

Commit 6595262

Browse files
ci(agents): gate that every .claude/agents/ definition declares a model (#6803) (#6836)
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. Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn Co-authored-by: Claude <noreply@anthropic.com>
1 parent 958cc13 commit 6595262

3 files changed

Lines changed: 586 additions & 0 deletions

File tree

.github/workflows/lint.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,33 @@ jobs:
878878
- name: Check SKILL.md compatibility declarations match the workspace majors
879879
run: pnpm check:skill-compatibility
880880

881+
# The fourth gate over the .claude/ file tree, and the first to read
882+
# .claude/agents/ at all (#6803). An agent definition that declares no `model:`
883+
# INHERITS the dispatching session's model, so the role's tier is set by whoever
884+
# dispatched it and when — a property of the caller's ambient state rather than
885+
# of the role. Measured cost: four devs dispatched from one smaller-model session
886+
# all died on the same shared quota wall, three leaving uncommitted and wholly
887+
# ungated work in their worktrees (#6686). The failure is batched, and invisible
888+
# to the dispatcher, whose pre-dispatch checks never ask what model the batch runs.
889+
#
890+
# The caller-side half of this rule already existed and did not hold: pm-dispatch
891+
# SKILL.md §5 has said 'pass `model: "opus"` on every dev dispatch' since before
892+
# that incident, and a seat that had read it still dispatched twelve agents in a
893+
# row without passing it. That is why the assertion is over the DEFINITION, which
894+
# holds regardless of caller compliance, rather than more prose at the call site.
895+
#
896+
# It asserts PRESENCE, never which tier — the tier is maintainer policy (#6803
897+
# puts changing it out of scope), and a policy change should not have to edit a
898+
# gate to land. `model: inherit` stays legal for a role that genuinely follows its
899+
# caller, but only with a written justification the script re-checks against the
900+
# live file, so a deliberate inherit is a recorded decision and a silent one is red.
901+
#
902+
# Same job and same reasons as its neighbours: no paths filter and required, so it
903+
# cannot go dormant on exactly the PR that breaks it — and a `.claude/**` filter
904+
# would blind it to the PR that adds a new agent definition anywhere else.
905+
- name: Check every agent definition declares a model
906+
run: pnpm check:agent-model-declared
907+
881908
- name: Check the react-blocks contract is in sync with the spec
882909
run: pnpm --filter @objectstack/spec check:react-blocks
883910

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"check:skill-frame-sync": "node scripts/check-skill-frame-sync.mjs --self-test && node scripts/check-skill-frame-sync.mjs",
4242
"check:skill-frame-freshness": "node scripts/check-skill-frame-freshness.mjs --self-test && node scripts/check-skill-frame-freshness.mjs",
4343
"check:skill-compatibility": "node scripts/check-skill-compatibility-version.mjs --self-test && node scripts/check-skill-compatibility-version.mjs",
44+
"check:agent-model-declared": "node scripts/check-agent-model-declared.mjs --self-test && node scripts/check-agent-model-declared.mjs",
4445
"check:adr-anchors": "node scripts/check-adr-anchors.mjs --self-test && node scripts/check-adr-anchors.mjs",
4546
"check:adr-links": "node scripts/check-adr-links.mjs --self-test && node scripts/check-adr-links.mjs",
4647
"check:platform-checklist": "node scripts/checklist-select.mjs --self-test && node scripts/check-platform-checklist.mjs",

0 commit comments

Comments
 (0)