Fix AI video generation hanging forever when the provider reports failure - #1809
Open
giladresisi wants to merge 3 commits into
Open
Fix AI video generation hanging forever when the provider reports failure#1809giladresisi wants to merge 3 commits into
giladresisi wants to merge 3 commits into
Conversation
…lure
A production user approved a veo3 generation in the agent chat and got a
spinner forever, a lost chat turn, and a burned video credit. kie.ai runs
content moderation asynchronously (generate returns 200 + taskId, the
rejection only shows up later via record-info), and the poll loop's only
exit was non-empty resultUrls: it never read successFlag and explicitly
whitelisted code 400, so a failed task looped forever. The never-settling
promise meant useCredit's catch-based refund never ran, the CopilotKit SSE
stream stayed open (eternal "..."), and Mastra never persisted the turn
(approval message gone on refresh).
Changes:
- veo3.ts: bounded polling matching the HeyGen provider (180 attempts x
10s ~= 30 min, then throw); keep polling only on successFlag 0; throw
with the provider's errorMessage on any other flag (2/3 = failed, and
unknown values fail fast rather than poll to the deadline); throw on
success reported without a URL; throw on top-level code !== 200 with the
provider msg (safety wording is then surfaced by generationError);
AbortSignal.timeout(30000) on both fetches so a stalled socket cannot
hang the loop. Existing refund in useCredit works untouched once the
callback rejects.
- generate.video.tool.ts: try/catch around generateVideo returning an
{ error } result (outputSchema fields now optional, same shape as
upload.from.url.tool.ts) instead of throwing, so the agent run completes,
the stream closes cleanly and the turn is persisted.
- ai.video.tsx: create-post modal no longer swallows errors - warning
toaster on non-2xx response, and on fetch rejection (network drop; the
backend may still finish, so the message says it may appear in the media
library).
Verified against a local mock kie.ai: baseline repro on the old code
(failed task -> 8+ polls/80s+ with the credit row leaked unrefunded, only
killing the process ends it) and post-fix (code:400, successFlag:2 and
deadline paths all throw on the first relevant poll with the credit row
deleted; chat replies with the failure and the turn survives refresh;
modal shows the toaster). Real happy-path generation verified end to end:
76s, video uploaded and saved to the media library, exactly one credit
consumed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…ovider tasks Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
What kind of change does this PR introduce?
Bug fix.
Why was this change needed?
A production user approved an AI video generation in the agent chat and got: "..." spinner forever, their approval message gone after refresh, and 1 of their 30 monthly video credits burned.
Root cause: kie.ai runs content moderation asynchronously —
/veo/generatereturns 200 + taskId and the rejection only appears later throughrecord-info. The veo3 poll loop's only exit condition was non-emptyresultUrls: it never inspectedsuccessFlag, explicitly whitelisted top-levelcode: 400, had no attempt cap and no socket timeout. A failed task therefore polled forever. The never-settling promise meantuseCredit's catch-based refund never ran (credit burned), the CopilotKit SSE stream never closed (eternal spinner), and Mastra never persisted the turn (that's the vanishing approval message).Fix, keeping all provider-specific logic inside the veo3 provider:
veo3.ts: bounded polling matching the HeyGen provider (180 × 10s ≈ 30 min, then throw). Keep polling only onsuccessFlag: 0; any other flag throws with the provider'serrorMessage(2/3 = failed; unknown values fail fast). Success reported without a URL throws. Top-levelcode !== 200throws with the providermsg(safety wording then surfaces viagenerationError).AbortSignal.timeout(30000)on both fetches so a stalled socket can't hang. Once the callback rejects, the existinguseCreditrefund works untouched.generate.video.tool.ts: try/catch returning an{ error }result (outputSchema fields made optional, same shape asupload.from.url.tool.ts) instead of throwing — the agent run completes, the stream closes cleanly, the turn is persisted.ai.video.tsx: the create-post modal no longer swallows errors — warning toaster on non-2xx, and on fetch rejection (network drop, where the backend may still finish and save to the media library).Verified with a local mock kie.ai: baseline repro on the old code (failed task → endless polling with the credit row leaked unrefunded) and post-fix (code:400 / successFlag:2 / deadline paths all throw promptly with the credit refunded; chat replies with the failure message and the turn survives refresh; modal shows the toaster). Real happy-path generation verified end to end: 76s, video uploaded and saved to the media library, exactly one credit consumed.
Other information:
Follow-up commit: log the kie.ai taskId when a generation starts. While debugging the production report we could not correlate our logs with kie.ai's task history because the taskId was never recorded; with it logged, a failed generation can be looked up directly via kie.ai's record-info endpoint or dashboard.
Deliberately out of scope: out-of-app notification for timed-out generations, eager persistence of the user chat message before tool execution, error boundary around the copilot stream handler.
Checklist:
🤖 Generated with Claude Code