{"record":{"id":"a0bb5d4aa8d3eb45","repo":"tinyhumansai/openhuman","slug":"gh-api-patch-failed-r-stderr-trim","errorCode":null,"errorMessage":"gh api PATCH failed: ${r.stderr?.trim() || \"\"}","messagePattern":"gh api PATCH failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/agent-batch/status.mjs","lineNumber":183,"sourceCode":"  } else {\n    const id = existing[0].id;\n    // Pass the comment body via stdin (-F body=@-) rather than a command-line\n    // arg. Long markdown tables can grow large and -f body=${body} risks\n    // hitting OS argv length limits (ARG_MAX).\n    const r = spawnSync(\n      \"gh\",\n      [\n        \"api\",\n        \"--method\",\n        \"PATCH\",\n        `repos/${spec.base_repo}/issues/comments/${id}`,\n        \"-F\",\n        \"body=@-\",\n      ],\n      { encoding: \"utf8\", input: body },\n    );\n    if (r.status !== 0) {\n      throw new Error(`gh api PATCH failed: ${r.stderr?.trim() || \"\"}`);\n    }\n    process.stdout.write(\n      `[agent-batch] updated tracking comment ${existing[0].html_url}\\n`,\n    );\n  }\n}\n\nfunction main() {\n  const { positional, flags } = parseArgs(process.argv.slice(2));\n  if (flags.help || flags.h || flags[\"?\"]) {\n    process.stdout.write(`${usage()}\\n`);\n    process.exit(0);\n  }\n  const specPath = positional[0];\n  if (!specPath) {\n    process.stderr.write(`${usage()}\\n`);\n    process.exit(2);\n  }","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/agent-batch/status.mjs#L165-L201","documentation":"postOrUpdateTrackingComment() in scripts/agent-batch/status.mjs updates the previously-found tracking comment via `gh api --method PATCH repos/<repo>/issues/comments/<id> -F body=@-` (body on stdin to dodge ARG_MAX) and throws with gh's stderr on non-zero exit. This branch runs when the comment listing DID find a comment carrying the <!-- batch:<id> --> marker, and patches it in place with the freshly rendered table.","triggerScenarios":"A --post run where the comment id captured from the listing step no longer exists at PATCH time (someone deleted the tracking comment between the list and the patch), the token lacks issues:write, or a rate limit/network error hits the PATCH. The id comes from existing[0].id, so two humans running --post concurrently against the same batch can also race one comment out of existence.","commonSituations":"Maintainers deleting a stale tracking comment while an automated status loop is mid-update; read-only tokens reaching the PATCH stage because the earlier listing only needed read; primary/secondary rate limits during long-lived monitoring.","solutions":["Re-run the command — if the comment was deleted, the listing step now finds none and the script posts a fresh comment instead of PATCHing","Check the token has issues:write permission on the target repo (`gh auth status`)\n","Delete the orphaned marker comment yourself if you intentionally removed the old one, so the next run starts clean at the post path","Space out concurrent --post invocations to avoid racing the same comment id"],"exampleFix":"# before\n$ node scripts/agent-batch/status.mjs batch.json --post\nError: gh api PATCH failed: HTTP 404: Not Found\n\n# after (comment was deleted; re-run posts a new one)\n$ node scripts/agent-batch/status.mjs batch.json --post\n[agent-batch] posted new tracking comment on #1234","handlingStrategy":"retry","validationCode":"// Re-verify the comment still exists right before patching\nconst check = spawnSync(\"gh\", [\"api\", `repos/${spec.base_repo}/issues/comments/${id}`], { encoding: \"utf8\" });\nif (check.status !== 0) {\n  // comment vanished — fall back to the post path instead of PATCHing\n  return postNewComment(spec, body);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await patchComment(id, body);\n} catch (e) {\n  if (/gh api PATCH failed/.test(e.message) && /404/.test(e.message)) {\n    return postNewComment(spec, body); // deleted comment → post a fresh one\n  }\n  if (/rate limit/i.test(e.message)) { await sleep(30_000); return patchComment(id, body); }\n  throw e;\n}","preventionTips":["Treat PATCH-404 as a signal to re-list and re-post, not as a hard failure","When deleting tracking comments manually, delete ALL marker comments for the batch so the next --post regenerates cleanly","Serialize --post runs (lockfile or CI job) so two writers never race the same comment id"],"tags":["github","cli","rate-limit","race-condition"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}