Skip to content

fix(ai-fal): handle errors from fal result fetch on completed jobs#396

Merged
AlemTuzlak merged 4 commits intoTanStack:mainfrom
tombeckenham:394-fal-video-adapter-failed-queue-status-mapped-to-processing-causes-infinite-polling
Mar 30, 2026
Merged

fix(ai-fal): handle errors from fal result fetch on completed jobs#396
AlemTuzlak merged 4 commits intoTanStack:mainfrom
tombeckenham:394-fal-video-adapter-failed-queue-status-mapped-to-processing-causes-infinite-polling

Conversation

@tombeckenham
Copy link
Copy Markdown
Contributor

@tombeckenham tombeckenham commented Mar 23, 2026

Summary

  • fal.ai never returns a FAILED queue status — invalid jobs go IN_PROGRESSCOMPLETED, and the real error (e.g. 422 validation) only surfaces when calling fal.queue.result()
  • getVideoUrl() now catches result fetch errors and extracts detailed validation messages from error.body.detail
  • Removed fictional FAILED queue status handling (fal doesn't use it)
  • getVideoJobStatus() now returns status: 'failed' when the result fetch throws on a "completed" job
  • Unknown queue statuses default to 'processing' (safe for polling) instead of 'failed'

Closes #394

Test plan

  • Verified behavior against live fal.ai API with a prompt exceeding 2500 char limit
  • Added test for 422 validation error extraction in getVideoUrl()
  • Updated unknown status test to expect 'processing'
  • All 25 tests pass (15 video + 10 image)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Completed-but-broken video jobs are now reported as failed when result fetching fails.
    • Detailed validation/error messages from fal.ai responses are extracted and surfaced to users.
  • Chores
    • Updated fal.ai client to the latest patch release and added a release changeset.
  • Tests
    • Added tests for status mapping and detailed error message handling.

Map fal.ai's FAILED queue status to 'failed' instead of falling through
to the default case which incorrectly returned 'processing'. Unknown
statuses now also map to 'failed' as a safety net. Error details from
fal responses are surfaced in VideoStatusResult.error.

Closes TanStack#394

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tombeckenham tombeckenham requested a review from a team March 23, 2026 09:54
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f31ae065-cb45-49ad-a183-18e627279da5

📥 Commits

Reviewing files that changed from the base of the PR and between 20ff087 and e5e063e.

📒 Files selected for processing (1)
  • packages/typescript/ai-fal/package.json
✅ Files skipped from review due to trivial changes (1)
  • packages/typescript/ai-fal/package.json

📝 Walkthrough

Walkthrough

Updates fal.ai video adapter and generateVideo activity to treat result-fetch failures for completed jobs as failed, surface validation/error details from fal result responses, bump @fal-ai/client, add tests, and include a patch changeset.

Changes

Cohort / File(s) Summary
Changeset & Package
\.changeset/fix-fal-failed-status.md, packages/typescript/ai-fal/package.json
Adds a patch changeset and updates @fal-ai/client from ^1.9.1 to ^1.9.4.
Adapter: types & status mapping
packages/typescript/ai-fal/src/adapters/video.ts
Enhances status mapping/docs to account for FAILED/unknown statuses and wraps queue.result(...) with try/catch to parse and rethrow structured fal error details.
Activity logic
packages/typescript/ai/src/activities/generateVideo/index.ts
When URL fetch after a COMPLETED provider report throws, emit video:request:completed with status: 'failed' and return status: 'failed' with normalized error message.
Tests
packages/typescript/ai-fal/tests/video-adapter.test.ts
Adds tests for unknown/FAILED status mapping and for parsing/throwing detailed validation errors from fal result fetches.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Adapter
  participant FalClient
  participant Activity

  Client->>Adapter: poll getVideoStatus(jobId)
  Adapter->>FalClient: queue.status(jobId)
  FalClient-->>Adapter: { status: "COMPLETED" | "FAILED" | ... , ... }
  alt status == COMPLETED
    Adapter->>FalClient: queue.result(jobId)
    FalClient--xAdapter: throws validation/error (body.detail)
    Adapter->>Activity: emit video:request:completed (status: 'failed', error)
    Adapter-->>Client: { status: 'failed', error, progress }
  else status == FAILED
    Adapter->>Activity: emit video:request:completed (status: 'failed', error)
    Adapter-->>Client: { status: 'failed', error }
  else other/unknown
    Adapter-->>Client: { status: 'failed' }
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through queues where statuses hid,
Found FAILED masked as processing — oh kid!
I parsed the errors, fixed the flow,
Now failures return and polling will go.
Tiny hop, big fix — a happy show.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(ai-fal): handle errors from fal result fetch on completed jobs' accurately describes the main change: improved error handling for fal.ai result fetching when jobs are completed.
Description check ✅ Passed The PR description provides clear context about the fix, root cause analysis, and test verification. The required checklist items are addressed, and a changeset was generated for the patch release.
Linked Issues check ✅ Passed All coding requirements from issue #394 are met: error handling in getVideoUrl(), failed status mapping in getVideoJobStatus(), safe default for unknown statuses, and comprehensive test coverage including validation error extraction.
Out of Scope Changes check ✅ Passed All changes directly address the objectives in issue #394: error handling, status mapping, validation message extraction, and test coverage. No unrelated or out-of-scope modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Mar 23, 2026

View your CI Pipeline Execution ↗ for commit e5e063e

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 3m 59s View ↗
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 1m 27s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-30 13:39:02 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 23, 2026

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@396

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@396

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@396

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@396

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@396

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@396

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@396

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@396

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@396

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@396

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@396

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@396

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@396

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@396

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@396

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@396

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@396

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@396

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@396

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@396

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@396

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@396

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@396

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@396

commit: e5e063e

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/typescript/ai-fal/tests/video-adapter.test.ts (1)

217-227: Good safety net test for unknown statuses.

This test ensures that any unrecognized status from fal.ai will result in 'failed' rather than continuing to poll indefinitely.

Consider adding an edge case test for FAILED without an error message to ensure graceful handling:

📝 Optional additional test case
it('returns failed status without error when error field is absent', async () => {
  mockQueueStatus.mockResolvedValueOnce({
    status: 'FAILED',
  })

  const adapter = createAdapter()

  const result = await adapter.getVideoStatus('job-failed-no-error')

  expect(result.status).toBe('failed')
  expect(result.error).toBeUndefined()
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai-fal/tests/video-adapter.test.ts` around lines 217 -
227, Add a test that ensures the adapter maps a fal.ai response with status
'FAILED' but no error field to { status: 'failed' } and does not set an error
message: create a new it block similar to the existing unknown status test, use
mockQueueStatus.mockResolvedValueOnce({ status: 'FAILED' }), call
createAdapter() and await adapter.getVideoStatus('job-failed-no-error'), then
assert result.status === 'failed' and that result.error is undefined; reference
mockQueueStatus, createAdapter, and getVideoStatus to locate where to add the
test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/typescript/ai-fal/src/adapters/video.ts`:
- Around line 19-26: Update the FalQueueStatus type to only include 'IN_QUEUE' |
'IN_PROGRESS' | 'COMPLETED' (remove 'FAILED') and ensure any failure detection
logic uses FalStatusResponse.error (i.e., check for status === 'COMPLETED' &&
error) rather than comparing to a non-existent 'FAILED' status; update the
FalStatusResponse type usage sites accordingly so callers handle
completed-with-error cases.

---

Nitpick comments:
In `@packages/typescript/ai-fal/tests/video-adapter.test.ts`:
- Around line 217-227: Add a test that ensures the adapter maps a fal.ai
response with status 'FAILED' but no error field to { status: 'failed' } and
does not set an error message: create a new it block similar to the existing
unknown status test, use mockQueueStatus.mockResolvedValueOnce({ status:
'FAILED' }), call createAdapter() and await
adapter.getVideoStatus('job-failed-no-error'), then assert result.status ===
'failed' and that result.error is undefined; reference mockQueueStatus,
createAdapter, and getVideoStatus to locate where to add the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61ab5164-14e9-44be-8b98-008f8aa8a168

📥 Commits

Reviewing files that changed from the base of the PR and between c3583e3 and 9257fc4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • .changeset/fix-fal-failed-status.md
  • packages/typescript/ai-fal/package.json
  • packages/typescript/ai-fal/src/adapters/video.ts
  • packages/typescript/ai-fal/tests/video-adapter.test.ts

fal.ai never returns a FAILED queue status — invalid jobs go through
IN_PROGRESS → COMPLETED, and the real error (e.g. 422 validation)
only surfaces when calling fal.queue.result(). This extracts detailed
error info from the result fetch, removes the fictional FAILED status
handling, and returns failed status from getVideoJobStatus when the
result fetch throws.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tombeckenham tombeckenham changed the title fix(ai-fal): handle FAILED queue status to prevent infinite polling fix(ai-fal): handle errors from fal result fetch on completed jobs Mar 23, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@AlemTuzlak AlemTuzlak merged commit 26d8243 into TanStack:main Mar 30, 2026
7 checks passed
@github-actions github-actions bot mentioned this pull request Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fal video adapter: FAILED queue status mapped to 'processing', causes infinite polling

2 participants