{"record":{"id":"96f7187a2316d816","repo":"sinelaw/fresh","slug":"workspace-has-no-agent-process-to-stop","errorCode":null,"errorMessage":"workspace has no agent process to stop","messagePattern":"workspace has no agent process to stop","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/plugins/orchestrator.ts","lineNumber":10675,"sourceCode":"  refreshOpenDialog();\n  return true;\n}\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(","sourceCodeStart":10657,"sourceCodeEnd":10693,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/plugins/orchestrator.ts#L10657-L10693","documentation":"requireEligible guards the single-workspace lifecycle verbs against ineligibility: for the \"stop\" action it throws this error when the workspace has no agent process to stop (bulkEligible('stop', id) is false). The picker disables such buttons; programmatic callers get an explicit reason instead of a silent no-op.","triggerScenarios":"Calling apiStopWorkspace(target) for a workspace whose session has no running agent process — already stopped, archived, or a placeholder session that never spawned a process.","commonSituations":"Double-clicking Stop so the second call races the first; scripting stop across a workspace list containing archived entries; stopping a workspace created but whose agent never launched.","solutions":["Check eligibility before stopping (e.g. verify the workspace has a running agent) or skip ineligible targets","Handle the already-stopped case as success in your script logic","Re-resolve the workspace; if it was archived, unarchive it first before lifecycle verbs"],"exampleFix":"// before\napi.stopWorkspace(id);\n// after\nif (api.getWorkspace(id)?.hasRunningAgent) {\n  api.stopWorkspace(id);\n}","handlingStrategy":"validation","validationCode":"const stoppable = (ws) => ws && ws.state === \"running\" && ws.hasRunningAgent;\nif (stoppable(api.getWorkspace(id))) api.stopWorkspace(id);","typeGuard":"function hasStoppableAgent(s) {\n  return s != null && s.hasRunningAgent === true && s.archived !== true;\n}","tryCatchPattern":"try {\n  api.stopWorkspace(id);\n} catch (e) {\n  if (String(e).includes(\"no agent process to stop\")) {\n    // already stopped: treat as success\n  } else throw e;\n}","preventionTips":["Check the workspace's agent state before calling stop","Deduplicate stop requests so a second click can't race the first","Skip archived or pending workspaces in batch stop scripts","Treat 'no agent process' as an idempotent-success case in automation"],"tags":["typescript","workspace-lifecycle","process-management"],"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"}