Skip to content

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

Open
Kosinkadink wants to merge 3 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 3 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ccd82895-2bcc-4222-ae76-0b60e317e810

📥 Commits

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

📒 Files selected for processing (10)
  • browser_tests/tests/dialogs/managerDialog.spec.ts
  • src/services/apiClient.ts
  • src/services/comfyRegistryService.ts
  • src/services/customerEventsService.ts
  • src/workbench/extensions/manager/components/manager/ManagerDialog.vue
  • src/workbench/extensions/manager/composables/useRegistrySearch.test.ts
  • src/workbench/extensions/manager/services/comfyManagerService.test.ts
  • src/workbench/extensions/manager/services/comfyManagerService.ts
  • src/workbench/extensions/manager/stores/comfyManagerStore.test.ts
  • src/workbench/extensions/manager/stores/comfyManagerStore.ts
💤 Files with no reviewable changes (1)
  • src/workbench/extensions/manager/composables/useRegistrySearch.test.ts

📝 Walkthrough

Walkthrough

Adds shared API client timeouts, registry search retry handling, Manager dialog retry UI, manager service error remapping, task failure helpers in the store, and per-task error rendering in the progress toast.

Changes

Manager retry and error surfacing

Layer / File(s) Summary
Shared API client and service wiring
src/services/apiClient.ts, src/services/comfyRegistryService.ts, src/services/customerEventsService.ts
Adds a shared axios client factory with a default timeout, and switches the registry and customer events services to it.
Registry search retry state
src/workbench/extensions/manager/composables/useRegistrySearch.ts, src/workbench/extensions/manager/composables/useRegistrySearch.test.ts
The registry search composable now tracks errors, clears them on new searches, exposes retry(), and has tests for failure and retry recovery.
Manager dialog retry UI
src/workbench/extensions/manager/components/manager/ManagerDialog.vue, browser_tests/tests/dialogs/managerDialog.spec.ts, src/locales/en/main.json
ManagerDialog now surfaces registry search failures with retry text and action handling, and browser tests cover the offline and retry flow.
Manager service timeouts and error mapping
src/workbench/extensions/manager/services/comfyManagerService.ts, src/workbench/extensions/manager/services/comfyManagerService.test.ts
The manager service adds read/request timeouts and remaps axios failures to backend, connectivity, and route-specific messages, with tests for the surfaced strings.
Task failure helpers in manager store
src/workbench/extensions/manager/stores/comfyManagerStore.ts, src/workbench/extensions/manager/stores/comfyManagerStore.test.ts
The store now classifies failed tasks from error statuses and exposes helpers for failure state and task error messages, with tests for error, success, and skipped history entries.
Toast rendering and task errors
src/workbench/extensions/manager/components/ManagerProgressToast.vue
The progress toast switches to task-id-based status helpers, adds conditional styling, and renders per-task error messages before log lines.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

core/1.45

Suggested reviewers

  • AustinMroz
  • DrJKL

Poem

🐰 I hopped through retries, bright and neat,
With “Try Again” at bunny feet.
Errors speak, and timeouts rest,
Task logs bloom with error text.
A carrot, click, and off I go —
Back through the manager flow!

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main user-facing fixes: offline loading prevention and backend security message surfacing.
Description check ✅ Passed The description includes Summary, Changes, and Testing, matching the template; Review Focus and Screenshots are optional and not critical.
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.
End-To-End Regression Coverage For Fixes ✅ Passed Bug-fix PR, but it does add a Playwright regression test in browser_tests/managerDialog.spec.ts, so the no-E2E-test failure condition is not met.
Adr Compliance For Entity/Litegraph Changes ✅ Passed No changed files are under src/lib/litegraph/, src/ecs/, or graph-entity paths, so the ADR checks don’t apply.
✨ 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
⚔️ Resolve merge conflicts
  • Resolve merge conflict 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.

@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 thread src/workbench/extensions/manager/services/comfyManagerService.ts
@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

@christian-byrne christian-byrne 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.

Reviewed via a multi-agent pass (13 reviewers) focused on how this fits the existing manager domain, the sibling services, and repo conventions/frameworks.

