Skip to content

Return 400 for a bad upload and 500 for a server fault - #395

Open
vasilevklart wants to merge 3 commits into
mstar-project:mainfrom
vasilevklart:users/kirill/http-status
Open

vasilevklart wants to merge 3 commits into
mstar-project:mainfrom
vasilevklart:users/kirill/http-status

Conversation

@vasilevklart

@vasilevklart vasilevklart commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

The data worker picked 400 or 500 from the exception type at every stage. Most media loaders let a corrupt upload's decoder error through as a 500, a few turned a server fault into a 400, and a ValueError from output postprocessing (a server bug) was a 400. Part of #394, from the #275 review.

Each loader now wraps only its decode call in decoding_upload (media_limits.py): the decoder refusing the file is a 400, while a failed allocation, a removed upload and a torchcodec without FFmpeg stay 500s. Past intake every failure is a 500 with no exception text, so the image output knobs (FLUX.2, Z-Image) and fps (Wan2.2, Cosmos3) are checked at intake instead. Both frontends take the status from the data worker, so the Rust side is unchanged.

CPU, each model's loader through intake bad file: main → PR server fault: main → PR
image, Model.load_image (Qwen3.5, Qwen3-Omni, BAGEL) 400 → 400 allocation fails: 400 → 500
image, FLUX.2, LingBot-World, Waypoint 500 → 400 allocation fails: 500 → 500
audio, Model.load_audio (Qwen3-Omni), OmniVoice 500 → 400 no FFmpeg: 500 → 500
audio, Fish S2-Pro, Qwen3-TTS 500 → 400
audio, Chatterbox, CosyVoice3 400 → 400 no FFmpeg, allocation fails: 400 → 500
audio, load_speech_file (Whisper, Qwen3-ASR, Canary, Parakeet) 400 → 400 allocation fails: 400 → 500
video, Model.load_video, V-JEPA 2 500 → 400 no FFmpeg: 500 → 500
video, Qwen3.5 no FFmpeg: 400 → 500
output postprocess, any model ValueError: 400 → 500

Bad files: truncated PNG/JPEG, garbage WAV/M4A/MP4 (a torchcodec stand-in refuses them where FFmpeg decodes). No FFmpeg: torchcodec 0.10 on a host without it. Allocation: a real 2^62-byte one inside the decoder.

