{"record":{"id":"40ac1168b2932e52","repo":"sinelaw/fresh","slug":"stop-failed","errorCode":null,"errorMessage":"stop failed","messagePattern":"stop failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/plugins/orchestrator.ts","lineNumber":10685,"sourceCode":"  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\",\n): Promise<boolean> {\n  // Before `requireEligible`, which rejects an ineligible workspace.\n  await yieldToCaller();\n  const s = resolveWorkspace(target);\n  if (!s) return false;\n  requireEligible(s, action);\n  const res = await runLifecycleBatch(action, [s.id]);\n  if (res.ok === 0) {","sourceCodeStart":10667,"sourceCodeEnd":10703,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/plugins/orchestrator.ts#L10667-L10703","documentation":"apiStopWorkspace resolves the target, asserts stop eligibility, then delegates to runStopBatch — the same runner the picker's confirmed Stop uses. If the batch reports zero stopped workspaces, the stop definitively failed and the API throws 'stop failed' rather than returning true while nothing was stopped.","triggerScenarios":"runStopBatch([id]) returning 0: the underlying stop of the agent process failed (process already gone in a bad state, batch runner error, cross-machine manifest conflict), even though the workspace initially looked stop-eligible.","commonSituations":"Agent process crashed between eligibility check and stop; permission issues terminating the process; transient backend errors during the batch execution.","solutions":["Inspect the workspace/agent state after the failure and retry the stop once","Fall back to the bulk/picker path (runStopBatch directly) to surface a more detailed batch error","If the agent process is already dead, treat the workspace as stopped and clean up via archive/delete instead"],"exampleFix":"// before\napi.stopWorkspace(id);\n// after\ntry {\n  api.stopWorkspace(id);\n} catch (e) {\n  if (String(e).includes(\"stop failed\")) {\n    await new Promise(r => setTimeout(r, 500));\n    api.stopWorkspace(id); // one retry\n  }\n}","handlingStrategy":"retry","validationCode":"const ws = api.getWorkspace(id);\nif (!ws || !ws.hasRunningAgent) return; // nothing to stop","typeGuard":null,"tryCatchPattern":"try {\n  api.stopWorkspace(id);\n} catch (e) {\n  if (String(e) === \"Error: stop failed\") {\n    await new Promise(r => setTimeout(r, 500));\n    api.stopWorkspace(id); // single retry for transient batch failure\n  } else throw e;\n}","preventionTips":["Verify the agent process exists before stopping","Allow a short delay/retry for transient stop failures","Inspect workspace state after a failed stop before escalating","Use the picker/batch path when you need richer failure detail"],"tags":["typescript","process-stop","batch-operation"],"backgroundTag":"operation-failed","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"}