Repository navigation
[Phase 3] Standard: name a model family, never pin a model version — replace every hard-coded Claude model id and lint for it #1979
Description
Story
As a maintainer,
I want every workflow, script and agent to name a model family (opus, sonnet, haiku) instead of a specific model version, with one place that maps each family to its current model,
so that a model upgrade is one edit (or one Actions-variable change), and never another codebase-wide sweep for stale pins like the ones this epic found.
The standard (maintainer decision, 2026-09-29)
Suggest a model family; never pin a model version. A caller may say which family suits the task (for example "Opus for the adversarial analyst pass, Haiku for classification"). It must not name a version id such as
claude-opus-4-6orclaude-sonnet-4-6. One resolver maps family → current model id (plus fallbacks), and changes there roll out through the normal release channels.
Allowed exceptions, each marked with an inline # model-pin-ok: <reason> comment:
- the resolver itself (
scripts/lib/engine-models.sh: the defaults, fallback chains and model notes); - price data (
scripts/lib/model-pricing.tsv, keyed by real ids); - recorded data (test fixtures, held-out eval sets, historical baselines);
- the eval judge (
AB_JUDGE_MODELinscripts/evals/model-ab.sh), which must stay fixed so A/B results stay comparable. Changing it is a deliberate, re-baselined decision.
Stale pins found on main (sweep of 2026-09-29)
| File | Pin |
|---|---|
.github/workflows/initiative-planner.yml (input default at L58, and the fallback at L225) |
claude-opus-4-8 |
.github/workflows/idea-triage.yml (L39, L100) |
claude-sonnet-4-6 |
gh-aw front-matter: .github/workflows/issue-triage.md, release-notes.md, stale-manager.md, ci-failure-analyst.md |
claude-sonnet-4-6 |
scripts/aw.sh (default engine at L281, and the known_engines set at L93–94) |
claude-sonnet-4-6; the set lacks the current models |
scripts/aw-dependency-advisory.sh, aw-docs-health-check.sh, aw-readme-refresh.sh, release-notes.sh, stale-manager.sh, pr_review_health.sh |
claude-sonnet-4-6 |
.github/workflows/feature-ideation.yml (comments) |
"Opus 4.6" (the pin itself is in petry-projects/.github's reusable; see the companion issue there) |
scripts/lib/engine-models.sh (the engine chains) is the resolver and is covered by #1978 (Sonnet 5.5) and #1901 (Fable deprecation). persona-runner-reusable.yml already derives its model from the deep chain.
Acceptance Criteria
- One resolver. Add
ai_model_for_family <family>toscripts/lib/engine-models.sh. It returns the current model id foropus,sonnetorhaiku, and warns and fails for an unknown family. Derive it from the existing chains so there is a single source of truth:opus= thedeepprimary,sonnet= theactionprimary,haiku= thetriageprimary. It must honourAI_MODELS_CLAUDEand theCLAUDE_<TIER>_MODEL_CHAINoverrides. It stays Bash 3.2-compatible. Add bats cases for each family, for an override and for an unknown family.- Alternative: a workflow with no access to
scripts/may pass the Claude CLI's family alias (--model opus|sonnet|haiku) only if a test or probe records what that alias resolves to under the pinned CLI version. Put the finding in the PR. If an alias lags the resolver (for exampleopusresolving to an older Opus), use the resolver.
- Alternative: a workflow with no access to
- Replace every pin in the table above. Workflow inputs default to a family, for example
model: { default: 'opus' }. They resolve it through the resolver, or through a verified CLI alias, before callingclaude. An explicit full model id passed by an operator is still honoured, because a pinned override is an operator choice, not a code pin. gh-aw front-matter names a family.scripts/aw.shvalidates a family, or anyclaude-*id, instead of a fixedknown_engineslist, and resolves families at run time. - Token records keep the real id.
TOKEN_LOG_FILErecords store the resolved model id, not the family name, somodel-pricing.shstill prices them. Add a test. - Lint. Add a check to
lint.yml(a small script underscripts/with bats coverage) that fails whenclaude-(opus|sonnet|haiku|fable)-[0-9]appears in.github/,scripts/,prompts/,agents/orpersonas/. It skips the allowed files above and any line carryingmodel-pin-ok:. It prints each offendingfile:line. It must pass on the finished branch. - Document the standard. Add a "Model selection" section to
AGENTS.md(the standard, the exceptions and the resolver) and todocs/engine-configuration.md(how to request a family, and how operators override). Link the companion.githubissue for the org-widestandards/text. - Validation.
shellcheck -S warningand actionlint are clean. Everytest_engine*.bats, the new resolver/lint bats andtest_model_dispatchpass.gh aw compile(the repo's existing gh-aw compile check) passes for the changed.mdworkflows. The PR states which workflows now resolve to a different model than before (for example Sonnet 4.6 → the current Sonnet).
Dev Notes
- This changes the model several scheduled workflows actually run (most move from Sonnet 4.6 to the current Sonnet). Call that out in the PR's Risk section. They are informational workflows, and the
pr-review/ dev-lead engines are unaffected beyond [Phase 3] Sonnet 5.5 rollout: price claude-sonnet-5-5 and make it the default Sonnet in every Claude chain #1978 and [Phase 3] Deprecate Fable 5: move the audit and single tiers to Opus 5.5 and remove Fable from every default chain #1901. sonnetresolves to Sonnet 5.5 only after [Phase 3] Sonnet 5.5 rollout: price claude-sonnet-5-5 and make it the default Sonnet in every Claude chain #1978 lands. Until then it resolves to Sonnet 5, which is still an upgrade over 4.6. Don't wait on [Phase 3] Sonnet 5.5 rollout: price claude-sonnet-5-5 and make it the default Sonnet in every Claude chain #1978.- Keep the eval judge pinned, and add the
model-pin-ok:marker with its reason. - Don't dispatch any workflow or release step from the PR.
Likely target surface
scripts/lib/engine-models.sh(resolver) plustests/dev-lead/unit/test_engine_models.bats- the workflows and scripts in the table
- a new
scripts/check-model-pins.shplus a bats test, and.github/workflows/lint.yml AGENTS.md,docs/engine-configuration.md
Extends epic #1895, as the maintainer requested. The companion issue in petry-projects/.github covers feature-ideation-reusable.yml (claude-opus-4-6) and the org standards/ docs.
Activity
don-petry commentedon Sep 29, 2026
Dev-Lead: implementation deferred — automation PR queue is full
No pull request was opened for issue #1979. The org-wide
open-automation-PR queue is currently at its signed-off cap (see
standards/pr-limits.jsoninpetry-projects/.github).This issue stays labeled
dev-leadand will be re-attempted automatically oncethe queue drains — no action is required.
don-petry commentedon Sep 29, 2026
The org-wide side of this standard is proposed in combsmith/.github#1199, a discussion in Ideas. It covers
standards/agent-standards.md, theclaude-opus-4-6default infeature-ideation-reusable.yml, andci-standards.md. Keep this repo'sAGENTS.md"Model selection" wording consistent with it, and link it from that section.Generated by Claude Code
don-petry commentedon Sep 30, 2026
Implementation plan — #1979 (name a model family, never pin a version)
Posting the plan before touching source, per the process. Work proceeds test-first.
AC1 —
ai_model_for_family <family>(single source of truth)Add to
scripts/lib/engine-models.sh. Maps family → tier and returns the firstmodel of that tier's resolved chain, so the existing chains stay the one source of truth:
claude-opus-5-5claude-sonnet-5claude-haiku-4-5-20251001CLAUDE_<TIER>_MODEL_CHAINenv first (theexisting
ai_models_chaindoes not read those — that happens inengine.sh),then falls back to
ai_models_chain claude <tier>(which already honoursAI_MODELS_CLAUDE), then the built-in default. Returns the chain's first id.::warning::to stderr +return 1.${!var},case, parameter expansion — nodeclare -A/mapfile/${x^^}).CLAUDE_DEEP_MODEL_CHAINoverride wins;an
AI_MODELS_CLAUDEoverride wins; unknown family warns + non-zero.AC2 — replace every pin with a family
.github/workflows/initiative-planner.yml— inputmodeldefaultclaude-opus-4-8→opus; add a resolve step (source scripts/lib/engine-models.sh;family →
ai_model_for_family, else honour an explicit full id) writingsteps.model.outputs.model;--modeluses it. (checks out repo — resolver available.).github/workflows/idea-triage.yml— inputmodeldefaultclaude-sonnet-4-6→sonnet; same resolve step;--modeluses it..github/workflows/issue-triage.md— frontmatterengine: claude-sonnet-4-6→engine: sonnet. (Consumed byscripts/aw.sh, which will resolve families; alsogh aw compile— a non-canonical engine string compiles today, so a family name isaccepted.)
.github/workflows/ci-failure-analyst.md—models: claude: [claude-sonnet-4-6]→
[sonnet](compile-only; runtime is the frozen.lock.ymlstub)..github/workflows/release-notes.md,stale-manager.md(prose specs, nofrontmatter) — reword the
claude-sonnet-4-6mention to name the family.scripts/aw.sh— accept a family (opus|sonnet|haiku) OR anyclaude-*id (dropthe fixed
known_enginesset); resolve a family to a real id at run time via theresolver before
claude --model.pr_review_health.sh,aw-docs-health-check.sh,aw-dependency-advisory.sh,release-notes.sh,stale-manager.sh,aw-readme-refresh.sh,mcp_connectivity_check.sh) — sourceengine-models.shandresolve via
ai_model_for_family sonnet/haikuinstead of the literal--model claude-sonnet-4-6/claude-haiku-4-5-20251001.AC3 — token records keep the real id
Because families resolve to a real id before
claudeis invoked, the resolved id(not the family) is what flows to
emit_token_record→TOKEN_LOG_FILE. Add a testasserting a record written after resolution carries the concrete id.
AC4 — lint check
New
scripts/check-model-pins.sh: fail whenclaude-(opus|sonnet|haiku|fable)-[0-9]appears under
.github/,scripts/,prompts/,agents/,personas/; skipallowed files and any line carrying
model-pin-ok:; print eachfile:line. New batstest + a
check-model-pinsjob inlint.yml. Must pass on the finished branch.Allowed exceptions (marked
# model-pin-ok: <reason>or in an allow-listed file):scripts/lib/engine-models.sh(defines the mapping);scripts/lib/model-pricing.tsv;AB_JUDGE_MODELinscripts/evals/model-ab.sh(must stay pinned).engine.shheadroom fallback + a couple ofexplanatory comments,
pr-review.yml/persona-runner-reusable.ymlcomments,ET_BASELINE_MODELhistorical baseline,token-metrics.shcomments) — reword wherenatural, else mark
model-pin-ok:.AC5 — docs
Add a "Model selection" section to
AGENTS.mdanddocs/engine-configuration.mdstating the standard (name a family; the resolver returns the current id; overrides via
AI_MODELS_CLAUDE/CLAUDE_<TIER>_MODEL_CHAIN; when a full id is warranted markmodel-pin-ok:). Link companion issuepetry-projects/.github#1199.AC6 — verification
shellcheck -S warning,actionlint,test_engine*.bats, the new resolver/lint/tokenbats,
test_model_dispatch, andscripts/check-model-pins.shall green;gh aw compile(CI) for changed.md. The PR body will list the workflows that nowresolve to a different model (Sonnet 4.6 → current Sonnet 5, i.e. idea-triage,
issue-triage, ci-failure-analyst, release-notes, stale-manager, readme-refresh,
docs-health, dependency-advisory, pr_review_health; initiative-planner Opus 4.8 → Opus
5.5).
Notes
sonnetresolves to Sonnet 5 today (5.5 after [Phase 3] Sonnet 5.5 rollout: price claude-sonnet-5-5 and make it the default Sonnet in every Claude chain #1978 lands) — automatic, no code change.don-petry commentedon Sep 30, 2026
Dev-Lead: Implementation Complete — PR #1983
The implementation for issue #1979 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.
dbe176c720df8da3d2ad81e1b343aac3861cc438main: 29 file(s), +417/-40 linesReview happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.
fix(#1979): derive the family test from the default chains; pin issue…
fix(#1979): address CodeRabbit's review on the model-family standard
fix(#1979): address cubic's review on the model-family standard
fix(#1979): re-validate aw.sh's resolved model id before calling claude
fix(#1979): address cubic's second review on the model-family standard
fix(#1979): scope aw compile's model check to the claude runner; reje…
fix(#1979): address cubic's third review on the model-family standard
fix(#1979): pass vars.AI_MODELS_CLAUDE to every workflow that resolve…
fix(#1979): aw compile rejects every non-string engine, including nul…
feat: implement issue #1979 — [Phase 3] Standard: name a model family…