{"record":{"id":"8ded61133bfcba7f","repo":"can1357/oh-my-pi","slug":"experiment-id-has-running-arms-live-map-r","errorCode":null,"errorMessage":"experiment ${id} has running arms (${live.map(r => r.jobName).join(\", \")}); cancel them first","messagePattern":"experiment (.+?) has running arms \\((.+?)\\); cancel them first","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/metaharness/src/server.ts","lineNumber":557,"sourceCode":"\t\t\tif (experimentOf(jobName) !== id) continue;\n\t\t\tif (this.#store.setRunMeta(jobName, update.runs[jobName])) updatedRuns.push(jobName);\n\t\t}\n\t\tthis.#tick();\n\t\treturn { id, updatedRuns };\n\t}\n\n\t/**\n\t * Delete an experiment: every arm's DB row, job dir, and manager log, plus\n\t * the goal row. Refuses while any arm is live (cancel first — deleting a\n\t * job dir under a writing runner would corrupt it). Returns null when the\n\t * id names neither runs nor a registered experiment.\n\t */\n\tdeleteExperiment(id: string): { id: string; deletedRuns: string[] } | null {\n\t\tconst runs = this.#store.listRuns().filter(r => experimentOf(r.jobName) === id);\n\t\tif (runs.length === 0 && !this.#store.getExperimentMeta(id)) return null;\n\t\tconst live = runs.filter(r => this.#runLive(r));\n\t\tif (live.length > 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`experiment ${id} has running arms (${live.map(r => r.jobName).join(\", \")}); cancel them first`,\n\t\t\t);\n\t\t}\n\t\tfor (const run of runs) this.#destroyRun(run.jobName);\n\t\tthis.#store.deleteExperimentMeta(id);\n\t\tthis.#tick();\n\t\treturn { id, deletedRuns: runs.map(r => r.jobName) };\n\t}\n\n\t/**\n\t * Permanently delete a run: DB row + trials, job dir, and manager log.\n\t * Disk removal is not optional — discover() would resurrect a surviving\n\t * job dir as a fresh row on the next restart. Refuses while the run is\n\t * live; returns false when the run is unknown.\n\t */\n\tdeleteRun(jobName: string): boolean {\n\t\tconst run = this.#store.getRun(jobName);\n\t\tif (!run) return false;","sourceCodeStart":539,"sourceCodeEnd":575,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/metaharness/src/server.ts#L539-L575","documentation":"deleteExperiment refuses to delete an experiment while any of its arms (runs) are still live. It throws listing the running job names so you can cancel them first. This prevents destroying run processes/DB rows and job dirs underneath active executions.","triggerScenarios":"Calling `deleteExperiment(id)` when at least one run whose `experimentOf(jobName)` equals id is currently live per `#runLive` (process still running).","commonSituations":"Cleanup scripts deleting old experiments while a long trial is still executing; a crashed manager left runs marked running; concurrent sessions where another operator started a new arm.","solutions":["Cancel each listed running arm first, then call deleteExperiment again","Wait for the arms to finish before deleting","If a run is stale (process actually dead but state says running), restart the manager or mark exit to clear the live state before deleting"],"exampleFix":"// before\nserver.deleteExperiment(\"sweep1\"); // throws: has running arms (sweep1-a, sweep1-b)\n// after\nfor (const arm of [\"sweep1-a\", \"sweep1-b\"]) {\n  server.cancelRun(arm); // stop live arms first\n}\nserver.deleteExperiment(\"sweep1\");","handlingStrategy":"try-catch","validationCode":"// check no arms are live before deleting\nconst running = server.listRuns().filter(\n  r => experimentOf(r.jobName) === id && server.isRunRunning(r.jobName)\n);\nif (running.length > 0) throw new Error(`cancel ${running.map(r => r.jobName)} first`);","typeGuard":null,"tryCatchPattern":"try {\n  server.deleteExperiment(id);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"has running arms\")) {\n    // parse job names from message and cancel each, then retry once\n  } else throw err;\n}","preventionTips":["Cancel all arms of an experiment before deleting it","Check run status in a cleanup script before deletion","Avoid deleting experiments while trials are active","Recover stale 'running' state by restarting the manager"],"tags":["state","concurrency","lifecycle"],"backgroundTag":"resource-in-use","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}