Skip to content

fix: prevent infinite Manager loading offline and surface backend security messages#12752

Open
Kosinkadink wants to merge 2 commits into
mainfrom
fix/manager-offline-loading-and-security-msg
Open

fix: prevent infinite Manager loading offline and surface backend security messages#12752
Kosinkadink wants to merge 2 commits into
mainfrom
fix/manager-offline-loading-and-security-msg

Conversation

@Kosinkadink

Copy link
Copy Markdown
Member

Summary

Frontend half of the ComfyUI-Manager Desktop fixes for Comfy-Org/Comfy-Desktop#1037. Companion to Comfy-Org/Comfy-Desktop#1041.

Fixes two Manager UX problems:

  1. Infinite loading loop when offline. Opening the Manager with no internet left the spinner running forever. The registry search set isLoading = true then awaited the request with no try/finally, so any failure (offline, hung socket) never cleared the loading state.
  2. Hidden/vague security errors. When an install was blocked (e.g. --listen is on and security_level is too strict), the UI showed a generic 403 message and discarded ComfyUI-Manager's actionable backend text describing exactly what is required.

Changes

  • Wrap the registry search in try/catch/finally so a failed/offline search always clears the spinner; expose error and retry.
  • Add request timeouts to the registry and manager axios clients so hung sockets reject instead of hanging.
  • Prefer ComfyUI-Manager's backend message (security-aware, actionable) over the generic per-status fallback.
  • Show a retryable connection-error empty state in the Manager dialog instead of an endless spinner.

Testing

  • pnpm test:unit for the new useRegistrySearch and comfyManagerService specs - pass.
  • pnpm typecheck / pnpm lint / pnpm format pass (run via pre-commit).

Part of Comfy-Org/Comfy-Desktop#1037

…urity messages

- Wrap registry search in try/catch/finally so a failed/offline search clears the loading spinner instead of hanging forever, and expose error + retry
- Add request timeouts to the registry and manager axios clients so hung sockets reject
- Surface ComfyUI-Manager's actionable backend error message (e.g. required security_level/--listen) instead of a generic 403 fallback
- Show a retryable connection-error empty state in the manager dialog

Amp-Thread-ID: https://ampcode.com/threads/T-019eafaf-ac38-734b-8fa1-1422ed378e78
Co-authored-by: Amp <amp@ampcode.com>
@Kosinkadink Kosinkadink requested a review from a team June 10, 2026 05:14
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 10, 2026
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Tracks registry search failures, exposes a retry action, surfaces errors in the Manager UI, adds 10s request timeouts to registry/manager clients, prefers backend-provided error messages, and exposes task-failure helpers for UI error rendering.

Changes

Search Error Recovery and API Reliability

Layer / File(s) Summary
Search composable error tracking and retry
src/workbench/extensions/manager/composables/useRegistrySearch.ts
The composable adds an error ref, wraps gateway calls with try/catch/finally to set error and clear isLoading, and exposes retry(); error and retry are returned.
Search composable retry behavior tests
src/workbench/extensions/manager/composables/useRegistrySearch.test.ts
Vitest tests mock useRegistrySearchGateway, use fake timers, and assert retry() sets/clears error and clears isLoading on failures/successes.
Manager UI retry integration
src/workbench/extensions/manager/components/manager/ManagerDialog.vue
ManagerDialog destructures error/retry from the composable, passes retry to NoResultsPlaceholder, introduces hasConnectionError combining search and manager errors, and stops showing a spinner when searchError exists.
API request timeouts
src/services/comfyRegistryService.ts, src/workbench/extensions/manager/services/comfyManagerService.ts
Adds a 10s REQUEST_TIMEOUT_MS and wires it into axios clients to avoid hung requests.
Manager service backend error extraction
src/workbench/extensions/manager/services/comfyManagerService.ts
Error mapping now extracts response.data.message from axios errors and prefers it for user messages, with route-specific and 404 connectivity fallbacks.
Manager service error message tests
src/workbench/extensions/manager/services/comfyManagerService.test.ts
Tests ensure 403 errors with backend messages surface exactly and 403 without body fall back to a generic security-related message.
Task failure helpers in store
src/workbench/extensions/manager/stores/comfyManagerStore.ts, src/workbench/extensions/manager/stores/comfyManagerStore.test.ts
Adds isTaskFailed(taskId) and getTaskErrorMessages(taskId) and tests verifying failed vs successful task behavior.
Toast rendering and helpers
src/workbench/extensions/manager/components/ManagerProgressToast.vue
Switches progress checks to task-ID-based helpers, uses cn for conditional styling, and renders per-task error messages in toast logs.
Retry button localization
src/locales/en/main.json
Adds manager.retry = "Try Again".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

size:M, released:cloud

