{"record":{"id":"b6ceaf1feb3b9d44","repo":"can1357/oh-my-pi","slug":"run-jobname-is-running-cancel-it-first","errorCode":null,"errorMessage":"run ${jobName} is running; cancel it first","messagePattern":"run (.+?) is running; cancel it first","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/metaharness/src/server.ts","lineNumber":576,"sourceCode":"\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;\n\t\tif (this.#runLive(run)) throw new Error(`run ${jobName} is running; cancel it first`);\n\t\tthis.#destroyRun(jobName);\n\t\tthis.#tick();\n\t\treturn true;\n\t}\n\n\t/** Remove a run's DB rows and on-disk artifacts (job dir + manager log). */\n\t#destroyRun(jobName: string): void {\n\t\tassertSafeJobName(jobName);\n\t\tthis.#store.deleteRun(jobName);\n\t\tfs.rmSync(path.join(this.jobsDir, jobName), { recursive: true, force: true });\n\t\tfs.rmSync(path.join(this.jobsDir, \"_manager\", \"logs\", `${jobName}.log`), { force: true });\n\t}\n\n\t/** Add a comparable arm to an existing experiment, inheriting its sample + config. */\n\taddArm(experimentId: string, req: AddArmRequest): { jobName: string; pid: number } {\n\t\treturn this.launch(resolveArmLaunch(this.#store, experimentId, req));\n\t}\n","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/metaharness/src/server.ts#L558-L594","documentation":"deleteRun removes a run's DB rows and on-disk artifacts (job dir and manager log), but refuses while the run is live: if `#runLive(run)` is true it throws telling you to cancel the run first. Unknown job names return false instead of throwing.","triggerScenarios":"Calling `deleteRun(jobName)` while the run's process is still executing (live per `#runLive`).","commonSituations":"A cleanup routine deleting jobs without checking status; attempting to purge a run that is mid-trial; operator deletes the wrong still-active job.","solutions":["Cancel the run first (cancel API), then delete it","Wait for the run to finish before deleting","If the process is actually dead but state is stale, restart the manager (which reaps state, e.g. markExit on restart) and retry"],"exampleFix":"// before\nserver.deleteRun(\"job-7\"); // throws: run job-7 is running\n// after\nif (server.isRunLive?.(\"job-7\")) server.cancelRun(\"job-7\");\nserver.deleteRun(\"job-7\");","handlingStrategy":"validation","validationCode":"function canDeleteRun(server: ManagerServer, jobName: string): boolean {\n  const run = server.getRun(jobName);\n  return !!run && !server.isRunRunning(jobName);\n}","typeGuard":null,"tryCatchPattern":"try {\n  server.deleteRun(jobName);\n} catch (err) {\n  if (err instanceof Error && /is running; cancel it first/.test(err.message)) {\n    server.cancelRun(jobName);\n    server.deleteRun(jobName);\n  } else throw err;\n}","preventionTips":["Check run status before delete","Cancel live runs first in cleanup tooling","Remember unknown runs return false, only live runs throw","Clear stale running state via manager restart"],"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"}