{"record":{"id":"9d5c9ea9186848fd","repo":"stablyai/orca","slug":"could-not-read-watchdog-child-rss-for-pid-pid","errorCode":null,"errorMessage":"Could not read watchdog child RSS for PID ${pid}","messagePattern":"Could not read watchdog child RSS for PID (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"config/scripts/hang-watchdog-process-metrics.mjs","lineNumber":30,"sourceCode":"  const physicalFootprintSamples = []\n  for (let index = 0; index < options.sampleCount; index += 1) {\n    rssSamples.push(readRss())\n    physicalFootprintSamples.push(readPhysicalFootprint())\n    await options.sleep(options.sampleIntervalMs)\n  }\n  return {\n    rssBytes: median(rssSamples),\n    physicalFootprintBytes: median(physicalFootprintSamples)\n  }\n}\n\nexport function childRssBytes(pid) {\n  const raw = execFileSync('ps', ['-o', 'rss=', '-p', String(pid)], {\n    encoding: 'utf8'\n  }).trim()\n  const rssKiB = Number(raw)\n  if (!Number.isFinite(rssKiB) || rssKiB <= 0) {\n    throw new Error(`Could not read watchdog child RSS for PID ${pid}`)\n  }\n  return rssKiB * 1024\n}\n\nexport function parsePhysicalFootprintBytes(output, processCount) {\n  const match =\n    processCount > 1\n      ? output.match(/^Summary Footprint:\\s+(\\d+) B$/m)\n      : output.match(/^[^\\s].*\\sFootprint:\\s+(\\d+) B/m)\n  const bytes = Number(match?.[1])\n  return Number.isFinite(bytes) && bytes > 0 ? bytes : null\n}\n\nexport function physicalFootprintBytes(pids) {\n  const pidArgs = pids.flatMap((pid) => ['--pid', String(pid)])\n  const output = execFileSync(\n    '/usr/bin/footprint',\n    [...pidArgs, '--format', 'bytes', '--noCategories'],","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/hang-watchdog-process-metrics.mjs#L12-L48","documentation":"Thrown by childRssBytes() when `ps -o rss= -p <pid>` returns output that is not a finite positive number. RSS (resident set size, in KiB) is parsed to bytes for the watchdog child process memory measurement. A non-positive or NaN result means the PID is gone or ps output is unparseable.","triggerScenarios":"The child PID exited before the read (race between measurement and shutdown), the PID was recycled, or ps produced empty/malformed output (e.g. locale/formatting differences).","commonSituations":"The watchdog child crashed or was killed mid-sampling, a slow shutdown timing overlap, or running on a macOS version where ps column formatting differs.","solutions":["Verify the child PID is still alive before sampling: `process.kill(pid, 0)`.","Ensure the child is not being killed externally during the measurement window.","Debug by running `ps -o rss= -p <pid>` manually during a trial."],"exampleFix":"// before\nconst rss = childRssBytes(child.pids[1])  // throws if child exited\n// after\nif (child.exitCode !== null) throw new Error('child exited before RSS sample')\nconst rss = childRssBytes(child.pids[1])","handlingStrategy":"validation","validationCode":"// Confirm the child is alive before sampling RSS\nfunction isAlive(pid: number): boolean {\n  try { process.kill(pid, 0); return true } catch { return false }\n}\nif (!isAlive(pid)) throw new Error(`pid ${pid} exited before RSS sample`)","typeGuard":"const isProcessAlive = (pid: number): boolean => { try { process.kill(pid, 0); return true } catch { return false } }","tryCatchPattern":null,"preventionTips":["Sample RSS while the child is held alive by the harness (before shutdown).","Avoid external kills during the measurement window."],"tags":["metrics","process","macos","ps","race-condition"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}