{"record":{"id":"4fc6a4766ecf0ffc","repo":"can1357/oh-my-pi","slug":"resume-jobdir-has-no-harbor-config-json-not","errorCode":null,"errorMessage":"--resume: ${jobDir} has no harbor config.json (not a harbor job dir)","messagePattern":"--resume: (.+?) has no harbor config\\.json \\(not a harbor job dir\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/metaharness/src/runner.ts","lineNumber":397,"sourceCode":"\tdataset?: string;\n\tconfig?: LaunchRequest;\n}\n\n/**\n * Recover the original launch Config for `--resume <job>` — nothing needs\n * re-specifying. Prefers the exact Config snapshot recorded at launch\n * (`_bench/<job>/runner-config.json`), falling back to rebuilding runner argv\n * from the manager.json launch record of API-launched runs. The job dir's own\n * harbor config.json decides the container backend: harbor rejects a resume\n * whose reconstructed config differs from the recorded one.\n */\nexport function resolveResumeConfig(cli: Config): Config {\n\tconst spec = cli.resume as string;\n\tconst jobsDir = spec.includes(path.sep) ? path.dirname(path.resolve(spec)) : cli.jobsDir;\n\tconst jobName = path.basename(spec);\n\tconst jobDir = path.join(jobsDir, jobName);\n\tconst jobConfig = readJson(path.join(jobDir, \"config.json\")) as { environment?: { type?: string } } | null;\n\tif (!jobConfig) throw new Error(`--resume: ${jobDir} has no harbor config.json (not a harbor job dir)`);\n\n\tlet cfg: Config | null = null;\n\tconst saved = readJson(path.join(jobsDir, \"_bench\", jobName, \"runner-config.json\"));\n\tif (saved && typeof saved === \"object\") {\n\t\tcfg = { ...defaultConfig(), ...(saved as Partial<Config>) };\n\t} else {\n\t\tconst manager = readJson(path.join(jobDir, \"manager.json\")) as ManagerRecord | null;\n\t\tif (manager?.config) {\n\t\t\tif (manager.benchmark && manager.benchmark !== \"harbor\") {\n\t\t\t\tthrow new Error(`--resume supports only harbor runs (${jobName} is ${manager.benchmark})`);\n\t\t\t}\n\t\t\tconst dataset = manager.config.dataset ?? manager.dataset ?? \"terminal-bench@2.0\";\n\t\t\tcfg = parseArgs(harborRunnerArgs(manager.config, { jobsDir, jobName, dataset }));\n\t\t}\n\t}\n\tif (!cfg) {\n\t\tthrow new Error(\n\t\t\t`--resume: no recorded launch config for ${jobName} ` +","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/metaharness/src/runner.ts#L379-L415","documentation":"resolveResumeConfig reconstructs a Config from a previous job directory. It first reads <jobDir>/config.json to verify the directory actually is a harbor job dir; if the file is missing or unparseable (readJson returns null), it throws rather than guessing, because all subsequent reconstruction depends on that config.","triggerScenarios":"`runner --resume <name>` where <jobsDir>/<name>/config.json does not exist; --resume given a path whose parent dir is the wrong folder; the job dir was partially deleted or cleanup removed config.json; typo'd job name so the dir itself doesn't exist.","commonSituations":"Resuming after manually clearing the job directory; passing a job from a different benchmark tool that has no harbor config.json; running from the wrong cwd so cli.jobsDir points elsewhere; truncation of the spec path.","solutions":["Verify the directory exists and contains config.json: `ls <jobsDir>/<jobName>/config.json`.","Use --resume with just the job name (resolved under --jobs-dir) or a full path to the job dir — mixed forms resolve differently (path separator triggers dirname resolution).","Recreate the job if config.json was deleted; resume cannot proceed without it.","Point --jobs-dir at the directory that actually contains your job dirs."],"exampleFix":"// before\nrunner --resume myjob   # jobs dir default, job actually in /tmp/runs/myjob\n// after\nrunner --resume /tmp/runs/myjob   # or: runner --jobs-dir /tmp/runs --resume myjob","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs\";\nconst jobDir = spec.includes(path.sep) ? path.dirname(path.resolve(spec)) : path.join(jobsDir, spec);\nif (!fs.existsSync(path.join(jobDir, \"config.json\"))) {\n  throw new Error(`not a harbor job dir (no config.json): ${jobDir}`);\n}","typeGuard":"function isJobDir(p: string): boolean {\n  return fs.existsSync(path.join(p, \"config.json\"));\n}","tryCatchPattern":"try {\n  await resumeJob(spec);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"has no harbor config.json\")) {\n    console.error(`No harbor job at '${spec}'. Check job name and --jobs-dir.`);\n  } else throw e;\n}","preventionTips":["List the jobs dir and confirm the job name exists before resuming.","Don't clean config.json out of job dirs; keep job dirs intact for resume.","Use a consistent --jobs-dir across invocations so name-based lookup resolves where you expect."],"tags":["resume","file-not-found","validation","job-directory"],"backgroundTag":"missing-job-config","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}