Skip to content

[Phase 3] Standard: name a model family, never pin a model version — replace every hard-coded Claude model id and lint for it #1979

Closed

Description

@don-petry

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-6 or claude-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_MODEL in scripts/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

  1. One resolver. Add ai_model_for_family <family> to scripts/lib/engine-models.sh. It returns the current model id for opus, sonnet or haiku, and warns and fails for an unknown family. Derive it from the existing chains so there is a single source of truth: opus = the deep primary, sonnet = the action primary, haiku = the triage primary. It must honour AI_MODELS_CLAUDE and the CLAUDE_<TIER>_MODEL_CHAIN overrides. 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 example opus resolving to an older Opus), use the resolver.
  2. 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 calling claude. 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.sh validates a family, or any claude-* id, instead of a fixed known_engines list, and resolves families at run time.
  3. Token records keep the real id. TOKEN_LOG_FILE records store the resolved model id, not the family name, so model-pricing.sh still prices them. Add a test.
  4. Lint. Add a check to lint.yml (a small script under scripts/ with bats coverage) that fails when claude-(opus|sonnet|haiku|fable)-[0-9] appears in .github/, scripts/, prompts/, agents/ or personas/. It skips the allowed files above and any line carrying model-pin-ok:. It prints each offending file:line. It must pass on the finished branch.
  5. Document the standard. Add a "Model selection" section to AGENTS.md (the standard, the exceptions and the resolver) and to docs/engine-configuration.md (how to request a family, and how operators override). Link the companion .github issue for the org-wide standards/ text.
  6. Validation. shellcheck -S warning and actionlint are clean. Every test_engine*.bats, the new resolver/lint bats and test_model_dispatch pass. gh aw compile (the repo's existing gh-aw compile check) passes for the changed .md workflows. The PR states which workflows now resolve to a different model than before (for example Sonnet 4.6 → the current Sonnet).

Dev Notes

Likely target surface

  • scripts/lib/engine-models.sh (resolver) plus tests/dev-lead/unit/test_engine_models.bats
  • the workflows and scripts in the table
  • a new scripts/check-model-pins.sh plus 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

  1. don-petry commented on Sep 29, 2026

    @don-petry
    CollaboratorAuthor

    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.json in petry-projects/.github).

    This issue stays labeled dev-lead and will be re-attempted automatically once
    the queue drains — no action is required.

  2. don-petry commented on Sep 29, 2026

    @don-petry
    CollaboratorAuthor

    The org-wide side of this standard is proposed in combsmith/.github#1199, a discussion in Ideas. It covers standards/agent-standards.md, the claude-opus-4-6 default in feature-ideation-reusable.yml, and ci-standards.md. Keep this repo's AGENTS.md "Model selection" wording consistent with it, and link it from that section.


    Generated by Claude Code

  3. added
    dev-leadFor dev-lead agent pickup
    and removed
    dev-leadFor dev-lead agent pickup
    on Sep 30, 2026
  4. don-petry commented on Sep 30, 2026

    @don-petry
    CollaboratorAuthor

    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 first
    model of that tier's resolved chain, so the existing chains stay the one source of truth:

    family tier current primary
    opus deep claude-opus-5-5
    sonnet action claude-sonnet-5
    haiku triage claude-haiku-4-5-20251001
    • Honours overrides: checks the per-tier CLAUDE_<TIER>_MODEL_CHAIN env first (the
      existing ai_models_chain does not read those — that happens in engine.sh),
      then falls back to ai_models_chain claude <tier> (which already honours
      AI_MODELS_CLAUDE), then the built-in default. Returns the chain's first id.
    • Unknown family → ::warning:: to stderr + return 1.
    • Bash 3.2-safe (indirect ${!var}, case, parameter expansion — no
      declare -A/mapfile/${x^^}).
    • bats: each family resolves to its primary; a CLAUDE_DEEP_MODEL_CHAIN override wins;
      an AI_MODELS_CLAUDE override wins; unknown family warns + non-zero.

    AC2 — replace every pin with a family

    • .github/workflows/initiative-planner.yml — input model default
      claude-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) writing
      steps.model.outputs.model; --model uses it. (checks out repo — resolver available.)
    • .github/workflows/idea-triage.yml — input model default claude-sonnet-4-6 →
      sonnet; same resolve step; --model uses it.
    • .github/workflows/issue-triage.md — frontmatter engine: claude-sonnet-4-6 →
      engine: sonnet. (Consumed by scripts/aw.sh, which will resolve families; also
      gh aw compile — a non-canonical engine string compiles today, so a family name is
      accepted.)
    • .github/workflows/ci-failure-analyst.md — models: claude: [claude-sonnet-4-6]
      → [sonnet] (compile-only; runtime is the frozen .lock.yml stub).
    • .github/workflows/release-notes.md, stale-manager.md (prose specs, no
      frontmatter) — reword the claude-sonnet-4-6 mention to name the family.
    • scripts/aw.sh — accept a family (opus|sonnet|haiku) OR any claude-* id (drop
      the fixed known_engines set); resolve a family to a real id at run time via the
      resolver before claude --model.
    • Standalone scripts (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) — source engine-models.sh and
      resolve via ai_model_for_family sonnet / haiku instead 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 claude is invoked, the resolved id
    (not the family) is what flows to emit_token_record → TOKEN_LOG_FILE. Add a test
    asserting a record written after resolution carries the concrete id.

    AC4 — lint check

    New scripts/check-model-pins.sh: fail when claude-(opus|sonnet|haiku|fable)-[0-9]
    appears under .github/, scripts/, prompts/, agents/, personas/; skip
    allowed files and any line carrying model-pin-ok:; print each file:line. New bats
    test + a check-model-pins job in lint.yml. Must pass on the finished branch.

    Allowed exceptions (marked # model-pin-ok: <reason> or in an allow-listed file):

    • the resolver scripts/lib/engine-models.sh (defines the mapping);
    • price data scripts/lib/model-pricing.tsv;
    • recorded data (fixtures, held-out evals, baselines);
    • the eval judge AB_JUDGE_MODEL in scripts/evals/model-ab.sh (must stay pinned).
    • Comment mentions that must remain (e.g. engine.sh headroom fallback + a couple of
      explanatory comments, pr-review.yml/persona-runner-reusable.yml comments,
      ET_BASELINE_MODEL historical baseline, token-metrics.sh comments) — reword where
      natural, else mark model-pin-ok:.

    AC5 — docs

    Add a "Model selection" section to AGENTS.md and docs/engine-configuration.md
    stating 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 mark
    model-pin-ok:). Link companion issue petry-projects/.github#1199.

    AC6 — verification

    shellcheck -S warning, actionlint, test_engine*.bats, the new resolver/lint/token
    bats, test_model_dispatch, and scripts/check-model-pins.sh all green;
    gh aw compile (CI) for changed .md. The PR body will list the workflows that now
    resolve 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

  5. don-petry commented on Sep 30, 2026

    @don-petry
    CollaboratorAuthor

    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.

    Review 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.

  6. added 10 commits that reference this issue on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dev-leadFor dev-lead agent pickupenhancementFeature requestsinitiativeEpic / initiative tracking issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions