fix(core): converge model-API ownership, provider auth-literal parity, and structured failover classification#104886
Conversation
…vider auth-literal parity test gateway-startup-plugin-ids duplicated the built-in model-API set byte-for-byte with nothing enforcing sync; provider-config-owner now owns it. New parity test proves bundled provider manifests and runtime provider.auth declare the same optionKey/cliFlag/envVar/method literals, guarding against silent wizard-vs-runtime auth drift. Part of #104219 (row 12).
…atch classification context_overflow and transient (timeout/server_error/overloaded) FailoverError reasons now classify structurally like billing/rate_limit already do, instead of relying on message text that may lack overflow or HTTP-status tokens. A typed transient failure without a leading status token now takes the single transient retry; typed context overflow suppresses the generic failure reply. Part of #104219 (row 12).
…path Shipped behavior pins overloaded FailoverErrors to immediate dedicated copy (agent-runner-execution.test.ts 'surfaces typed overloaded failures'); only timeout and server_error reasons join the transient retry gate.
|
Codex review: needs real behavior proof before merge. Reviewed July 12, 2026, 12:08 AM ET / 04:08 UTC. Summary PR surface: Source +2, Tests +305. Total +307 across 5 files. Reproducibility: yes. at source level: current main relies on message-only gates, and focused tests can construct tokenless typed errors that select the wrong terminal behavior. Normal live provider paths do not currently emit the exact shape, so this is not a high-confidence live reproduction. Review metrics: 2 noteworthy metrics.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Preserve the single canonical model-API owner and fail-closed auth parity ratchet, and land the structured-reason classification after the terminal-catch owner explicitly accepts focused tests plus real no-regression logs as sufficient proof for a path current runtime formatting does not naturally emit. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main relies on message-only gates, and focused tests can construct tokenless typed errors that select the wrong terminal behavior. Normal live provider paths do not currently emit the exact shape, so this is not a high-confidence live reproduction. Is this the best way to solve the issue? Yes, subject to proof acceptance. Single-sourcing the ownership fact and using the closed FailoverError reason contract are narrower and more maintainable than synchronized lists or formatter-dependent policy, and the contract test now fails closed for included API-key methods. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against ec11440176bb. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +2, Tests +305. Total +307 across 5 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (5 earlier review cycles)
|
…r registers Silent skip on missing runtime registration recreated the drift class the test guards (ClawSweeper finding); capability-only plugins now must register zero text providers to be exempt.
Loading built plugin dists pulls large module graphs into the shared vitest worker cache and broke co-resident vi.mock unit tests (memory-host-sdk embeddings, checks-node-compact-large-2). An explicit dynamic import of the surface loader trips the lane classifier's dynamic-import rule, moving the file to the plugins project where dist loading is the norm.
|
Review findings addressed, real behavior proof added (PR body Evidence updated), and CI triaged. Findings addressed
Live proof (ephemeral gateway, real Telegram path — details in PR body)
CI triage — remaining failures are current
This PR's own surface is green: @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
An unprobeable api-key choice left its flag/env literals unchecked while CI stayed green (ClawSweeper finding). The probe now supplies a real agent dir and placeholder preflight opts (sentinel still bound to the declared optionKey only, preserving the key-correctness proof), so every api-key method must reach resolveApiKey — this immediately exercised cloudflare-ai-gateway's account/gateway preflight path.
|
Fail-closed finding fixed in Validation on Blacksmith Testbox at On the remaining proof P1 (exact tokenless typed-reason live delta): as documented in the PR body, every reachable live path currently formats provider failures with a leading @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Maintainer proof override: accepting the structured failure-classification subchange on unit-test evidence. Live A/B established behavior parity on all reachable paths (no regression); the tokenless typed-reason case is contract hardening pinned by the new regression tests. Landing. |
What Problem This Solves
Row 12 of #104219: the three concrete drift/misclassification exposures that survived re-verification of the tracker's remaining seams.
CORE_BUILT_IN_MODEL_APISwas defined byte-for-byte in bothsrc/plugins/provider-config-owner.tsandsrc/plugins/gateway-startup-plugin-ids.ts. Both decide "is this model API plugin-owned"; nothing enforced sync, so adding a core API to one silently diverges startup plugin-id resolution from provider-config ownership hints.providerAuthChoices/setup.providers— consumed by the wizard/CLI-flag control plane without loading plugin runtime) and the plugin entry'sprovider.auth(consumed at request time). IfoptionKey/env var/CLI flag drift, the wizard stores the user's key under one name and runtime reads another — the provider looks configured but auth silently fails. No test compared the two surfaces.src/auto-reply/reply/agent-runner-execution.ts, the context-overflow and transient-HTTP branches classified by message text only (isLikelyContextOverflowError,isTransientHttpError— the latter requires a leading HTTP status token). A typedFailoverErrorwithreason: "context_overflow"but sanitized message fell through to the generic failure reply; a typed transient failure without a status token skipped the single transient retry. Billing and rate-limit already classified structurally; these two branches were the drift surface.Why This Change Was Made
CORE_BUILT_IN_MODEL_APISnow has one owner (provider-config-owner.ts, exported);gateway-startup-plugin-ids.tsimports it. Import direction verified cycle-free (pnpm check:import-cyclesgreen incheck:changed).src/plugins/bundled-provider-auth-literal-parity.test.tsdiscovers bundled plugins programmatically (listBundledPluginMetadata), loads each plugin's public entry via the existingloadBundledPluginPublicSurfacehelper (noextensions/*/src/**deep imports), registers it withcreateCapturedPluginRegistration, and table-drives every api-key-styleproviderAuthChoicesentry: manifestmethod↔ runtimeauth.id,cliFlag↔ probedflagName,optionKeyproven by the option value reachingflagValue, env var membership insetup.providers[].envVars/provider.envVars. Capability-only manifests (video/image onboard flags with no text provider) and oauth-style choices are excluded by construction to avoid false positives.context_overflowreason → overflow handling;timeout/server_errorreasons → the existing single transient retry. Deliberately excluded:overloaded— a shipped-behavior test ("surfaces typed overloaded failures without rate-limit cooldown copy") pins typed overloaded failures to immediate dedicated user copy, and routing them into the silent retry would change that product behavior. The first draft includedoverloaded; the pinned test caught it and the gate was narrowed (commitc61e59abde9).User Impact
timeout/server_error) get the same single retry that status-token-bearing messages already got, so a transient blip mid-conversation recovers instead of surfacing an error.Evidence
Real behavior proof (ephemeral gateway, real Telegram channel path)
Built this branch, ran a real gateway (temp
OPENCLAW_STATE_DIR, loopback) with mock Telegram Bot API + mock OpenAI-completions provider, and drove real reply turns:Transient HTTP provider error before reply (HTTP 500: …). Retrying once in 2500ms., inter-request gaps[470, 859, 3789]ms(the 3.8s gap is the agent-runner retry delay), and the user-visible Telegram reply is the recovered text — not an error.context_length_exceededon every request; after auto-compaction also fails, the user-visible reply is the targetedContext overflow: prompt too large for the model…copy, not the generic failure text.Honest scope of the live delta: an A/B run with only
agent-runner-execution.tsrolled back tomainrecovers identically on these paths, because the runtime currently formats provider failures with a leading status token (HTTP 500: …), which main's message-only gate already matches; held-socket timeouts route through the embedded idle-watchdog (llm-idle-timeout→ surface) and never reach this catch. The structured gate therefore changes no reachable live path today — it pins the retry/overflow decision to the typedFailoverError.reason(the canonical fact) instead of the message formatter's prefix, so a formatter change can't silently alter retry behavior. The unit-level delta (typed reason + token-less message → retry/overflow copy) is covered by the new regression tests; the live runs prove behavior parity and no regression.Review-loop corrections (found during validation)
overloadedfailures into the transient retry; the shipped-behavior test caught it (overloaded failures must surface dedicated copy immediately). Gate narrowed totimeout/server_errorinc61e59abde9.unit-fastCI lane, where loading built plugin dists poisons the shared worker module cache and broke unrelatedvi.mock-based tests (memory-host-sdkembeddings inchecks-node-compact-large-2; bisected to a single surface load). Fixed in320aab1a37bby making the loader import dynamic, which moves the file to thepluginslane where dist loading is the norm.3eed7c70cad).Static evidence
git showofgateway-startup-plugin-ids.tsbefore this change vsprovider-config-owner.ts:5— identical 8-element sets; now one definition (−9 prod LOC).agent-runner-execution.test.ts:FailoverErrorserver_error/timeoutwithout a leading status token → exactly one transient retry then success; structuredcontext_overflowwith non-overflow message → overflow copy, generic reply suppressed. The pre-existing overloaded pin stays green.tbx_01kx9yh69g4043rppaj23e49tv):pnpm check:changedexit 0; focused suites green on headc61e59abde9—bundled-provider-auth-literal-parity+bundled-plugin-metadata(36 files' worth of parity cases) and fullagent-runner-execution.test.ts(253 tests).git diff --numstatvs base: prod +11/−13 (net −2) acrossprovider-config-owner.ts,gateway-startup-plugin-ids.ts,agent-runner-execution.ts; tests +266.Part of #104219 (row 12: small hardening batch from the 2026-07-12 re-verification).