The two user-facing fixes are real and mostly land in the right layers (error/retry in the composable, normalization in the service, derived task state in the store, reuse of NoResultsPlaceholder + cn() + i18n + semantic tokens), and the core try/catch/finally spinner fix is correct and well tested.

Requesting changes on two points, both about alignment with the rest of the repo rather than the local logic:

  1. comfyManagerService error handling now diverges from its sibling comfyRegistryService in two ways at once: the timeout this PR adds renders "failed with status undefined" on the offline/timeout case it targets (no no-response branch), and the backend-message precedence is inverted globally so curated/localized route and 404 messages are silently demoted for every status, not just the 403 security case in the description. See the two inline issues.

  2. The timeout is copy-pasted into two of the three near-identical axios clients (the third, customerApiClient, is left with the original hung-socket bug). A shared client factory would fix all three and remove the duplicated constant/comment. Inline suggestion.

The rest are non-blocking suggestions and nitpicks on architecture shape, framework consistency, AGENTS.md comment style, a 'skip'-task edge case, and a11y/design polish. A rebase onto main (currently ~90 commits behind) before merge would also help. Happy to re-review once the two blocking items are addressed.

// 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) {

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.

issue: This reorders precedence so any backend data.message now wins over routeSpecificErrors[status] and the 404 -> "Could not connect to ComfyUI-Manager" string, for every route and status, not just the 403 security case the PR targets. Curated/localized messages (queueTask 404, updateAllPacks 401, the security_level strings) are silently demoted whenever the backend returns any body, and the status code is dropped from the fallback. The sibling comfyRegistryService.ts uses the opposite order (route-specific, then status switch, then data.message as the innermost default), so the two services that mirror each other now disagree on where an error string comes from. Could the backend-first rule be scoped to where it is needed, e.g. only when there is no routeSpecificErrors[status] match (or 403 only), so curated strings stay authoritative?

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.

issue: The new client timeout is the headline fix, but a timeout/offline error is an axios error with response === undefined, so status and backendMessage are both undefined and this branch renders "<context> failed with status undefined", the exact vague UX the PR set out to remove, now reachable through the timeout it just added. The sibling comfyRegistryService.ts handles the no-response case (return ${context}: ${axiosError.message}); this service does not. Could you add a no-response / code === 'ECONNABORTED' branch before the final else, plus a timeout-shaped test (e.g. mockRejectedValue({ isAxiosError: true, code: 'ECONNABORTED' }))?


// Without a timeout a hung socket (e.g. no internet, captive portal) never
// rejects, leaving callers stuck in their loading state indefinitely.
const REQUEST_TIMEOUT_MS = 10_000

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.

suggestion: There are three near-identical axios clients in the repo: managerApiClient here, registryApiClient in comfyRegistryService.ts, and customerApiClient in customerEventsService.ts. This PR adds the timeout to two of them but leaves customerApiClient without one, so the exact hung-socket bug this PR fixes stays latent there. Would a small shared createApiClient({ baseURL, ...overrides }) factory (baking in the JSON header + timeout, and later interceptors) be cleaner? It gives the timeout policy one home, lets customerApiClient inherit the fix for free, and removes the duplicated constant + comment, while each service still owns its baseURL and error mapping.


