From 4c0259fa761cb03309b85edee1d941ca8c069c8f Mon Sep 17 00:00:00 2001 From: Jiang Bohan Date: Tue, 21 Apr 2026 20:49:05 +0800 Subject: [PATCH] fix(agents): tasks tab crashes when agent has autopilot run_only tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Autopilot `run_only` tasks have no linked issue; the server serializes that as `issue_id: ""` (not null) via `uuidToString` on an invalid pgtype.UUID. The agent detail Tasks tab assumed every task had a real issue id and fed `""` into `api.getIssue(id)` → `/api/issues/` and into `paths.issueDetail("")`, crashing the whole tab as soon as one such task existed on the agent. Handle the empty-issue case explicitly: - Filter empty ids out of `issueIds` so `useQueries` doesn't fire `/api/issues/` for a nonexistent issue. - Render run_only rows as non-link `
`s labeled "Autopilot run" instead of clickable issue links. No server-side change — the `""` serialization stays as-is; callers just need to treat it as "no issue". --- .../agents/components/tabs/tasks-tab.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/views/agents/components/tabs/tasks-tab.tsx b/packages/views/agents/components/tabs/tasks-tab.tsx index 8f740c38e..47b05c032 100644 --- a/packages/views/agents/components/tabs/tasks-tab.tsx +++ b/packages/views/agents/components/tabs/tasks-tab.tsx @@ -31,8 +31,13 @@ export function TasksTab({ agent }: { agent: Agent }) { // issue may or may not be in the paginated issue-list cache, so going // through `issueDetailOptions` is the reliable lookup path (and it shares // the same cache as the issue detail page). + // + // Autopilot `run_only` tasks have no linked issue; the server serializes + // that as issue_id = "". Filter those out before issuing detail queries + // so we don't hit `/api/issues/` with an empty id (which was crashing + // the whole tab). const issueIds = useMemo( - () => Array.from(new Set(tasks.map((t) => t.issue_id))), + () => Array.from(new Set(tasks.map((t) => t.issue_id).filter((id) => id !== ""))), [tasks], ); const issueQueries = useQueries({ @@ -99,7 +104,10 @@ export function TasksTab({ agent }: { agent: Agent }) { {sortedTasks.map((task) => { const config = taskStatusConfig[task.status] ?? taskStatusConfig.queued!; const Icon = config.icon; - const issue = issueMap.get(task.issue_id); + // Autopilot run_only tasks carry issue_id = "" — skip the lookup + // and render them as non-link rows labeled "Autopilot run". + const hasIssue = task.issue_id !== ""; + const issue = hasIssue ? issueMap.get(task.issue_id) : undefined; const isActive = task.status === "running" || task.status === "dispatched"; const isRunning = task.status === "running"; const rowClassName = `flex items-center gap-3 rounded-lg border px-4 py-3 transition-shadow hover:shadow-sm ${ @@ -125,7 +133,7 @@ export function TasksTab({ agent }: { agent: Agent }) { )} - {issue?.title ?? `Issue ${task.issue_id.slice(0, 8)}...`} + {issue?.title ?? (hasIssue ? `Issue ${task.issue_id.slice(0, 8)}...` : "Autopilot run")}
@@ -146,6 +154,14 @@ export function TasksTab({ agent }: { agent: Agent }) { ); + if (!hasIssue) { + return ( +
+ {content} +
+ ); + } + return (