Suggested reviewers

  • jtydhr88
  • dante01yoon
  • christian-byrne

Poem

🐰
A failed search once spun and sighed,
Now "Try Again" brings hope inside.
Timeouts stop the endless wait,
Backend words now name our fate.
Retry and hop — we fix and glide.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore (reviewers only)

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
End-To-End Regression Coverage For Fixes ❌ Error PR title uses bug-fix language ("fix:"), changes src/ application code without updating browser_tests/, and lacks explanation of why E2E regression tests weren't added. Add or update a Playwright regression test under browser_tests/ for the Manager offline/retry behavior, or add a concrete explanation in the PR description of why E2E testing is not practical for these changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: preventing infinite loading when offline and surfacing backend security error messages.
Description check ✅ Passed The description comprehensively covers summary, changes, and testing information, but partially deviates from template by not including explicit 'What', 'Breaking', 'Dependencies' subsections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Adr Compliance For Entity/Litegraph Changes ✅ Passed PR modifies only manager extension, registry service, and locale files—no changes to src/lib/litegraph/, src/ecs/, or graph entity-related files. ADR compliance check not applicable.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/manager-offline-loading-and-security-msg

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.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 06/10/2026, 05:33:11 AM UTC

Links

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

🎭 Playwright: ✅ 1662 passed, 0 failed · 3 flaky

📊 Browser Reports
  • chromium: View Report (✅ 1643 / ❌ 0 / ⚠️ 2 / ⏭️ 5)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 16 / ❌ 0 / ⚠️ 1 / ⏭️ 0)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/workbench/extensions/manager/components/manager/ManagerDialog.vue`:
- Around line 487-493: The isLoading computed currently short-circuits on
searchError before checking isTabLoading, causing a stale search error to hide
the tab loading skeleton; update the isLoading logic in the isLoading computed
to check isTabLoading (and return true) before short-circuiting on searchError
so tab loading takes precedence, while preserving the existing checks for
isSearchLoading (with searchResults.length === 0) and isInitialLoad.

In `@src/workbench/extensions/manager/services/comfyManagerService.ts`:
- Around line 82-94: The current error-handling branch in comfyManagerService
that builds the user message (where axiosError, backendMessage,
routeSpecificErrors, status and context are used) doesn't treat axios timeouts
as cancellations; add an explicit branch before the generic fallback to detect
axios timeout errors (e.g., axiosError.code === 'ECONNABORTED' or
axiosError.message contains 'timeout') and set message to a clear user-facing
i18n string (use the existing i18n/t helper used elsewhere, e.g.,
t('comfyManager.timeout') or similar) so timeouts produce a specific translated
message instead of the generic `${context} failed with status ${status}`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: febe19dd-f55c-48e9-b336-95905f4b313d

📥 Commits

Reviewing files that changed from the base of the PR and between 6f6141a and d349677.

📒 Files selected for processing (7)
  • src/locales/en/main.json
  • src/services/comfyRegistryService.ts
  • src/workbench/extensions/manager/components/manager/ManagerDialog.vue
  • src/workbench/extensions/manager/composables/useRegistrySearch.test.ts
  • src/workbench/extensions/manager/composables/useRegistrySearch.ts
  • src/workbench/extensions/manager/services/comfyManagerService.test.ts
  • src/workbench/extensions/manager/services/comfyManagerService.ts

Comment on lines 487 to 493
const isLoading = computed(() => {
// A failed search must not read as "still loading" -- otherwise the spinner
// runs forever (e.g. offline) instead of showing the error placeholder.
if (searchError.value) return false
if (isSearchLoading.value) return searchResults.value.length === 0
if (isTabLoading.value) return true
return isInitialLoad.value

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prioritize tab loading before search-error short-circuiting.

On Line 490, searchError returns false before checking isTabLoading, so a stale search failure can suppress the loading skeleton while tab data is still loading.

Suggested fix
 const isLoading = computed(() => {
-  // A failed search must not read as "still loading" -- otherwise the spinner
-  // runs forever (e.g. offline) instead of showing the error placeholder.
-  if (searchError.value) return false
-  if (isSearchLoading.value) return searchResults.value.length === 0
   if (isTabLoading.value) return true
+  // A failed search must not read as "still loading" -- otherwise the spinner
+  // runs forever (e.g. offline) instead of showing the error placeholder.
+  if (searchError.value) return false
+  if (isSearchLoading.value) return searchResults.value.length === 0
   return isInitialLoad.value
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isLoading = computed(() => {
// A failed search must not read as "still loading" -- otherwise the spinner
// runs forever (e.g. offline) instead of showing the error placeholder.
if (searchError.value) return false
if (isSearchLoading.value) return searchResults.value.length === 0
if (isTabLoading.value) return true
return isInitialLoad.value
const isLoading = computed(() => {
if (isTabLoading.value) return true
// A failed search must not read as "still loading" -- otherwise the spinner
// runs forever (e.g. offline) instead of showing the error placeholder.
if (searchError.value) return false
if (isSearchLoading.value) return searchResults.value.length === 0
return isInitialLoad.value
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workbench/extensions/manager/components/manager/ManagerDialog.vue` around
lines 487 - 493, The isLoading computed currently short-circuits on searchError
before checking isTabLoading, causing a stale search error to hide the tab
loading skeleton; update the isLoading logic in the isLoading computed to check
isTabLoading (and return true) before short-circuiting on searchError so tab
loading takes precedence, while preserving the existing checks for
isSearchLoading (with searchResults.length === 0) and isInitialLoad.

