fix(agent): treat a cron run's in-band model error as a failed run - #261
Open
williamwa wants to merge 1 commit into
Open
fix(agent): treat a cron run's in-band model error as a failed run#261williamwa wants to merge 1 commit into
williamwa wants to merge 1 commit into
Conversation
A model provider error (e.g. 402 Insufficient credits, 404 No endpoints) does not throw — pi-agent-core ends the turn with stopReason='error' and resolves normally. runScheduledJob therefore recorded the empty turn as a SUCCESS, so the schedule kept firing at its full cron cadence while every firing failed. During an account-wide credit outage that turned each frequent schedule (e.g. feed-scan) into a per-tick error storm — the "loop" users observed — and burned an extra introspection LLM call each firing. Nothing retries internally (confirmed: the OpenAI SDK doesn't retry 402/404, pi-agent-core doesn't retry stopReason='error', and the 10s scheduler tick can't produce sub-second bursts); the amplification was purely "error recorded as success -> re-fire at full cadence". Now handleAgentEvent stamps session.lastTurnModelError at message_end, and a cron runScheduledJob surfaces it as a ScheduledRunModelError after the turn completes. The central scheduler's existing failure path then applies exponential backoff (30s->1h) and auto-recovers on the next success — no permanent pause, which would be wrong for a transient shared-pool outage. Scoped to cron: `once` firings never re-fire, so their completion semantics are left unchanged. Also classify "404 no endpoints" as `unavailable` so the user-facing notice isn't the generic fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 11 minutes. View limit detailsLimit details: You’ve used all 3 included reviews currently available. Your 43 included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Essentials Run ID: 📒 Files selected for processing (5)
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
During an account-wide 402 Insufficient credits outage on the amiko/OpenRouter pool, every frequent cron schedule (e.g.
feed-scan-comment) looked like it was looping — a rapid burst ofassistant(stopReason=error) +agent_endevents, each firing an extra introspection LLM call.Root cause (there is no retry loop)
Traced the full path — nothing retries internally:
stopReason==='error'— it ends the turn and resolves.The real mechanism: a model error does not throw — the turn ends with
stopReason==='error'andwaitForSessionIdleresolves normally. SorunScheduledJobrecorded the empty turn as a success, and the schedule kept firing at its full cron cadence while every firing failed. The "burst" is many frequent schedules all erroring each tick during the shared-pool outage, each also spending an introspection call. Confirmed againstsession_events: 392 credit-exhaustion errors in one day, balance near-zero/negative, across many agents' feed-scan schedules.Fix
handleAgentEventstampssession.lastTurnModelError(classified kind + raw message) atmessage_endwhenstopReason==='error'; cleared at turn start.runScheduledJobreads it after the turn completes and, if set, throwsScheduledRunModelError. The scheduler's existing failure path then applies exponential backoff (30s→1h) and auto-recovers on the next success — no permanent pause (wrong for a transient shared-pool outage).type==='cron':oncefirings never re-fire, so their completion semantics are unchanged.404 No endpoints foundasunavailableso the user-facing notice isn't the generic fallback (the deadanthropic/claude-3.5-haikufallback surfaced this).What this does and does not do
Tests
scheduled-turn.test.ts: a cron run whose turn ended in an in-band model error now rejects withScheduledRunModelError(kindquota) and heals the dedicatedsession.queue; aoncerun on the same error does not throw and tears down normally.user-facing-error.test.ts:404 No endpoints foundclassifies asunavailable.InternalStateStore.researcherrors are onmain).🤖 Generated with Claude Code