const managerApiClient = axios.create({
baseURL: api.apiURL('/v2/'),
timeout: REQUEST_TIMEOUT_MS,

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.

question: This 10s timeout applies to every Manager endpoint on the shared client, including the synchronous POSTs that are not pure enqueues (REBOOT, UPDATE_COMFYUI, UPDATE_ALL, bulk LIST). On slow disks or large installs these can legitimately exceed 10s and would now abort where they previously succeeded. Would a higher ceiling on the shared client plus a per-request override for the search/status polls that actually motivated the fix be safer?

QUEUE_TASK = 'manager/queue/task'
}

// Without a timeout a hung socket (e.g. no internet, captive portal) never

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 (non-blocking): AGENTS.md asks not to add multi-line block comments to justify trivial changes. The REQUEST_TIMEOUT_MS name already conveys intent, so this 2-line comment (duplicated verbatim in comfyRegistryService.ts) is the kind it discourages. Same for the two comment blocks above the small guards in ManagerDialog.vue (hasConnectionError, the isLoading early-return) and the // The bug: comment in useRegistrySearch.test.ts. Flagging for house-style alignment, not blocking.

// restriction ComfyUI-Manager reports on a blocked install) lives in task
// history, not the streamed server logs -- which stay empty when the request
// is rejected before the task ever runs. Surface it so the failure isn't silent.
const getTaskErrorMessages = (taskId: string): string[] =>

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 (non-blocking): Placement in the store is right (pure derivation over state the store owns). The smell is that getTaskErrorMessages reaches independently into taskHistory[taskId].status.messages, a third walk of task history alongside partitionTasks and partitionTaskLogs. Would building a failedTaskMessages map during the existing partition pass keep a single definition of "failed task plus its reason"?


// The registry search failing (e.g. offline) is also a connection error worth
// surfacing, and unlike the manager-store error it can be retried in place.
const hasConnectionError = computed(

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.

suggestion (non-blocking): hasConnectionError ORs two errors with different recovery semantics: the manager-store error is not retryable here, while searchError is. But @action always binds retrySearch() and the title/button-label are driven by different predicates, so a non-retryable manager error still shows a retry that re-runs the registry search. Modeling the empty-state error as one discriminated value ({ kind: 'search', retry } | { kind: 'manager' } | null) would let title/message/button/action derive from one source and never disagree. The if (searchError.value) return false guard in isLoading is a symptom of the same thing: the composable leaves loading/error mutual exclusion implicit rather than in the type.

} = options

const isLoading = ref(false)
const error = ref<string | null>(null)

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 (non-blocking): This stores e.message (a string), but sibling composables in this domain (useNodePacks, useMissingNodes) expose the raw Error from useAsyncState. The only consumer here needs truthiness, so ref<Error | null> (assigning e directly and extracting the message at the display site) would match the domain's existing error shape and keep the type/cause for future consumers.

v-else-if="displayPacks.length === 0"
:title="emptyStateTitle"
:message="emptyStateMessage"
:button-label="searchError ? $t('manager.retry') : undefined"

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 (non-blocking): Two small polish gaps on this connection-error state, both about matching existing patterns. (1) Design: the canonical error placeholder ErrorDialogContent.vue passes icon="pi pi-exclamation-circle", but this passes only title/message/button, so a hard connection failure renders icon-less, identical to a benign "no results" state. Could you pass an error icon when searchError is set? (2) A11y: this content swap happens in a plain div with no role="alert"/aria-live, so screen-reader users who triggered the search are not told it failed or that a retry exists. Wrapping the error branch in role="alert" would announce it. Both non-blocking.

:key="`error-${errorIndex}`"
class="text-danger"
>
<pre class="wrap-break-word whitespace-pre-wrap">{{

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 (non-blocking): a11y, the surfaced failure reason renders as <pre> lines with no aria-live, so the actionable security text (e.g. the security_level/--listen guidance) is not announced when it appears, and the log panel may be collapsed. The status label flipping to "Failed" is good (text, not color only); marking this container aria-live="polite" (or role="alert") would surface the reason to AT users.

- Scope backend-message preference to 403 so curated route/404 strings stay
  authoritative (was overriding every status); keep the security goal intact.
- Handle timeout/offline (no-response) errors explicitly so they no longer
  render "failed with status undefined".
- Check isTabLoading before short-circuiting on searchError in ManagerDialog so
  a stale search error can't hide the tab loading skeleton.
- Use a generous shared timeout ceiling for long sync POSTs and a short
  per-request timeout for reads/polls.
- Model the empty-state error as a discriminated value so a non-retryable
  manager error no longer shows a search retry.
- Treat only status_str === 'error' as a failed task ('skip' is not a failure).
- Extract a shared createApiClient factory (JSON header + timeout), fixing the
  latent missing timeout on customerApiClient.
- Add unit tests (timeout, route-string precedence, skipped task) and Playwright
  regression coverage for the Manager offline/retry flow.

Amp-Thread-ID: https://ampcode.com/threads/T-019efced-7233-707c-bccf-7a4218671fdd
Co-authored-by: Amp <amp@ampcode.com>
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.

2 participants