{"record":{"id":"997fbd1076906b9f","repo":"sinelaw/fresh","slug":"workspace-cannot-be-action-archive-archived-deleted","errorCode":null,"errorMessage":"workspace cannot be ${action === \"archive\" ? \"archived\" : \"deleted\"}","messagePattern":"workspace cannot be (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/plugins/orchestrator.ts","lineNumber":10677,"sourceCode":"}\n\nfunction apiDeleteFolder(folderId: string): boolean {\n  if (!folderById(folderId)) return false;\n  // `deleteFolder` reparents the subtree one level up, so nothing inside is\n  // lost — same as the dock's \"Delete Folder\".\n  deleteFolder(folderId);\n  refreshDockTree();\n  return true;\n}\n\n/// Shared guard for the three lifecycle verbs: the picker disables a button\n/// it cannot honour, and a caller deserves the reason rather than a no-op.\nfunction requireEligible(s: AgentSession, action: BulkAction): void {\n  if (bulkEligible(action, s.id)) return;\n  if (action === \"stop\") {\n    throw new Error(\"workspace has no agent process to stop\");\n  }\n  throw new Error(`workspace cannot be ${action === \"archive\" ? \"archived\" : \"deleted\"}`);\n}\n\nfunction apiStopWorkspace(target: string | number): boolean {\n  const s = resolveWorkspace(target);\n  if (!s) return false;\n  requireEligible(s, \"stop\");\n  // Same batch runner as the picker's confirmed Stop, over one id.\n  if (runStopBatch([s.id]) === 0) throw new Error(\"stop failed\");\n  refreshOpenDialog();\n  return true;\n}\n\n/// Archive / delete over a single workspace, through the same batch runner\n/// the picker's confirmed action uses — so the cross-machine manifest sync,\n/// and anything added to that path later, applies to a scripted archive too.\nasync function runLifecycle(\n  target: string | number,\n  action: \"archive\" | \"delete\",","sourceCodeStart":10659,"sourceCodeEnd":10695,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/plugins/orchestrator.ts#L10659-L10695","documentation":"requireEligible throws this for the \"archive\" and \"delete\" bulk actions when the workspace is not eligible for the requested verb — the message reads 'workspace cannot be archived' or 'workspace cannot be deleted'. As with stop, the goal is to give programmatic callers the reason a picker would have shown by disabling the button.","triggerScenarios":"Calling the archive/delete API (apiArchiveWorkspace/apiDeleteWorkspace) on a workspace for which bulkEligible returns false — e.g. a still-running agent that must be stopped first, a pending/placeholder session, or an already-archived workspace for a second archive.","commonSituations":"Bulk scripts that archive workspaces without stopping running agents; retry loops re-archiving an already-archived workspace; deleting a workspace in a mid-creation pending state.","solutions":["Stop the workspace's agent before requesting archive/delete","Check eligibility (bulkEligible or an equivalent status read) before calling","Skip or log already-archived/ineligible targets instead of retrying"],"exampleFix":"// before\nawait api.archiveWorkspace(id);\n// after\nconst ws = api.getWorkspace(id);\nif (ws?.hasRunningAgent) api.stopWorkspace(id);\nawait api.archiveWorkspace(id);","handlingStrategy":"validation","validationCode":"const ws = api.getWorkspace(id);\nif (!ws || ws.pending || ws.archived || ws.hasRunningAgent) {\n  throw new Error(`workspace ${id} not eligible for ${action}`);\n}","typeGuard":"function isLifecycleEligible(s, action) {\n  return s != null && !s.pending && !s.archived &&\n    (action === \"stop\" ? s.hasRunningAgent === true : true);\n}","tryCatchPattern":"try {\n  await api.archiveWorkspace(id);\n} catch (e) {\n  if (String(e).includes(\"cannot be archived\")) {\n    api.stopWorkspace(id);\n    await api.archiveWorkspace(id);\n  } else throw e;\n}","preventionTips":["Stop running agents before archive/delete","Filter out pending and already-archived workspaces in bulk operations","Check eligibility via status reads instead of blind retry","Map the verb in the message ('archived'/'deleted') back to your intended action when logging"],"tags":["typescript","workspace-lifecycle","precondition"],"backgroundTag":"invalid-state-transition","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}