Skip to content

bug(ui): navigating to a non-existent agent renders a blank page instead of a not-found message #1914

Description

@obasilakis

Summary

Navigating directly to an agent URL that doesn't resolve — e.g. http://localhost/agents/does-not-exist — renders a completely blank page. No error, no "agent not found", no way back except the top nav. The route matches, AgentDetail.vue mounts, and then nothing is drawn at all.

AgentDetail.vue already has an error banner (v-if="error && !agent", line 25) — it just never fires, because the error never reaches the view.

Context

Hit while navigating to a stale bookmark/URL for an agent that no longer exists. The user is left staring at an empty dark canvas with no indication that anything went wrong.

Root cause — stores/agents.js::fetchAgent (line 138) swallows the failure:

async fetchAgent(name) {
  try {
    const response = await axios.get(`/api/agents/${name}`, { headers: authStore.authHeader })
    ...
    return this.selectedAgent
  } catch (error) {
    this.error = error.message
    console.error('Failed to fetch agent:', error)   // <-- swallowed, returns undefined
  } finally { this.loading = false }
}

It catches, logs to the console, and returns undefined. So in AgentDetail.vue::loadAgent (line 858) the catch block never runs, error.value stays '', and agent.value is undefined — making both v-if="error && !agent" and v-if="agent" false. Nothing renders. The only evidence is a console line the user never sees.

Enumeration-safety constraint (Invariant #8, #186): the backend deliberately returns a uniform 404 for both a non-existent agent and one the caller can't access — that differential is an enumeration oracle. The frontend copy must therefore not distinguish "this agent doesn't exist" from "you don't have access to it". One neutral message covers both.

Acceptance Criteria

  • Navigating to /agents/<unknown-name> renders a visible not-found state instead of a blank page
  • The message is neutral and does not disclose whether the agent exists (uniform-404 contract, Invariant security: implement safe tar extraction with symlink/hardlink validation #8 / SEC: User and Agent Enumeration via differential API responses #186) — e.g. "Agent not found, or you don't have access to it."
  • The state offers a next action (link back to the Dashboard) — no dead empty state. Deliberately not the Agents list: the agent you asked for isn't in that list either, so it's a second dead end for the same question
  • A non-404 failure (network error, 500, expired token) shows a distinguishable "couldn't load" message with a retry, not the not-found copy
  • stores/agents.js::fetchAgent no longer silently returns undefined on failure — the caller can tell success from failure
  • The existing tolerant caller in AgentDetail.vue (the post-stop status poll, ~line 712, which deliberately tolerates transient fetch errors) still tolerates them and does not start throwing
  • Regression test covering the 404 path renders the not-found state (not a blank page)

Technical Notes

  • src/frontend/src/stores/agents.js — fetchAgent (line 138): the swallow. Either re-throw, or return a discriminated result; re-throwing is cleaner but requires wrapping the ~line 712 caller.
  • src/frontend/src/views/AgentDetail.vue — loadAgent (line 858) and the error banner (line 25). Minimal fix: distinguish 404 from other errors and render a proper not-found panel rather than the generic red banner.
  • src/frontend/src/router/index.js line 58 — the /agents/:name route. Alternative/complementary approach: a beforeEnter guard, though the two other agent routes (/agents/:name/workspace, /agents/:name/brain) already bail to AgentDetail, so fixing the view is the higher-leverage single point.
  • Worth checking whether sibling views that fetch a named agent (AgentWorkspace.vue, AgentBrainOrb.vue — each with their own local fetchAgent) share the blank-page failure mode. If so, note it here rather than expanding this issue's scope.
  • Same swallow-and-return-undefined shape exists elsewhere in stores/agents.js; out of scope here, but a candidate for a follow-up refactor.

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions