Repository navigation
fix(execution): classify SDK run aborts and refuse doomed provider and media requests before sending - #9211
Conversation
…d media requests before sending - Recognise the Anthropic, OpenAI and Groq SDKs' APIUserAbortError (name 'Error') as an abort, with the run's abort signal deciding whether a failure is the run's own Stop or time limit - Agent blocks a user stopped soft-complete again instead of failing; the agent handler no longer rewrites a stopped run's abort into "Provider request timed out" - Provider catch logs: a user Stop at info, a run timeout at warn, an author fault at info, everything else still at error - Media tools (audio, video, image) validate an explicit output path before generating, so a stale moved-folder path no longer bills a generation it then cannot save - Gemini TTS refuses a voice it does not offer before submitting, listing the valid voices - Gemini refuses a request with no user content as EmptyProviderInputError instead of the SDK's opaque "contents are required" - File archive downloads no longer log a client disconnect as a failure; resume failures log once per attempt through logFailureOnce
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
All reported issues were addressed across 42 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
[High impact] The changes since the previous review appear safe to merge. SummaryThe PR separates stopped Agent runs from provider failures and rejects invalid media requests before sending them.
|
…ute a lost resume claim to the caller
|
@greptile |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
|
@greptile |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Summary
APIUserAbortErrorwithname === 'Error', so the name check ('AbortError', plus an'APIUserAbortError'name check that could never match) missed every SDK abort. A user Stop therefore painted the Agent block failed and logged it at ERROR asinternal. NewisRunAbortError(error, signal)recognises the SDK classes byinstanceofand treats the run's abort signal as the deciding fact: an abort-shaped error while the run is still live stays a real failure.BlockExecutorandruntime-contextnow use it.AgentBlockHandler.handleExecutionErrorrewrote anyAbortErrorinto a transport-timeout message markedthird_party_server, user Stops included. A stopped run's abort now passes through unchanged. A run-timeout abort is still rewritten as before.providerFailureLogLevel: a user Stop logs at info, a run time limit at warn, aUserFailureat info, and everything else still at error. This applies to every provider'sError in X requestcatch and to OpenAI's transport-failure log.generate_audio,generate_videoandgenerate_imagenow validate an explicit output path (validateCopilotWorkspaceFileWriteTarget, the same check Function exports run) before calling the provider. Before, a path into a folder that had since moved was refused only at write time, after fal/Gemini had billed, and the model retried and paid again. The write still re-validates.Brian,Liam, …) now fail before submitting, and the error lists the valid voices. This builds on fix(media): send ElevenLabs TTS text under the field fal requires #9195.EmptyProviderInputError(fix(providers): refuse an empty Responses input before sending instead of retrying a guaranteed 400 #9196's abstraction; the constructor takes an optional message) instead of the SDK's opaquecontents are required. Gemini needs a user turn even when a system prompt is set. The error is nonretryable and attributed to the author.Failed to build file archive, because a closed archive is the deciding fact. Resume failures (manager, background job, poll dispatcher) now log once per attempt throughlogFailureOnceinstead of three times.Out of scope: Anthropic's empty-input behaviour (it injects a
Hellouser turn) is left unchanged. "Assistant message prefill" 400s on newer Claude models come from a conversation ending in an assistant turn. That happens either because the author configured it that way (prefill used to be a supported technique) or because history ends on an assistant turn and the new user turn is empty. Neither has a single correct fix, so this PR does not change it.Type of Change
Testing
block-executor.test.ts: real Anthropic/OpenAI/Groq SDK requests (a fetch that only rejects on cancel). A user Stop soft-completes, a run timeout fails the block attributed touser, and a request cancelled while the run is live fails asinternal. Red against the old name check, against the SDK classes removed, and against the signal guard removed.providers/anthropic/core.test.ts: real SDK client. A Stop logs at info, a run timeout at warn, a genuine 500 at error.handler.test.ts: a stopped run's abort passes through unrewritten, and a run timeout is still reported as a provider timeout.write-target-precheck.integration.ts(real Postgres): for audio, video and image, a moved-folder target is refused without calling the provider and without recording a cost. A target at the folder's current path still generates and writes.falai.test.ts: an unsupported Gemini TTS voice is refused with nothing submitted.gemini/core.test.ts: realGoogleGenAI. System-only and all-empty requests now raiseEmptyProviderInputErrorinstead ofcontents are required.archive-presenter.test.ts: a disconnect that tears down storage with Node's bareabortedno longer logs, and a failure with the client still connected still logs.resume-execution.test.ts: a failure the resume manager already logged is not logged again.bun run lintpasses, and so docheck:test-patterns,comment-hygiene,utils,explicit-any,source-conventions,boundaries,client-boundaryandunused-exports.tscover the changed files reports the same diagnostics before and after (no new type errors in the test files). Full type-check and audits are left to CI.Checklist
test-auditauthoring gate)