From 80eeb075140df646adabdfb8111bbafbcc0ac2d7 Mon Sep 17 00:00:00 2001 From: centdix Date: Wed, 10 Jun 2026 01:37:33 +0200 Subject: [PATCH] fix(ai): only treat 404 as missing draft, surface real errors Co-Authored-By: Claude Opus 4.8 (1M context) --- .../copilot/chat/global/core.test.ts | 13 +++++++++---- .../components/copilot/chat/global/core.ts | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/frontend/src/lib/components/copilot/chat/global/core.test.ts b/frontend/src/lib/components/copilot/chat/global/core.test.ts index ea4ceb5794..3b26c406ac 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.test.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.test.ts @@ -208,6 +208,11 @@ const WORKSPACE = 'global-core-test' // implementations re-wired in beforeEach. type SimRow = Record +// Mimic the generated client's ApiError shape so the code's `status === 404` +// not-found discrimination behaves the same against the sim. +function notFound(message: string): Error { + return Object.assign(new Error(message), { status: 404 }) +} const scriptRows = new Map() const scriptDrafts = new Map() const flowRows = new Map() @@ -246,13 +251,13 @@ function resetDbSim(): void { }) setImpl(ScriptService.getScriptByPathWithDraft, async ({ path }: any) => { const row = scriptRows.get(path) - if (!row) throw new Error(`script "${path}" not found`) + if (!row) throw notFound(`script "${path}" not found`) const draft = scriptDrafts.get(path) return { ...row, draft, draft_created_at: draft ? DRAFT_TS : undefined } }) setImpl(ScriptService.getScriptByPath, async ({ path }: any) => { const row = scriptRows.get(path) - if (!row) throw new Error(`script "${path}" not found`) + if (!row) throw notFound(`script "${path}" not found`) return row }) setImpl(ScriptService.deleteScriptByPath, async ({ path }: any) => { @@ -295,13 +300,13 @@ function resetDbSim(): void { }) setImpl(FlowService.getFlowByPathWithDraft, async ({ path }: any) => { const row = flowRows.get(path) - if (!row) throw new Error(`flow "${path}" not found`) + if (!row) throw notFound(`flow "${path}" not found`) const draft = flowDrafts.get(path) return { ...row, draft, draft_created_at: draft ? DRAFT_TS : undefined } }) setImpl(FlowService.getFlowByPath, async ({ path }: any) => { const row = flowRows.get(path) - if (!row) throw new Error(`flow "${path}" not found`) + if (!row) throw notFound(`flow "${path}" not found`) return row }) setImpl(FlowService.deleteFlowByPath, async ({ path }: any) => { diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index 4c153cfec0..d10e884663 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -2339,6 +2339,13 @@ function finishDraftWrite(stored: WorkspaceItem, existed: boolean, ctx: WriteDra type DbDraftKind = Extract +// A 404 from getXByPathWithDraft means the item genuinely doesn't exist (no +// deployed version and no draft). Any other error (network, 403, 5xx) is real +// and must surface rather than be silently swallowed as "no draft". +function isNotFoundError(e: unknown): boolean { + return (e as { status?: number } | undefined)?.status === 404 +} + // Returns the localStorage draft ONLY when a live editor is currently mounted // for the item (i.e. its draft is the live editor's working buffer). A stale, // non-live localStorage draft is ignored so the DB draft stays authoritative. @@ -2378,9 +2385,9 @@ async function loadDbDraftItem( const item = flowToItem(src as Flow, true) item.isDraft = true return item - } catch { - // Item doesn't exist (never deployed and no draft row). - return undefined + } catch (e) { + if (isNotFoundError(e)) return undefined // never deployed and no draft row + throw e } } @@ -2450,8 +2457,10 @@ async function deleteScriptFlowDbDraft( const flow = await FlowService.getFlowByPathWithDraft({ workspace, path }) draftOnly = flow.draft_only === true } - } catch { - // No backing row; nothing to delete server-side beyond the editor buffer. + } catch (e) { + // 404 = no backing row; nothing to delete server-side beyond the editor + // buffer. Any other error is real and should surface. + if (!isNotFoundError(e)) throw e } if (draftOnly) {