Skip to content

fix(errors): a Router bucket outranks the status table on every status - #118

Merged
mattmillerai merged 2 commits into
mainfrom
matt/router-bucket-any-status
Sep 1, 2026
Merged

fix(errors): a Router bucket outranks the status table on every status#118
mattmillerai merged 2 commits into
mainfrom
matt/router-bucket-any-status

Conversation

@mattmillerai

Copy link
Copy Markdown
Contributor

Problem

A Router error response names its own bucket — the X-Comfy-Error-Type header, repeated as the body's top-level error_type — but the decoder only let that bucket through on 409 and on statuses the status table did not name. Everywhere else the table won and destroyed the bucket before anything downstream could read it:

  • a 403 not_enabled surfaced as Forbidden, so except NotEnabled — the one handler every pre-launch caller writes — never fired (reproduced live: a not-yet-enabled account got Forbidden from models.run)
  • a 422 invalid_input surfaced as InvalidWorkflow (a jobs-surface class), which is what every reachability probe tripped over
  • a 404 model_not_found surfaced as plain NotFound

Fix — two halves

comfy_low.errors: precedence is now envelope error.code → wire bucket when one is present → status table. Only responses that carry a bucket retype, and only Router sends one; the v2 envelope and bucket-less responses (including a bare 429QueueFull) decode exactly as before.

comfy_sdk.exceptions.to_sdk_error: a preserved router-only bucket now raises its typed RouterError subclass (NotEnabled, InvalidInput, ModelNotFound, ConcurrencyLimitExceeded, …) instead of a bare ComfyError. The three buckets both surfaces spell identically (unauthorized, forbidden, insufficient_credits) deliberately keep their v2 classes — the code string alone cannot say which surface answered, and those classes catch on both.

Retry pacing is keyed on status + Retry-After and is unchanged; the collect rule on 409 concurrency_limit_exceeded is unchanged.

Tests

  • Table-driven: the bucket survives on 403/404/409/422/429/500, with Retry-After intact
  • Router-only buckets raise their typed classes across 7 statuses; overlapping buckets keep their v2 classes
  • Bucket-less and v2-envelope decoding pinned unchanged
  • One prior test asserting the old precedence (429 + bucket → queue_full) is rewritten to the new invariant — that test encoded exactly the behavior this PR removes
  • Full suite: 718 passed locally (ruff, mypy, format, hygiene all green)

🤖 Generated with Claude Code

A Router error response names its own bucket (X-Comfy-Error-Type header,
repeated as the body's top-level error_type), but the decoder only let
that bucket through on 409 and on statuses the status table did not
name. Everywhere else the table won and destroyed the bucket: a 403
not_enabled surfaced as Forbidden (so 'except NotEnabled' never fired),
a 422 invalid_input as InvalidWorkflow, a 404 model_not_found as plain
NotFound.

Two halves:

- comfy_low.errors: the precedence is now envelope error.code, then the
  wire bucket when one is present, then the status table. Only responses
  that carry a bucket retype, which only Router sends; the v2 envelope
  and bucket-less responses decode exactly as before.

- comfy_sdk.exceptions.to_sdk_error: a preserved router-only bucket now
  raises its typed RouterError subclass (NotEnabled, InvalidInput,
  ModelNotFound, ...) instead of a bare ComfyError. The three buckets
  both surfaces spell identically (unauthorized, forbidden,
  insufficient_credits) deliberately keep their v2 classes -- the code
  string alone cannot say which surface answered, and those classes
  catch on both.

Retry pacing is keyed on status + Retry-After and is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mattmillerai
mattmillerai requested review from a team as code owners September 1, 2026 01:16
@mattmillerai mattmillerai added the full-autonomy Approved AI-brownfield: merges on machine gates alone, no human approver. Design doc + flag req'd. label Sep 1, 2026
'\ ' is an invalid escape sequence in a non-raw docstring, and the test
suite runs with warnings as errors, so the module failed to compile on a
cold cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@robinjhuang robinjhuang left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Auto-approved under the full-autonomy policy.

Gates verified at d5d47bc9b5471e22637ea3e3ac1c2c2fc70ec7aa:

  • full-autonomy label present
  • assigned to, or review requested from, @robinjhuang
  • not a draft
  • 8 required check(s) green — none failing, none pending

Issued by full-autonomy-approve.yml (run). This approval attests
that the machine gates above passed at this commit. It does not attest that a
human read the diff.

@mattmillerai
mattmillerai merged commit 94a4677 into main Sep 1, 2026
10 checks passed
@mattmillerai
mattmillerai deleted the matt/router-bucket-any-status branch September 1, 2026 01:24
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

full-autonomy Approved AI-brownfield: merges on machine gates alone, no human approver. Design doc + flag req'd.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants