{"record":{"id":"5d3074559aadf497","repo":"can1357/oh-my-pi","slug":"run-jobname-is-already-running","errorCode":null,"errorMessage":"run ${jobName} is already running","messagePattern":"run (.+?) is already running","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/metaharness/src/server.ts","lineNumber":393,"sourceCode":"\t\t\t},\n\t\t});\n\t}\n\n\t/** Launch any supported benchmark and register it in the uniform run store. */\n\tlaunch(request: LaunchRequest): { jobName: string; pid: number } {\n\t\tif (!request.model) throw new Error(\"model is required\");\n\t\tconst benchmark = request.benchmark ?? \"harbor\";\n\t\tif (benchmark !== \"harbor\" && benchmark !== \"edit\" && benchmark !== \"snapcompact\") {\n\t\t\tthrow new Error(`unsupported benchmark: ${benchmark}`);\n\t\t}\n\t\tconst dataset =\n\t\t\trequest.dataset ??\n\t\t\t(benchmark === \"harbor\" ? \"terminal-bench@2.0\" : benchmark === \"edit\" ? \"typescript-edit\" : \"squad-dev\");\n\t\tconst stamp = new Date().toISOString().replace(/[:.]/g, \"-\").slice(0, 19);\n\t\tconst modelSlug = request.model.replace(/[^a-zA-Z0-9]+/g, \"-\");\n\t\tconst jobName = request.jobName ?? `${modelSlug}-${stamp}`;\n\t\tif (this.#children.has(jobName) || this.#store.getRun(jobName)?.status === \"running\") {\n\t\t\tthrow new Error(`run ${jobName} is already running`);\n\t\t}\n\t\tconst jobDir = path.join(this.jobsDir, jobName);\n\t\tfs.mkdirSync(jobDir, { recursive: true });\n\n\t\tlet argv: string[];\n\t\tlet cwd: string;\n\t\tif (benchmark === \"edit\") {\n\t\t\tcwd = PKG_DIR;\n\t\t\targv = [\"bun\", \"adapters/edit/cli.ts\", \"--model\", request.model, \"--output\", path.join(jobDir, \"result.json\")];\n\t\t\tif (request.tasks !== undefined) argv.push(\"--max-tasks\", String(request.tasks));\n\t\t\tif (request.include?.length) argv.push(\"--tasks\", request.include.join(\",\"));\n\t\t\tif (request.concurrency !== undefined) argv.push(\"--task-concurrency\", String(request.concurrency));\n\t\t\tif (request.attempts !== undefined) argv.push(\"--runs\", String(request.attempts));\n\t\t} else if (benchmark === \"snapcompact\") {\n\t\t\tcwd = PKG_DIR;\n\t\t\targv = [\"uv\", \"run\", \"src/adapters/snapcompact.py\", \"--model\", request.model, \"--output-dir\", jobDir];\n\t\t\tif (request.tasks !== undefined) argv.push(\"--limit-paras\", String(request.tasks));\n\t\t\tif (request.concurrency !== undefined) argv.push(\"--workers\", String(request.concurrency));","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/metaharness/src/server.ts#L375-L411","documentation":"Before spawning a runner, launch() checks both this.#children (live child processes this server owns) and the run store for a run whose status is 'running'. If either shows activity for the derived or requested jobName, a duplicate launch is refused to prevent two runners writing the same job directory. This error means the job name is already live.","triggerScenarios":"Calling launch() twice with the same explicit request.jobName while the first is still running; re-running a launch script quickly enough that the previous runner hasn't exited; a stale store row with status 'running' left by a crashed server (no markExit fired); a custom jobName colliding with an existing run.","commonSituations":"Cron jobs overlapping; CI retry re-invoking launch after a timeout while the first runner is still alive; server restart orphaning 'running' rows; two operators launching the same named experiment concurrently.","solutions":["Wait for the existing run to finish, or kill the live runner process before relaunching.","Omit request.jobName or change it — the default name embeds a timestamp, so letting it auto-generate avoids collisions.","Check the store: if the row says 'running' but the pid is dead, mark the exit (the resume path does this via markExit) or restart the server to reconcile.","Add application-level locking in your launcher script to prevent overlapping scheduled launches."],"exampleFix":"// before\nserver.launch({ model: \"m1\", jobName: \"m1-fixed\" }); // called again while running\n// after\nconst run = server.listRuns().find(r => r.jobName === \"m1-fixed\");\nif (!run || run.status !== \"running\") {\n  server.launch({ model: \"m1\", jobName: \"m1-fixed\" });\n}","handlingStrategy":"validation","validationCode":"const jobName = request.jobName ?? defaultName(request);\nconst live = store.listRuns().some(r => r.jobName === jobName && r.status === \"running\");\nif (live) throw new Error(`${jobName} still running; skipping launch`);","typeGuard":null,"tryCatchPattern":"try {\n  server.launch(req);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"is already running\")) {\n    console.warn(`run ${req.jobName} active; waiting 60s before retry`);\n    await Bun.sleep(60_000);\n  } else throw err;\n}","preventionTips":["Rely on the timestamped default jobName instead of fixed custom names.","Add a mutex/lock file around scheduled launches to prevent overlap.","Check run status before relaunching after CI timeouts.","After a server crash, reconcile stale 'running' rows (dead pid -> markExit) before relaunching."],"tags":["concurrency","state-conflict","duplicate"],"backgroundTag":"resource-already-running","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}