Comment on lines +82 to 94
const backendMessage = axiosError.response?.data?.message
// Prefer the backend's message: ComfyUI-Manager returns actionable,
// security-aware text (e.g. which security_level/--listen is required)
// that is far more useful than our generic per-status fallbacks.
if (backendMessage) {
message = backendMessage
} else if (status && routeSpecificErrors?.[status]) {
message = routeSpecificErrors[status]
} else if (status === 404) {
message = 'Could not connect to ComfyUI-Manager'
} else {
message =
axiosError.response?.data?.message ??
`${context} failed with status ${status}`
message = `${context} failed with status ${status}`
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify that isAbortError handles axios timeout errors (ECONNABORTED)

# Check the implementation of isAbortError
ast-grep --pattern $'export $_isAbortError($_) {
  $$$
}'

# Also search for ECONNABORTED or timeout handling
rg -n -C3 'ECONNABORTED|timeout.*error|isAbortError' --type=ts -g '!*.test.ts'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 9114


Handle axios timeout errors (ECONNABORTED) explicitly in comfyManagerService

isAbortError only matches DOMException AbortError, so axios timeouts (ECONNABORTED) won’t be treated as cancellations and will fall into the generic fallback (${context} failed with status ${status}), which can produce an unhelpful message (e.g., status being undefined). Add an ECONNABORTED/timeout branch in the error handler with a clear, user-facing (vue-i18n) message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workbench/extensions/manager/services/comfyManagerService.ts` around
lines 82 - 94, The current error-handling branch in comfyManagerService that
builds the user message (where axiosError, backendMessage, routeSpecificErrors,
status and context are used) doesn't treat axios timeouts as cancellations; add
an explicit branch before the generic fallback to detect axios timeout errors
(e.g., axiosError.code === 'ECONNABORTED' or axiosError.message contains
'timeout') and set message to a clear user-facing i18n string (use the existing
i18n/t helper used elsewhere, e.g., t('comfyManager.timeout') or similar) so
timeouts produce a specific translated message instead of the generic
`${context} failed with status ${status}`.

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.02041% with 24 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...nsions/manager/components/ManagerProgressToast.vue 0.00% 10 Missing ⚠️
...sions/manager/components/manager/ManagerDialog.vue 0.00% 7 Missing ⚠️
...xtensions/manager/composables/useRegistrySearch.ts 68.42% 6 Missing ⚠️
...extensions/manager/services/comfyManagerService.ts 83.33% 1 Missing ⚠️
@@             Coverage Diff             @@
##             main   #12752       +/-   ##
===========================================
- Coverage   75.39%   61.61%   -13.78%     
===========================================
  Files        1560     1449      -111     
  Lines       89109    74916    -14193     
  Branches    24759    21129     -3630     
===========================================
- Hits        67180    46163    -21017     
- Misses      21256    28401     +7145     
+ Partials      673      352      -321     
Flag Coverage Δ
e2e ?
unit 61.61% <51.02%> (+0.14%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/services/comfyRegistryService.ts 24.80% <100.00%> (-21.98%) ⬇️
...nch/extensions/manager/stores/comfyManagerStore.ts 66.66% <100.00%> (+11.51%) ⬆️
...extensions/manager/services/comfyManagerService.ts 49.61% <83.33%> (+13.95%) ⬆️
...xtensions/manager/composables/useRegistrySearch.ts 83.63% <68.42%> (+11.93%) ⬆️
...sions/manager/components/manager/ManagerDialog.vue 2.71% <0.00%> (-62.90%) ⬇️
...nsions/manager/components/ManagerProgressToast.vue 0.00% <0.00%> (-28.58%) ⬇️

... and 1119 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

When an install is rejected (e.g. ComfyUI-Manager blocks it due to the
security_level/--listen restriction), the reason is captured in task
history but the progress toast only rendered the streamed server logs --
which stay empty for a request rejected before the task runs. The failed
task also misleadingly showed 'Completed'.

- Expose isTaskFailed and getTaskErrorMessages from the manager store
- Render a task's error messages in its failed panel
- Label failed tasks 'Failed' (in danger color) instead of 'Completed'

Amp-Thread-ID: https://ampcode.com/threads/T-019eafaf-ac38-734b-8fa1-1422ed378e78
Co-authored-by: Amp <amp@ampcode.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/workbench/extensions/manager/stores/comfyManagerStore.test.ts (1)

415-430: ⚡ Quick win

Prefer function declaration for pure test helper.

The historyItem helper is a pure function with no side effects. Per coding guidelines, use function historyItem(...) instead of const historyItem = (...) => for better hoisting clarity and consistency with the repository's functional style.

♻️ Refactor to function declaration
-    const historyItem = (
+    function historyItem(
       uiId: string,
       statusStr: 'success' | 'error' | 'skip',
       messages: string[]
-    ): TaskHistoryItem => ({
+    ): TaskHistoryItem {
+      return {
       ui_id: uiId,
       client_id: 'client',
       kind: 'install',
       result: statusStr === 'success' ? 'success' : 'failed',
       timestamp: '2024-01-01T00:00:00Z',
       status: {
         status_str: statusStr,
         completed: statusStr === 'success',
         messages
       }
-    })
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workbench/extensions/manager/stores/comfyManagerStore.test.ts` around
lines 415 - 430, The test helper historyItem is a pure function defined as a
const arrow; change it to a named function declaration to follow project style
and hoisting conventions: replace the const historyItem = (...) => { ... } with
function historyItem(uiId: string, statusStr: 'success' | 'error' | 'skip',
messages: string[]): TaskHistoryItem { ... } and keep the same return shape
(ui_id, client_id, kind, result, timestamp, status) and types to avoid changing
behavior; ensure TaskHistoryItem typing is preserved and update any references
to historyItem accordingly.

