Skip to content

refactor: share cloud HTTPError/URLError envelope handler across workflow + jobs (BE-3266)#524

Open
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-3266-cloud-http-error-envelope
Open

refactor: share cloud HTTPError/URLError envelope handler across workflow + jobs (BE-3266)#524
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-3266-cloud-http-error-envelope

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

When comfy workflow ... or comfy jobs cancel ... talks to Comfy Cloud and the request fails, the CLI turns the failure into a tidy, structured error with a code and a hint. Two files were doing that translation with their own near-identical copies of the same code — and the copies had quietly drifted apart. workflow knew that a 401/403 means "your login expired, run comfy cloud login", but jobs cancel didn't: it just said "generic cloud error." This PR pulls the shared translation into one helper both call, so they can't drift again, and jobs cancel now gives the same actionable "please re-login" message.

What changed

  • New comfy_cli/command/_cloud_errors.pyhandle_cloud_http_error(...), the single source of truth for mapping a cloud HTTPError/URLError/OSError to a structured envelope. Parameterized on the 404 code/message/hint, the id label (workflow_id vs prompt_id), and the operation string; everything else (body truncation, 401/403 → cloud_unauthorized, generic cloud_http_error, network hint) is shared.
  • workflow.py_handle_cloud_http_error becomes a thin wrapper supplying the workflow-specific 404 envelope. All five existing call sites are unchanged and keep their exact prior behavior.
  • jobs.py_cloud_cancel routes its except through the shared helper instead of its own hand-rolled copy.
  • Test — added a parametrized 401/403 regression test proving jobs cancel now surfaces cloud_unauthorized.

Behavior change (intentional, per the ticket)

jobs cloud-cancel previously mapped 401/403 → generic cloud_http_error; it now maps them to cloud_unauthorized with a comfy cloud login hint, matching workflow. This is the "small behavior change" the ticket calls out. Two additive, harmless detail changes fall out of unifying the generic-cloud_http_error shape: the generic error's details now also carries the resource id (workflow_id/prompt_id) and operation, and the jobs generic message is HTTP <code> during cancel (was HTTP <code> cancelling <id>).

Out of scope

models/search.py's three URLError/OSError branches differ deliberately (distinct error-code routing + hints, and a docstring from BE-2143 recording the decision not to unify them) — left untouched, per the ticket.

Judgment calls

  • The ticket pointed at tests/comfy_cli/command/test_jobs.py; the actual jobs test file is tests/comfy_cli/jobs/test_jobs.py — updated the real one.
  • Placed the helper in the command layer (comfy_cli/command/_cloud_errors.py) rather than comfy_cli/http.py since it depends on the renderer + typer.Exit (both command-layer concerns).

Negative-claim falsification

N/A — this change adds an actionable capability (the cloud_unauthorized re-login guidance for jobs cancel) rather than denying one; no dead-end/throw path or "not supported" string is introduced.

Testing

  • ruff format + ruff check clean on all touched files.
  • Full suite: 2575 passed, 37 skipped.

…flow + jobs (BE-3266)

Promote workflow.py's _handle_cloud_http_error to a shared
comfy_cli/command/_cloud_errors.helper, parameterized on the 404 error
code/message/hint, the id label, and the operation string. workflow.py's
public wrapper and jobs.py _cloud_cancel both route through it.

This closes a user-visible drift: jobs cloud-cancel previously mapped
401/403 to a generic cloud_http_error, so an expired session yielded no
actionable code. It now surfaces cloud_unauthorized with a
`comfy cloud login` hint, matching workflow. Adds a regression test.

models/search.py's three branches differ deliberately (per BE-2143) and
are left untouched.
@mattmillerai
mattmillerai marked this pull request as ready for review July 17, 2026 05:31
@mattmillerai mattmillerai added cursor-review Request Cursor bot review agent-coded PR authored by the agent-work loop labels Jul 17, 2026
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 29cb6d97-3690-4c5d-9bd5-acb082a9e89a

📥 Commits

Reviewing files that changed from the base of the PR and between a732f5c and 105a002.

📒 Files selected for processing (5)
  • comfy_cli/command/_cloud_errors.py
  • comfy_cli/command/jobs.py
  • comfy_cli/command/workflow.py
  • tests/comfy_cli/command/test_cloud_errors.py
  • tests/comfy_cli/jobs/test_jobs.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-3266-cloud-http-error-envelope
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-3266-cloud-http-error-envelope

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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 2 finding(s).

Severity Count
🟡 Medium 1
🟢 Low 1

Panel: 6/8 reviewers contributed findings.

Reviewers that did not contribute: kimi-k2.5:adversarial (empty), kimi-k2.5:edge-case (empty)

Comment thread comfy_cli/command/_cloud_errors.py Outdated
Comment thread comfy_cli/command/_cloud_errors.py
…3266)

Cursor panel findings on the shared envelope handler:

- `e.read()` was uncapped and eager. `base_url` is env-configurable, so a
  hostile/misbehaving endpoint could OOM the CLI, and a reset stream raised
  *before* any `renderer.error` fired — turning the structured envelope the
  helper exists to emit into an unhandled traceback. The read is now capped at
  1000 bytes (matching the prior `[:1000]` truncation exactly), best-effort
  (a failed read degrades to an empty body), and skipped entirely on the 404
  branch that discards it.

