Skip to content

[Phase 2 blocker] Deep-tier reviews fail on v1-next: invalid claude-sonnet-5-0 ID (404), stale cached Claude Code CLI 2.1.138, and a rate-limit regex that hides every Claude error #1957

Closed

Description

@don-petry

Summary

The Opus 5.5 canary (#1899) found a regression: every pr-review that escalates to the deep tier fails on pr-review/v1-next (v1.17.0 and v1.18.0, since 2026-09-25 14:07Z). Low-risk PRs are unaffected because they stop at triage plus a Fable 5 check. Four defects combine to cause it. Three were latent under Opus 4.8, because 4.8 always answered first.

Evidence (identical in every failed run inspected, e.g. runs 36280169426 on #1906 and 36280307395 on #1794, debug artifact pr-review-debug-84020-1):

[tier2] deep review (claude-opus-5-5) + rubber duck (o4-mini via copilot)
::warning::[claude] model claude-opus-5-5 throttled (rc=1) — trying next in chain      # ~1s, 0 tokens
{"is_error":true,"api_error_status":404,"result":"There's an issue with the selected model (claude-sonnet-5-0). It may not exist or you may not have access to it."}
::warning::[claude] model claude-sonnet-5-0 throttled (rc=1)
triage stderr: [copilot] ... flags=--deny-tool *   →  Invalid --deny-tool value. Error: Invalid rule format: *
##[error]Review failed ... (exit code 1)

Model ID audit (checked against the current Anthropic model catalog)

Where Current value Valid? Notes / recommendation
Triage ENGINE_TRIAGE_MODEL, chain head, headroom probe claude-haiku-4-5-20251001 ✅ Full snapshot ID of Haiku 4.5 (alias claude-haiku-4-5). No newer Haiku exists, so no bump.
Deep ENGINE_DEEP_MODEL, chain head claude-opus-5-5 ✅ ID valid Fails at runtime on CI, cause unconfirmed (see D2/D3). Thinking is always on; effort is the only control and defaults to medium, so the deep tier's --effort high pin is correct. thinking: disabled and budget_tokens return 400; forced tool_choice returns 400.
Action ENGINE_ACTION_MODEL; 2nd hop in triage, deep and action chains; scripts/aw.sh model allowlist claude-sonnet-5-0 ❌ invalid → 404 The correct ID is claude-sonnet-5 (no -0). The action tier has probably been silently falling back to claude-opus-4-8 since #1100.
Audit / single ENGINE_AUDIT_MODEL, ENGINE_SINGLE_MODEL claude-fable-5 ✅ Still served. Its successor, claude-fable-5-1, has the same price ($10/$50) and is stronger, but is not a drop-in: forced tool_choice returns 400 and thinking blocks are bound to the model. Optional bump; gate it on the audit held-out A/B (add it as an arm in #1901) rather than swapping blind.
Fallback hops (audit/action/single) claude-opus-4-8, claude-opus-4-7 ✅ Valid. #1900 replaces the 4.8 hops with Opus 5.5, but only after Opus 5.5 passes liveness (AC-5 below).
Duck DUCK_MODEL (claude duck engine) claude-sonnet-4-6 ✅ Previous-generation Sonnet. Optional bump to claude-sonnet-5 once the ID fix lands. Not required.
persona-runner-reusable.yml --model claude-opus-5-5 --fallback-model opus --effort high ✅ ID valid Runs on the same stale CLI (D2), so likely affected too. opus is a CLI alias that resolves per CLI version.
model-pricing.tsv glob claude-sonnet-5-* ⚠️ Does not match claude-sonnet-5. Fixing the ID without a matching price row would leave Sonnet 5 calls unpriced.

Also available but unused: claude-opus-5 ($5/$25). No action proposed.

Defects

  • D1: invalid model ID. claude-sonnet-5-0 returns HTTP 404 and appears in scripts/engine.sh (lines ~122, 147, 151, 153) and scripts/aw.sh:93.
  • D2: stale Claude Code CLI on CI. Runners log claude-code cache hit: 2.1.138. The Cache claude-code CLI step uses the fixed key claude-code-latest-Linux, and install_claude returns early when a claude binary exists, so CLAUDE_CODE_VERSION: latest is never re-resolved. The current release is 2.1.283. 2.1.138 probably predates the Opus 5.5 launch (2026-09-22), so it may send a request shape Opus 5.5 rejects (e.g. thinking disabled or budget_tokens, both 400 on Opus 5.5) or not know the model. This is the leading hypothesis for the Opus 5.5 failure, and is unconfirmed; the other candidates are no account access, or a genuine 429.
  • D3: the rate-limit classifier masks every Claude error. _rate_limit_pattern contains claude.*usage|usage.*claude. Every --output-format json result includes the model name and a "usage" object, so any Claude CLI error (400, 404, …) is classified as "throttled" and silently falls through. On top of that, _claude_chain_invoke keeps only the last hop's stdout, so Opus 5.5's actual error text is lost.
  • D4: Copilot fallback is broken. It passes --deny-tool '*', which the current gh copilot rejects (Invalid rule format: *). The duck logs Classic Personal Access Tokens (ghp_) are not supported by Copilot, and the Gemini fallback is out of credits (prepayment credits depleted; [Phase 2] Swap the deep tier to claude-opus-5-5 in engine.sh (model, chain, label, effort) #1898's run also hit gemini-2.5-pro is no longer available to new users). With D1–D3, a deep review has no working fallback.

Acceptance Criteria

  1. ID fix (D1): every live claude-sonnet-5-0 in scripts/engine.sh and scripts/aw.sh becomes claude-sonnet-5, with the affected bats tests updated. Historical docs and fixtures stay unchanged. model-pricing.tsv gets an appended row (append-only convention) with a glob matching claude-sonnet-5 at the same effective-dated rates as the existing claude-sonnet-5-* rows, and price_for claude-sonnet-5 <date> is covered by a test.
  2. Current CLI on CI (D2): the workflows that install Claude Code (pr-review.yml, dev-lead-reusable.yml, persona-runner-reusable.yml, ci-failure-analyst-reusable.yml, and any other workflow using the claude-code-latest-* cache) resolve a current version. Either key the cache on the resolved version, or pin CLAUDE_CODE_VERSION to a reviewed version that supports Opus 5.5 and bump it deliberately. Each run logs claude --version.
  3. Honest error classification (D3): a JSON result with is_error: true and an api_error_status other than 429/529 is not treated as a rate limit. 404 is labelled "model unavailable" and 400 "invalid request". The next hop may still be tried, but the log says why, and each hop's stdout and stderr are kept in the cascade log. Remove claude.*usage|usage.*claude, or scope it so it can't match a JSON envelope. Bats covers a 404 envelope, a 400 envelope and a real 429.
  4. Deep-tier safety net: CLAUDE_DEEP_MODEL_CHAIN gets a known-good hop (claude-opus-5-5,claude-opus-4-8, then claude-sonnet-5 once AC-1 lands), so an Opus 5.5 failure degrades to Opus 4.8 instead of failing the review. Token records already carry the model actually used, so the canary report ([Phase 2] Canary go/no-go report: compare deep-tier cost, cache-read cost and latency by model from token-usage artifacts #1951) counts only real Opus 5.5 calls.
  5. Liveness (the epic's untracked prerequisite): after AC-1 to AC-3 reach v1-next, run the [Phase 2] Add a workflow_dispatch runner for model-ab.sh plus a liveness/effort probe, so the Opus 5.5 non-regression A/B actually executes #1950 probe (or an equivalent one-shot claude --print --model <id> --output-format json) with the CI credential for every ID in the chains: claude-haiku-4-5-20251001, claude-sonnet-5, claude-opus-5-5, claude-fable-5, claude-opus-4-8 and claude-opus-4-7. Record the result on [Phase 2] Canary the deep-tier swap through the v1-next ring and monitor before stable promotion #1899, including Opus 5.5's real error if it still fails.
  6. Copilot fallback (D4): replace --deny-tool '*' with a rule syntax current gh copilot accepts. Record whether the duck's classic PAT must become a fine-grained token (a secrets change for a maintainer). If this is too large, split it into its own issue linked here; AC-1 to AC-5 must not wait on it.
  7. shellcheck --severity=warning -x is clean, the touched bats suites pass, and the PR states that a next channel cut is required (autocut will do it) before the canary resumes.

Out of scope / decisions for the maintainer

Blocks #1899 (canary metrics can't be measured while every Opus 5.5 deep call fails) and therefore #1900. Epic #1895.

Activity

  1. don-petry commented on Sep 27, 2026

    @don-petry
    CollaboratorAuthor

    Decision: fix forward. No rollback of pr-review/v1-next. I've implemented this issue in #1958, so dev-lead doesn't need to pick it up.

    AC Status in #1958
    1. ID fix and pricing row ✅ claude-sonnet-5 in engine.sh and aw.sh; claude-sonnet-5 pricing rows plus tests
    2. Current CLI on CI ✅ latest resolved to the concrete npm version before the cache step (8 workflows); pr-review and dev-lead log claude --version
    3. Honest error classification ✅ claude.*usage removed; failed hops classified by api_error_status; per-hop output kept in cascade/claude-chain-hops.log (debug artifact); bats for 404, 400, 5xx and a real 429
    4. Deep safety hop ✅ claude-opus-5-5,claude-opus-4-8,claude-sonnet-5
    5. Liveness ⏳ after #1958 is cut to next (needs the #1950 probe, PR #1952)
    6. Copilot fallback 🟡 --deny-tool '*' replaced with --deny-tool shell --deny-tool write. Maintainer action still needed: the duck's classic PAT (ghp_) must become a fine-grained token, and Gemini is out of prepaid credits
    7. Lint, tests, channel-cut note ✅ see the PR's Test plan and Monitoring sections

    After merge, Release Manager autocut cuts a release to next within about 4h, and the canary on #1899 resumes from there.


    Generated by Claude Code

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

    bugBug reportsinitiativeEpic / initiative tracking issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions