Skip to content

fix: retry transient HTTP statuses and show friendly download errors#432

Merged
bigcat88 merged 5 commits intomainfrom
fix/download-errors-and-progress
Apr 19, 2026
Merged

fix: retry transient HTTP statuses and show friendly download errors#432
bigcat88 merged 5 commits intomainfrom
fix/download-errors-and-progress

Conversation

@bigcat88
Copy link
Copy Markdown
Contributor

@bigcat88 bigcat88 commented Apr 15, 2026

Summary

Fixes #430.

Three focused changes that together address the bug report (visual glitches + verbose tracebacks when pasting many comfy model download commands into a shell):

  • Retry transient HTTP statuses. _download_file_httpx now raises a private _TransientHTTPStatusError for statuses in {408, 429, 500, 502, 503, 504}, which the existing retry loop catches alongside network-level errors. CivitAI CDN blips and other transient server errors can now self-heal instead of aborting the download on the first attempt.
  • Friendly error rendering. comfy model download catches DownloadException and prints a one-line red error before exiting with code 1, replacing the previous ~100-line Rich-panel Python traceback. comfy node install catches it the same way, using the existing ui.display_error_message path.
  • Transient progress bars. ui.show_progress and _poll_aria2_download now use transient=True, so each progress bar is erased when its download completes. Running many comfy model download commands in sequence no longer leaves 30 persistent bars stacked in the terminal.

Notes / follow-ups

  • Retry-After header on 429 responses is not honored; fixed 2 s / 4 s backoff is used. Worth a follow-up if we see CivitAI or similar providers sending it.
  • HuggingFace downloads (huggingface_hub.hf_hub_download) are not touched — they have their own error semantics.
  • A pre-existing raise DownloadException("Filename cannot be empty") at comfy_cli/command/models/models.py:315 still produces a traceback because it sits above the new try/except; unrelated to [Potential bug] Visual glitches and error #430 and can be cleaned up separately.

- HTTP 408/429/500/502/503/504 are now retried (up to 3 attempts with
  backoff) rather than failing immediately. Transient server-side errors
  can now self-heal instead of aborting the download.
- DownloadException raised from comfy model download is rendered as a
  one-line red error with exit code 1, instead of a full Rich-panel
  Python traceback. comfy node install uses the existing
  display_error_message path.
- Download progress bars now use transient=True so they disappear when
  the download completes. Running many downloads in sequence no longer
  leaves a stack of persistent bars.

Signed-off-by: Alexander Piskun <[email protected]>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

This pull request enhances download resilience and error handling across the CLI. It introduces transient HTTP status detection with automatic retries, improves error messages, ensures clean CLI exits on failures, and prevents console visual glitches through transient progress displays. Comprehensive tests validate the new error-handling paths.

Changes

Cohort / File(s) Summary
Command Error Handlers
comfy_cli/command/custom_nodes/command.py, comfy_cli/command/models/models.py
Added try/except blocks around download operations to catch DownloadException, display user-friendly error messages via UI, and exit cleanly with code 1 instead of propagating exceptions. The models command uses Rich markup-safe rendering via escape().
Download Retry & Status Handling
comfy_cli/file_utils.py
Introduced _TransientHTTPStatusError to detect retriable HTTP statuses (408, 429, 500–504), extended retry logic to handle these transient errors, added fast-fail cleanup for non-retriable errors, and improved error messaging. Progress bar now renders as transient to prevent visual glitches—you might say the download bar is now transiently better.
UI Error Rendering
comfy_cli/ui.py
Modified display_error_message to render messages without Rich markup parsing (markup=False), preventing bracketed substrings in error text from being misinterpreted. Progress display now uses transient=True for cleaner console output.
Command Error Handling Tests
tests/comfy_cli/command/models/test_models.py, tests/comfy_cli/command/nodes/test_node_install.py
Added comprehensive test suites validating that download exceptions exit with code 1, display error messages without tracebacks, and prevent downstream operations (extraction, script execution) after failure.
UI Markup Safety Tests
tests/comfy_cli/test_ui.py
New test module with helper function to capture console output and validate that error messages containing Rich-like bracket patterns (e.g., [/], [id]) are rendered literally without parsing or crashing.
Download Retry Behavior Tests
tests/test_file_utils_network.py
Extensive test coverage for transient HTTP status retries, including verification of sleep backoff, cause chaining, exhaustion messages, and that non-retriable statuses fail fast. Tests confirm retry success on subsequent attempts and preserve existing destination files on transient errors.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed PR addresses core objectives: retries for transient HTTP statuses [430], friendly error messages without tracebacks [430], transient progress bars [430], and improved error handling for downloads [430].
Out of Scope Changes check ✅ Passed All changes directly support issue #430: HTTP retry logic, error message handling, progress bar transience, and related test coverage remain in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/download-errors-and-progress
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/download-errors-and-progress

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@            Coverage Diff             @@
##             main     #432      +/-   ##
==========================================
+ Coverage   76.63%   77.15%   +0.52%     
==========================================
  Files          35       35              
  Lines        4279     4311      +32     
==========================================
+ Hits         3279     3326      +47     
+ Misses       1000      985      -15     
Files with missing lines Coverage Δ
comfy_cli/command/custom_nodes/command.py 84.09% <100.00%> (+2.47%) ⬆️
comfy_cli/command/models/models.py 72.80% <100.00%> (+0.58%) ⬆️
comfy_cli/file_utils.py 89.96% <100.00%> (+1.57%) ⬆️
comfy_cli/ui.py 66.66% <100.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Non-retriable httpx errors (UnsupportedProtocol, TooManyRedirects,
DecodingError, etc.) previously escaped download_file and surfaced as a
full Typer-rendered traceback. They are now caught and converted to
DownloadException so the existing catches in model download and
registry-install render them as a one-line friendly error.

Also adds tests for the new wrapping and for the registry-install
DownloadException catch path.

Signed-off-by: Alexander Piskun <[email protected]>
…ndering

DownloadException messages can contain server-controlled text (e.g. a
401 body's "message" field, echoed back via guess_status_code_reason).
If that text contained rich-markup metacharacters it would either
silently strip bracketed substrings (e.g. "[id]" in a URL) or, for a
stray "[/]" or unbalanced closing tag, raise rich.errors.MarkupError —
which escaped both catch sites and surfaced as the exact Typer
traceback panel #430 set out to suppress.

- ui.display_error_message: use markup=False and the style= kwarg so
  the message is never parsed as markup.
- models.download exception rendering: escape the DownloadException
  message before embedding it in the [bold red]...[/bold red] template.

New tests cover: plain messages, unbalanced / closing / style-like
bracketed tokens, and an end-to-end CLI invocation where download_file
raises a DownloadException carrying markup characters.

Signed-off-by: Alexander Piskun <[email protected]>
…_file

- _friendly_network_error now includes the stdlib http.HTTPStatus phrase
  alongside the code ("HTTP 500 Internal Server Error" instead of just
  "HTTP 500"), so the retry-exhaust DownloadException carries what the
  status means — not only its number. Unknown codes fall back cleanly.
- httpx.InvalidURL inherits directly from Exception, not httpx.HTTPError,
  so the previous except clause would have let it leak as a raw Typer
  traceback. Add it to the non-retriable catch tuple and to the friendly
  message branches, so a malformed URL surfaces as a one-line error.

Tests: phrase assertions (known + unknown code + unrelated-reason
preservation), InvalidURL wrap behaviour (single call, no retry,
__cause__ chain, pre-existing file preserved), and retry-exhaust
message now asserts the phrase.

Signed-off-by: Alexander Piskun <[email protected]>
Before this change, a failed custom-node download in registry-install
returned normally after displaying the error, giving exit code 0.
Automation that chains `comfy node registry-install X && ...` would
treat the failure as success.

Raise typer.Exit(code=1) so the failure is visible to CI / shell
pipelines, matching the pattern used in models.py download. Update the
regression tests to assert the non-zero exit code.

Signed-off-by: Alexander Piskun <[email protected]>
@bigcat88 bigcat88 marked this pull request as ready for review April 19, 2026 12:49
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Apr 19, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
comfy_cli/command/custom_nodes/command.py (1)

1144-1152: ⚠️ Potential issue | 🟠 Major

Return a non-zero exit for pre-download registry failures.

These paths print “Failed to download” but then return, so registry-install exits successfully even when the registry omitted a URL or the install API failed. That lets the exit-code goblin sneak past CI.

🐛 Proposed fix
         if not node_version.download_url:
             logging.error("Download URL not provided from the registry.")
             ui.display_error_message(f"Failed to download the custom node {node_id}.")
-            return
+            raise typer.Exit(code=1)
 
     except Exception as e:
         logging.error(f"Encountered an error while installing the node. error: {str(e)}")
         ui.display_error_message(f"Failed to download the custom node {node_id}.")
-        return
+        raise typer.Exit(code=1) from None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@comfy_cli/command/custom_nodes/command.py` around lines 1144 - 1152, The code
paths that detect missing node_version.download_url or catch Exception only log
and return, causing the CLI command (registry-install) to exit with a zero
status; change those early returns to exit with a non-zero code (e.g., call
sys.exit(1) or raise SystemExit(1)) so failures propagate to the process exit
status. Update the branches that log via logging.error(...) /
ui.display_error_message(...) (the block checking node_version.download_url and
the except Exception as e handler) to call sys.exit(1) after logging instead of
plain return; ensure sys is imported if needed and keep the same error messages
and node_id variable in the display text.
comfy_cli/command/models/models.py (1)

315-316: ⚠️ Potential issue | 🟡 Minor

Route empty filename errors through the friendly CLI path.

This DownloadException is raised before the new handler, so comfy model download --filename "" ... can still show the raw exception/traceback instead of a clean red error.

🐛 Proposed fix
     if local_filename is None:
         raise typer.Exit(code=1)
     if local_filename == "":
-        raise DownloadException("Filename cannot be empty")
+        ui.display_error_message("Filename cannot be empty")
+        raise typer.Exit(code=1) from None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@comfy_cli/command/models/models.py` around lines 315 - 316, The
empty-filename check in models.py currently raises DownloadException (if
local_filename == "") which bypasses the new friendly CLI handler; change this
to raise the CLI-friendly error type used by the new handler (e.g.,
FriendlyCLIError or the project's CLIError/ClickException wrapper) with the same
message so the new handler will format it nicely (replace the DownloadException
raise at the local_filename check with a raise of the friendly CLI error class).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@comfy_cli/command/custom_nodes/command.py`:
- Around line 1144-1152: The code paths that detect missing
node_version.download_url or catch Exception only log and return, causing the
CLI command (registry-install) to exit with a zero status; change those early
returns to exit with a non-zero code (e.g., call sys.exit(1) or raise
SystemExit(1)) so failures propagate to the process exit status. Update the
branches that log via logging.error(...) / ui.display_error_message(...) (the
block checking node_version.download_url and the except Exception as e handler)
to call sys.exit(1) after logging instead of plain return; ensure sys is
imported if needed and keep the same error messages and node_id variable in the
display text.

In `@comfy_cli/command/models/models.py`:
- Around line 315-316: The empty-filename check in models.py currently raises
DownloadException (if local_filename == "") which bypasses the new friendly CLI
handler; change this to raise the CLI-friendly error type used by the new
handler (e.g., FriendlyCLIError or the project's CLIError/ClickException
wrapper) with the same message so the new handler will format it nicely (replace
the DownloadException raise at the local_filename check with a raise of the
friendly CLI error class).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 60bf0838-6106-489d-84f3-b542f8a6dd08

📥 Commits

Reviewing files that changed from the base of the PR and between d838ec7 and a8a3da2.

📒 Files selected for processing (8)
  • comfy_cli/command/custom_nodes/command.py
  • comfy_cli/command/models/models.py
  • comfy_cli/file_utils.py
  • comfy_cli/ui.py
  • tests/comfy_cli/command/models/test_models.py
  • tests/comfy_cli/command/nodes/test_node_install.py
  • tests/comfy_cli/test_ui.py
  • tests/test_file_utils_network.py

@bigcat88 bigcat88 merged commit 5f8937f into main Apr 19, 2026
15 checks passed
@bigcat88 bigcat88 deleted the fix/download-errors-and-progress branch April 19, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Potential bug] Visual glitches and error

1 participant