Tests: test_api_completion_guard.py runs 20 cases through the real loaders (11 bad files → 400, 9 server faults → 500); 12 fail on main. CPU Core fails the same tests as main, by name. ruff clean. Not run: a GPU server (the loaders run on the API server's CPU).

AI assistance (Claude) was used for this PR.

Output failures are always 500. An upload that won't decode is a 400, and
client knobs read only in postprocess (image format, fps) are checked at intake.
@NSagan271

Copy link
Copy Markdown
Collaborator

🤖 Automated live test, AI-generated (Claude Code). Not checked line by line by a human. Numbers come from one allocation, so treat small deltas as provisional.

Live test: api: 400 for bad uploads, 500 past intake

Works. A corrupt or truncated upload is now a 400 (base 500), for Qwen3.5 chat images and FLUX.2 edits. Bad FLUX.2 output knobs and a NaN Wan 2.2 fps are refused at intake in 0.01 s, before the GPU runs; on base they were refused only after generation. Valid PNG, JPEG and WebP still serve.

1x H200 · qwen3_5_0.8b, flux2_klein, wan22 (stock configs) · PR head b07040c2 vs base 3c5f0dda (merge-base)

Claims

Claim Result
An upload that won't decode is a 400, not a 500 ✅ Qwen3.5 chat, corrupt PNG: base 500 (RuntimeError: Internal error.), PR 400 (could not decode the image input: …). Truncated JPEG: base 500, PR 400. FLUX.2 /v1/images/edits with a corrupt upload: base 500 (UnidentifiedImageError: cannot identify image file '/tmp/mstar_uploads_naomi/<uuid>.png'), PR 400 naming "the upload" instead of the server path
FLUX.2 output_format, output_compression, png_compress_level checked at intake ✅ bmp, level 42, compression "high": PR 400 in 0.01 s from preprocessing, with messages naming the knob (png_compress_level must be -1 to 9, got 42). Base also answered 400, but from output postprocessing after the 4-step image was generated (0.21–0.22 s), and level 42 came back as Pillow's Invalid initialization option
Wan 2.2 fps NaN, inf, ≤ 0 checked at intake ✅ fps: "nan": PR 400 in 0.01 s at intake; base 400 after the video was generated (video output postprocessing failed: … cannot convert NaN to integer ratio). fps 0 and −8 were already intake 400s on base, and still are
Valid inputs unchanged ✅ PNG, JPEG, WebP uploads to Qwen3.5 and FLUX.2 generations in all three formats, a FLUX.2 edit and a Wan 2.2 generation: 200 on both arms
New tests fail on main, pass here ✅ The touched test files: 8 fail on base (test_an_upload_that_will_not_decode_is_a_400, the FLUX.2 knob, Wan nan/inf and postprocess-failure cases), and all 181 pass on the PR (2 skipped)

Other

  • Seen on both arms: every 400 above has "type": "server_error" in its error body.
  • Not tested: Z-Image and Cosmos3 fps served; audio and video uploads that won't decode; a decoder that fails to import (should stay 500).
  • No CPU-overhead row: the change runs once per request in the API server and model intake, not per step.

@Gaurav-Shah05 Gaurav-Shah05 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The output half needs no changes.

Chatterbox's _decode_audio has the same problem on main: a missing FFmpeg returns 400, though its comment says that stays a server error. The library check from the inline comment would fix it too, so I'd include it here.

I merged this PR onto main, served Qwen3.5-0.8B on one H100, and ran each model's loader on good and bad files. Since #633 Model.load_image returns 400 for a corrupt image, but the other image, video and audio loaders still return 500.

Comment thread mstar/api_server/data_worker.py Outdated
becomes a ValueError, so the client gets a 400 instead of a 500."""
try:
return load(filepath, device)
except _DECODE_ERRORS as exc:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This catch spans the whole loader, so a server failure inside it, like torchcodec failing to load FFmpeg or a failed allocation in Model.load_image, returns a 400 where main returns 500. I think each loader should raise the ValueError at its decode call, after a once-per-process check that its decoder library loads.

main moved the media load into _load_media and request intake into
_process_waiting_input, and added a stream-input intake path and
client_error_text. Intake failures (preprocessing and stream input) pass
intake=True, so a ValueError there stays a 400; a 500 past intake no
longer shows the exception text (client_error_text's refusal follows the
status). Tests that call _fail_request for preprocessing pass intake=True,
and an inline-token or pooled-encode postprocess failure is now a 500.
The data worker's catch spanned the whole loader, so a failed allocation or a
torchcodec without FFmpeg could become a 400 as well. Each loader now wraps
only its decode call in media_limits.decoding_upload: a file the decoder
refuses is an UndecodableMediaError (a 400), and a failed allocation, a
removed upload and a missing FFmpeg are re-raised (a 500).

FLUX.2, LingBot-World, Waypoint, the base audio and video loaders, OmniVoice,
Fish S2-Pro, Qwen3-TTS and V-JEPA 2 now answer a corrupt file with a 400
(500 on main); Chatterbox, CosyVoice3 and Qwen3.5 video without FFmpeg, and
the base image loader and load_speech_file on a failed allocation, with a 500
(400 on main). Cosmos3 still retries a refused file with PyAV.
@mstar-review mstar-review Bot added size/M and removed size/S labels Oct 31, 2026
@vasilevklart vasilevklart changed the title api: 400 for bad uploads, 500 past intake Return 400 for a bad upload and 500 for a server fault Oct 31, 2026
@Gaurav-Shah05

Gaurav-Shah05 commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

@vasilevklart does this need to stay open? or is this fixed by any upstream PR?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants