fix(ui): show a not-found state instead of a blank page for an unknown agent (#1914) - #1915
Merged
Merged
Conversation
…n agent (#1914) Navigating to /agents/<unknown> rendered nothing at all — no error, no way back. AgentDetail already HAD an error banner; it just never fired. Root cause: stores/agents.js::fetchAgent caught the 404, logged it to the console, and returned `undefined`. So AgentDetail::loadAgent's catch never ran — `error` stayed '' and `agent` stayed null, making BOTH `v-if="error && !agent"` and `v-if="agent"` false. The only evidence was a console line the user never sees. - fetchAgent re-throws. Returning `undefined` on failure is indistinguishable from a successful empty fetch, which is what made the failure invisible. The one caller that genuinely tolerates a failure (the post-stop status poll) already wraps it in its own try/catch, so it is unaffected. - loadAgent splits 404 from everything else: 404 -> a not-found panel with a "Back to Dashboard" link (no dead empty state); anything else -> the generic banner, now with a Retry (retrying a 404 is pointless, so only the retryable branch offers it). The exit is the Dashboard, not the Agents list — the agent you asked for isn't in that list either, so the list is a second dead end for the same question. - The copy does NOT say whether the agent exists. The backend returns a uniform 404 for "no such agent" and "you can't see this agent" on purpose — the differential is an enumeration oracle (Invariant #8 / #186) — and the UI must not reintroduce it. A test pins the absence of an existence claim. e2e/agent-not-found.spec.js is @smoke: the agent deliberately does not exist, so unlike the other AgentDetail specs it needs no fixture agent, no running container and no Claude dispatch. Verified failing on the pre-fix code (3/3) and passing after.
|
Resolve by running |
vybe
approved these changes
Aug 4, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
Validated via /validate-pr — clean pass: root cause fixed at the store, both fetchAgent call-sites audited, enumeration-safe 404 copy pinned by test, regression specs verified failing pre-fix and green in CI (e2e ran). Approving and merging in sequence.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1914
The bug
Navigating to
/agents/<unknown>rendered a completely blank page — no error, no way back.AgentDetail.vuealready had an error banner (v-if="error && !agent"). It just never fired.Root cause — the store, not the view
stores/agents.js::fetchAgentcaught the 404, logged it to the console, and returnedundefined:So
AgentDetail::loadAgent'scatchnever ran:errorstayed''andagentstayednull, which made bothv-if="error && !agent"andv-if="agent"false. Nothing rendered. The only evidence was a console line the user never sees.Returning
undefinedon failure is indistinguishable from a successful empty fetch — that ambiguity is the whole bug.The fix
fetchAgentre-throws. The one caller that genuinely tolerates a failure — the post-stop status poll inAgentDetail(waitForAgentStatus, ~L739) — already wraps it in its owntry/catch, so it keeps polling through transient errors exactly as before. Verified: only two callers ofagentsStore.fetchAgentexist, both in this file. (AgentWorkspace.vue/AgentBrainOrb.vuehave their own localfetchAgentthat hits axios directly and deliberately degrades — untouched.)loadAgentsplits 404 from everything else. 404 → a not-found panel. Anything else (network blip, 500, expired token) → the generic banner, now with a Retry. Retrying a 404 is pointless, so only the retryable branch offers one.The copy is deliberately vague, and a test pins that
The backend returns a uniform 404 for "no such agent" and "you can't see this agent" on purpose — the differential is an enumeration oracle (Invariant #8 / #186). So the panel says:
…covering both without claiming which. One of the specs asserts the absence of an existence claim (
not.toContainText(/no such agent|does not exist on this instance|was deleted/i)) so a well-meaning future copy edit can't quietly leak it back.Tests
src/frontend/e2e/agent-not-found.spec.js— 3 specs, tagged@smokeso they run in CI onui-labelled PRs. They're@smoke-eligible precisely because the agent deliberately does not exist: unlike every other AgentDetail spec, they need no fixture agent, no running container, and no Claude dispatch. ~2s, deterministic.Verified failing on the pre-fix code — stashed the two source files, re-ran, 3/3 failed; restored, 3/3 pass. Existing
smoke.spec.jsstill green. Happy path (real agent loads) confirmed unaffected by the re-throw.Verification
Run against a live stack via a Vite dev server on the branch, proxying to the local backend:
/agents/skills-fixture//agents/acme-scoutScope note
stores/agents.jshas the same swallow-and-return-undefinedshape in other actions (fetchAgents, etc.). Left alone here — none of them produce a blank page, and widening this would turn a targeted bug fix into a store-wide refactor. Worth a follow-up.