Files
shockbot/utils
Colin McDonnell 5f3e46c42d fix: don't reuse disabled proxy key on workflow re-runs; non-fatal title-gen errors (#636)
* fix: don't reuse disabled proxy key on workflow re-runs; non-fatal title-gen errors

Three small surgical fixes addressing run https://github.com/pullfrog/app/actions/runs/25580969379:

1. **`/api/proxy-token` idempotency now checks `finalizedAt`.** GitHub re-runs
   share the same `run_id` (only `run_attempt` increments), so attempt N+1's
   action calls /api/proxy-token and inherits attempt N's `proxyKeyId`. The
   `workflow_run.completed` webhook between attempts retires that key on
   OpenRouter (`disableKey`), so attempt N+1 was getting back a disabled key
   and OpenRouter responded with `401 User not found` on every call. Falling
   through when finalized routes through the same billing gate
   (`handleRouterBilling` balance check), so no new attack surface.

2. **OpenCode title-gen / small-model errors no longer fatal.** OpenCode
   auto-spawns a small `agent=title small=true` background call at session
   start to name the thread, defaulting to `anthropic/claude-haiku-4.5`
   (anomalyco/opencode#1243). Pre-fix, the wrapper's `error` event handler
   treated any `type=error` as fatal, so a cosmetic title failure killed the
   run before primary inference even started. Now: stderr matching `small=true`
   sets a one-shot suppression flag for the next stdout `error` event, which
   is logged as a warning instead.

3. **Provider-error classifier puts auth patterns above rate-limit.** OpenRouter
   401 payloads bundle `x-ratelimit-*` response headers, and the loose
   `\brate[_ ]limit/i` pattern was winning. Added 401/403 status, `User not
   found`, `Invalid authentication`, `No auth credentials found` patterns
   ahead of rate-limit. Updated the existing 401-headers regression test to
   assert correct auth classification rather than `null`.

* opencode: correlate small-model error suppression by message, not by next-event

Pullfrog self-review on #636 flagged a real concurrency hole. OpenCode forks
the title-gen call (`session/prompt.ts:1452-1457` via `Effect.forkIn(scope)`)
so it races primary inference. The previous one-shot `suppressNextErrorEvent`
boolean had no per-call correlation: it was consumed by whichever stdout
`type=error` event landed next, regardless of which subagent produced it.
Under concurrent failures, a primary-agent error landing first could be
silently downgraded to a warning while the small-model error then propagated
fatally — the inverse of the bug the suppression was meant to prevent.

Replaced the boolean with a `Set<string>` of pending small-model error
messages. stderr extracts the inner `"message":"..."` from any classified
provider error tagged `small=true`; the stdout `error` handler suppresses
only when `event.error.data.message` matches a pending entry. Set is capped
at 32 entries so a long stream of small-model failures can't wedge memory.

Also corrected the comment that referenced "session summarizer" — verified
in opencode source that summarize() does NOT use `small: true`; only the
title generator does today (only `small: true` match in the codebase).

* revert: drop opencode title-gen suppression

We have no evidence — and can't construct a realistic scenario — where
title-gen fails on an otherwise-successful run. Title-gen and primary share
the same OPENROUTER_API_KEY and hit the same proxy/upstream; whatever breaks
one breaks the other. The original repro on run 25580969379 is fully
explained by the stale proxy key (fix #1) — title-gen happened to be the
first call that surfaced the auth error, but every subsequent primary call
would have died the same way.

Suppression code adds complexity (cross-stream correlation logic, message
matching, set capping) and a real failure mode of its own (a small-model
error with a unique message could mask an unrelated primary error landing
shortly after). Net negative. Removing.
2026-05-08 23:00:41 +00:00
..
2026-02-13 20:01:48 +00:00
2026-03-25 19:36:13 +00:00
2026-03-12 05:22:51 +00:00
2026-01-28 07:47:52 +00:00
2026-03-25 19:36:13 +00:00
2026-03-12 05:22:51 +00:00
2026-03-27 16:09:13 +00:00
2026-04-16 16:33:49 +00:00
2026-03-25 19:36:13 +00:00