Source: Coding guidelines

src/workbench/extensions/manager/components/ManagerProgressToast.vue (1)

44-54: ⚡ Quick win

Prefer function declaration for consistency.

The isTaskInProgress helper is defined as an arrow function, but other helpers in this file (togglePanel, isAtBottom, scrollLastPanelToBottom) use function declarations. Per coding guidelines, prefer function isTaskInProgress(taskId: string) { ... } for consistency and better hoisting clarity.

♻️ Refactor to function declaration
-const isTaskInProgress = (taskId: string) => {
+function isTaskInProgress(taskId: string) {
   const taskQueue = comfyManagerStore.taskQueue
   if (!taskQueue) return false
 
   const allQueueTasks = [
     ...(taskQueue.running_queue || []),
     ...(taskQueue.pending_queue || [])
   ]
 
   return allQueueTasks.some((task) => task.ui_id === taskId)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workbench/extensions/manager/components/ManagerProgressToast.vue` around
lines 44 - 54, Convert the isTaskInProgress arrow function into a function
declaration to match other helpers; replace "const isTaskInProgress = (taskId:
string) => { ... }" with "function isTaskInProgress(taskId: string) { ... }"
while preserving the logic that reads comfyManagerStore.taskQueue, builds
allQueueTasks from running_queue and pending_queue, and returns
allQueueTasks.some(task => task.ui_id === taskId); keep the same null check for
taskQueue and return false when absent.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/workbench/extensions/manager/components/ManagerProgressToast.vue`:
- Around line 44-54: Convert the isTaskInProgress arrow function into a function
declaration to match other helpers; replace "const isTaskInProgress = (taskId:
string) => { ... }" with "function isTaskInProgress(taskId: string) { ... }"
while preserving the logic that reads comfyManagerStore.taskQueue, builds
allQueueTasks from running_queue and pending_queue, and returns
allQueueTasks.some(task => task.ui_id === taskId); keep the same null check for
taskQueue and return false when absent.

In `@src/workbench/extensions/manager/stores/comfyManagerStore.test.ts`:
- Around line 415-430: The test helper historyItem is a pure function defined as
a const arrow; change it to a named function declaration to follow project style
and hoisting conventions: replace the const historyItem = (...) => { ... } with
function historyItem(uiId: string, statusStr: 'success' | 'error' | 'skip',
messages: string[]): TaskHistoryItem { ... } and keep the same return shape
(ui_id, client_id, kind, result, timestamp, status) and types to avoid changing
behavior; ensure TaskHistoryItem typing is preserved and update any references
to historyItem accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2db1d51e-78a8-4421-b99a-4868327c11bb

📥 Commits

Reviewing files that changed from the base of the PR and between d349677 and c7ccafc.

📒 Files selected for processing (3)
  • src/workbench/extensions/manager/components/ManagerProgressToast.vue
  • src/workbench/extensions/manager/stores/comfyManagerStore.test.ts
  • src/workbench/extensions/manager/stores/comfyManagerStore.ts

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

Labels

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