- 401/403 hardcoded the `comfy cloud login` hint and dropped the server body
  and resource id from `details` — a regression for jobs, which surfaced both
  via the generic branch before unification. A 403 is not unambiguously an auth
  failure (forbidden resource, quota, already-finished job), so it now keeps the
  server's explanation and gets a hint that doesn't assert re-login as the fix.
  401 is unchanged.

`details` is free-form in error.json, so the added keys are additive.
@mattmillerai

Copy link
Copy Markdown
Collaborator Author

CI status: the build/test failures are pre-existing and unrelated to this PR

Diagnosed while resolving review feedback — this PR did not cause them, and they block every open PR equally.

build — 9 test failures in tests/comfy_cli/registry/test_config_parser.py (8) and tests/comfy_cli/command/nodes/test_node_init.py (1), all ValueError: Comment cannot contain line breaks. This PR touches 4 files (_cloud_errors.py, jobs.py, workflow.py, test_jobs.py) and none of them are in that blast radius.

Root cause: tomlkit is unpinned (pyproject.toml:49), and config_parser.py:75 / :250 pass multi-line strings to .comment(). tomlkit 0.15 started rejecting comments containing line breaks — those calls were always outside its contract, 0.15 just enforces it. Verified both directions locally:

uv pip install tomlkit==0.15.1 && pytest tests/comfy_cli/registry/test_config_parser.py -q
# -> 8 failed, 73 passed   (reproduces CI exactly)

uv pip install tomlkit==0.13.3 && pytest tests/ -q --deselect tests/e2e
# -> 2587 passed, 18 skipped   (green, including this PR)

Worth flagging that this is not only a CI problem — it breaks comfy node init for any user who installs fresh and resolves tomlkit >= 0.15.

test (Windows) — failed in Install Dependencies, before any test ran: ImportError: cannot import name '__version__' from pydantic_core. Dependency-resolution infra, unrelated to this diff.

Fixing the tomlkit break inside this focused refactor would be scope creep, and it deserves its own review since it helps every PR at once — so I've filed it as a follow-up (pin tomlkit<0.15 to unblock, then rework the two multi-line .comment() calls and unpin). This PR needs that landed on main before its CI can go green.


Review feedback addressed (105a002)

Both Cursor panel findings were valid and are fixed, with regression tests in tests/comfy_cli/command/test_cloud_errors.py:

  • 🟡 Uncapped/eager e.read() — now capped at 1000 bytes (byte-identical to the prior [:1000] truncation), best-effort so a reset stream degrades to an empty body instead of escaping as a traceback in place of the envelope, and skipped on the 404 branch that discards it.
  • 🟢 401/403 losing body/resource_id + misleading hintcloud_unauthorized details now carry body, operation, and the resource id (fixing a regression this PR introduced for jobs). 401 keeps the re-login hint; 403 points at details.body since it is not unambiguously an auth failure.

Full suite green locally: 2587 passed, 18 skipped.

@mattmillerai

Copy link
Copy Markdown
Collaborator Author

🤖 The reviews loop filed Linear follow-up ticket(s) for review thread(s) deferred as out of scope for this PR:

@mattmillerai

Copy link
Copy Markdown
Collaborator Author

Both CI failures are now owned by dedicated PRs — nothing left to fix on this branch

Re-checked this PR end-to-end. No merge conflict (mergeable: MERGEABLE against main), both Cursor review threads resolved with real fixes in 105a002, and the two red checks are pre-existing repo-wide breaks that each already have a fix in flight:

Check Root cause Owned by
build tomlkit 0.15.1 (released 2026-07-17T01:48Z, ~4h before this run) rejects multi-line .comment(); tomlkit is unpinned in pyproject.toml:49 #533 (BE-3285)
test (Windows) uv hardlink mode cannot overwrite an open .pydImportError: cannot import name '__version__' from pydantic_core, fails in Install Dependencies before any test runs #529 (BE-3278)

Neither is caused by this diff, and I deliberately did not duplicate either fix here — folding an unrelated config_parser.py or test-windows.yml change into a cloud-error-envelope PR would be scope creep, and both are already correctly scoped elsewhere. This branch should go green on a rebase once #533 and #529 land.

Verified locally with the unrelated breaks isolated (tomlkit==0.13.3):

pytest tests/comfy_cli/command/test_cloud_errors.py \
       tests/comfy_cli/jobs/test_jobs.py \
       tests/comfy_cli/command/test_workflow_saved.py -q
# -> 111 passed

ruff format --diff .   # -> 237 files already formatted

Self-review of the diff turned up no regressions. All four workflow call sites (list/get/save/delete) route through the thin _handle_cloud_http_error adapter and jobs cancel calls the shared mapper directly; the 404 envelope stays caller-specific (workflow_not_found vs prompt_not_found) as intended, and since HTTPError subclasses URLError subclasses OSError, the except (HTTPError, URLError, OSError) tuples still dispatch correctly via the isinstance check inside the mapper. No new follow-ups filed — BE-3285 was already raised by an earlier pass on this PR.

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

Labels

agent-coded PR authored by the agent-work loop cursor-review Request Cursor bot review size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant