mirror of
https://github.com/Comfy-Org/comfy-python-sdk.git
synced 2026-11-04 23:37:47 +00:00
[PR #208] fix(assets): read a bodiless HEAD 429 on the by-hash probe as rate_limited, not queue_full #208
Labels
No labels
audit:ai-readiness
audit:community
audit:github
audit:health-scoring
audit:policy
audit:release
audit:supply-chain
effort:medium
effort:small
effort:small
effort:trivial
priority:nice-to-have
priority:nice-to-have
priority:recommended
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-python-sdk#208
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-python-sdk/pull/208
Author: @mattmillerai
Created: 10/9/2026
Status: 🔄 Open
Base:
main← Head:matt/be-19949-head-429-rate-limited📝 Commits (2)
e659071fix(assets): read a bodiless HEAD 429 on the by-hash probe as rate_limited, not queue_fulla1bc9a5fix(assets): keep a named bucket on a bodiless HEAD 429 and pin the SDK class📊 Changes
4 files changed (+187 additions, -3 deletions)
View changed files
📝
CHANGELOG.md(+9 -0)📝
src/comfy_low/transport.py(+34 -3)📝
tests/conftest.py(+10 -0)➕
tests/test_head_refusal.py(+134 -0)📄 Description
ELI-5
Before uploading a file, the SDK asks the server "do you already have these bytes?" with a
HEADrequest. When the gateway refuses that request because the account is rate-limited, it sends a429with no body (aHEADresponse can't have one). With no body to read, the SDK guessed from the status code alone and reported a full job queue (QueueFull). That's the wrong error, and it sends callers off to debug a queue that has nothing to do with the problem. Now a bodiless429on that probe is reported asrate_limited, and theRetry-Afterwait is still included.What changed
comfy_low/transport.py: new_Prepared.raise_for_head_refusal(resp). On a429it builds the same errorerror_from_envelopewould build for{"error": {"code": "rate_limited", "message": "Rate limited"}}, withretry_afterfrom the existing_retry_afterheader parser andrequest_idfrom the header. A429that names its own bucket onX-Comfy-Error-Type(for exampleconcurrency_limit_exceeded) takes the existing generic path instead, so it keeps that bucket. Every other status also goes through the generic path, so a bodiless403is stillForbidden.ComfyLow.head_asset_by_hashandAsyncComfyLow.head_asset_by_hashcall it after their 200/404 checks, and the docstring now says the code is synthesized from the status.tests/conftest.py: newServerState.head_refusal: (status, headers) | Nonesetting. When it is set, the stub answersHEAD /assets/by-hashwith that status and those headers and an empty body.tests/test_head_refusal.py: tests for the low layer, sync and async:429withRetry-After: 7givescode == "rate_limited",http_status == 429,retry_after == 7, and the error is notQueueFull429without the header givesretry_after is None429withX-Comfy-Error-Type: concurrency_limit_exceededkeeps that code anderror_type403givesForbiddencommit()(sync and async) against the429stub raises exactlyRateLimited(code == "rate_limited",retry_after == 7), notQueueFull, and uploads nothing.commit()against the bucket-header429raisesConcurrencyLimitExceeded, and against the403stub raisesForbidden.CHANGELOG.md: added an entry under Unreleased → Fixed.Judgment calls
rate_limitedmapping.comfy_sdk.exceptions._class_foralready maps the coderate_limitedthrough the Router bucket table, socommit()raisescomfy_sdk.router_exceptions.RateLimited. That class is aComfyErrorsubclass withcode == "rate_limited"and carriesretry_after. A bodiedrate_limitedon any v2 route already surfaces this way, so this change matches that behavior instead of adding a class. No new exception class is exported andexportedErrorClassesis unchanged.403keeps the generic messageHTTP 403. Changing it toForbiddenwas optional, and I left it alone so this PR only changes the429case._CODE_BY_STATUSand the client's 429 retry helper (_retry_delay) are unchanged, so the bodilessqueue_fullfallback onPOST /jobsstill works.Provenance
ruff check,ruff format --checkandmypy srcall clean429branch disabled, the rate-limit tests fail; with the bucket-header guard removed, the 3 bucket tests failbuild-check, codegen drift (no generated or spec file touched)429that names a bucket is no longer relabeledrate_limitedResidual
RouterErrorsubclass. Arate_limitedfrom the v2 gateway now arrives ascomfy_sdk.router_exceptions.RateLimited, soexcept RouterErroralso catches a non-Router refusal. This is the existing translation for every bodiedrate_limited, not something this PR introduces. Whether v2-surfacerate_limitedshould become a plainComfyErrorbelongs with the separate audit of the status/code fallback table.HEAD429/403 was reproduced against the in-repo stub server only. A real gateway refusal was not triggered, because that means deliberately exhausting an account's rate limit.Retry-Afterin HTTP-date form is still read asNone. That comes from the shared_retry_afterparser, which this PR reuses and does not change.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.