fix(errors): a Router bucket outranks the status table on every status - #118
Merged
Conversation
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>
'\ ' 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
approved these changes
Sep 1, 2026
robinjhuang
left a comment
Member
There was a problem hiding this comment.
Auto-approved under the full-autonomy policy.
Gates verified at d5d47bc9b5471e22637ea3e3ac1c2c2fc70ec7aa:
full-autonomylabel 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.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Problem
A Router error response names its own bucket — the
X-Comfy-Error-Typeheader, repeated as the body's top-levelerror_type— but the decoder only let that bucket through on409and on statuses the status table did not name. Everywhere else the table won and destroyed the bucket before anything downstream could read it:403not_enabledsurfaced asForbidden, soexcept NotEnabled— the one handler every pre-launch caller writes — never fired (reproduced live: a not-yet-enabled account gotForbiddenfrommodels.run)422invalid_inputsurfaced asInvalidWorkflow(a jobs-surface class), which is what every reachability probe tripped over404model_not_foundsurfaced as plainNotFoundFix — two halves
comfy_low.errors: precedence is now envelopeerror.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 bare429→QueueFull) decode exactly as before.comfy_sdk.exceptions.to_sdk_error: a preserved router-only bucket now raises its typedRouterErrorsubclass (NotEnabled,InvalidInput,ModelNotFound,ConcurrencyLimitExceeded, …) instead of a bareComfyError. 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-Afterand is unchanged; the collect rule on409concurrency_limit_exceededis unchanged.Tests
Retry-Afterintact429+ bucket →queue_full) is rewritten to the new invariant — that test encoded exactly the behavior this PR removes🤖 Generated with Claude Code