{"record":{"id":"99e895f9a7e11c81","repo":"santifer/career-ops","slug":"could-not-release-report-reservation-e-message","errorCode":null,"errorMessage":"Could not release report reservation: ${e.message}","messagePattern":"Could not release report reservation: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"openrouter-runner.mjs","lineNumber":698,"sourceCode":"    const tsvFile     = `batch/tracker-additions/or-${numStr}-${slug}.tsv`;\n    // AGENTS.md: a tracker-addition TSV is a SINGLE data line of 9 tab-separated\n    // columns. merge-tracker.mjs reads the whole file as ONE record (no line\n    // splitting), so a leading header row makes parts[4]/parts[5] the literal\n    // \"status\"/\"score\" and the evaluation is skipped (\"cannot tell score from\n    // status\"). Write only the data line.\n    writeFile(tsvFile, tsvLine);\n\n    console.log(`\\n✅ Report saved: ${relPath}`);\n    console.log('\\n─── EVALUATION ──────────────────────────────────────\\n');\n    console.log(result);\n    console.log('\\n─────────────────────────────────────────────────────\\n');\n\n    return relPath;\n  } finally {\n    try {\n      await releaseReportNumbers(reservedNumbers, { reportsDir: path.join(__dirname, 'reports') });\n    } catch (e) {\n      console.warn(`Could not release report reservation: ${e.message}`);\n    }\n  }\n}\n\n// -- PIPELINE --\nasync function cmdPipeline(ctx) {\n  const pending = readPipeline();\n  if (pending.length === 0) {\n    console.log('No pending listings in pipeline.md.');\n    return;\n  }\n\n  console.log(`Processing ${pending.length} pending listing(s) from pipeline.md...\\n`);\n\n  for (let i = 0; i < pending.length; i++) {\n    const item = pending[i];\n    console.log(`\\n[${i + 1}/${pending.length}] ${item.company} — ${item.role}`);\n    try {","sourceCodeStart":680,"sourceCodeEnd":716,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/openrouter-runner.mjs#L680-L716","documentation":"After writing a report, openrouter-runner.mjs's finally block calls releaseReportNumbers() (imported from reserve-report-num.mjs) to delete the NNN-RESERVED.md sentinel files that claim its report-number slots. If that release throws -- typically ENOENT because the sentinel was already deleted by verify-pipeline's stale GC or another release -- the warn fires and the reservation leaks; leaked sentinels expire on their own after 4 hours.","triggerScenarios":"A batch run overlapping verify-pipeline.mjs, which unlinks sentinels older than 4h while the worker is still finishing; filesystem errors or permission changes on reports/ between reservation and release; two code paths releasing the same range.","commonSituations":"Long batch evaluations running concurrently with health checks; interrupted processes on CI; NFS/latency-prone mounts where unlink races are common.","solutions":["Manually free the range: node reserve-report-num.mjs --release NNN-MMM.","Or run node verify-pipeline.mjs -- sentinels older than 4h are GC'd automatically and reported as 'Removed stale reservation sentinel'.","If release keeps failing on a live sentinel, check permissions on reports/ (ls -la reports/ | grep RESERVED)."],"exampleFix":"// before: any release error surfaces as a generic warn\ntry { await releaseReportNumbers(reservedNumbers, opts); }\ncatch (e) { console.warn(`Could not release report reservation: ${e.message}`); }\n// after: tolerate the benign already-deleted race\ntry { await releaseReportNumbers(reservedNumbers, opts); }\ncatch (e) {\n  if (!/ENOENT/.test(e.message)) console.warn(`Could not release report reservation: ${e.message}`);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Release in finally, and treat an already-deleted sentinel as success\ntry {\n  await writeReports(reservedNumbers);\n} finally {\n  try {\n    await releaseReportNumbers(reservedNumbers, { reportsDir: REPORTS_DIR });\n  } catch (e) {\n    if (e.code !== 'ENOENT') throw e; // someone else GC'd/released it: benign\n  }\n}","preventionTips":["Always wrap reservation usage in try/finally with releaseReportNumbers in the finally block.","Don't run verify-pipeline's sentinel GC in parallel with live batch workers — the 4h age check makes overlap mostly safe, but release races still warn.","If a leak happens anyway, node reserve-report-num.mjs --release NNN-MMM or verify-pipeline after 4h cleans it up."],"tags":["reservation","sentinel","cleanup","finally-block","report-numbering"],"backgroundTag":"stale-lock-cleanup","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}