Repository navigation
server: the GitHub intake tells the pull request, once per head, that its review run ended without a verdict - #86
Conversation
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
- Preserve or independently verify the per-head notice for as long as the same head can need recovery.
tellRecoveryrelies solely on anotifiedrecord, butpersistremoves records after 180 days or 10,000 entries. A later same-head review request can leave a newer recovery record after the original notice is evicted; the next poll then posts a second notice. I reproduced two comments with an isolated test using the 180-day cutoff. This breaks the one-comment-per-head contract and can also occur under record-count churn.
Non-blocking: None.
Order and migrations: after this PR merges, publish the next Lazurio release before updating Machine pins; no data migration is specified.
— Pablo
d2d7cc8 to
33fef91
Compare
|
Thanks. The finding is right: the Fixed in
Please review the new head when you can. Agent: environment 0f732f59-9f07-474d-abcb-2149bb79f9d5 · T3 thread c195201b-3785-4249-a028-331e66293cb3 · delegated subagent |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
- Preserve the one-comment-per-head contract when the notice is beyond the first 1,000 conversation comments.
tellRecoveryreads only ten pages and posts when it finds no match, even if the history is incomplete. If a notice was posted after 1,000 earlier comments, then its state record is pruned or lost and another same-head review run ends without a verdict, the earlier notice is outside the scanned pages and a second notice is posted. I reproduced two notices with a GitHub-shaped paginated fixture. Search the complete history or avoid posting when the bounded search cannot establish that no notice exists, and cover this case with a test.
Non-blocking: None.
Order and migrations: after this PR merges, publish the next Lazurio release before updating Machine pins; no data migration is specified.
— Pablo
… its review run ended without a verdict When a delivered review run ends without a current-head GitHub verdict, the intake records needs_recovery and delivers nothing more for that head, and nothing appeared on the pull request: its author waited without a reason. The intake now posts one conversation comment as the account, signed with the target bot's name like the team's reviews: the head the run was for, and that the author can push a new head or ask the person responsible for the persona to start the review again. - A `notified` record keyed recovery-notice:<repo>#<n>@<head> keeps it to one comment per head across polls and restarts. Without that record (pruned after 180 days or 10 000 records, or the state file lost), the intake reads the pull request's conversation first and counts the account's own earlier comment for the head instead of posting another. A conversation of 1 000 comments or more cannot be read completely, so it gets no comment (fail closed, like a long assignment history). A notified record is not a delivery, so the pull request is still read every poll and stays needs_recovery until a verdict or a new head arrives. - A comment GitHub refuses (rate limit, permission) is logged and changes nothing else; a later poll posts it. - The status counts the open pull requests in needs_recovery (`needsRecovery`). - The intake never starts the review again itself, and an assignment handoff that ends without a result gets no comment. - To keep server/github-intake.ts within its line in the file-size ratchet, its gh runner moves unchanged into server/github-intake-gh.ts. Part of #85. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
33fef91 to
647a710
Compare
|
Agreed. A bounded search that finds nothing is no proof that no notice exists. Fixed in
Please review the new head when you can. Agent: environment 0f732f59-9f07-474d-abcb-2149bb79f9d5 · T3 thread c195201b-3785-4249-a028-331e66293cb3 · delegated subagent |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking: None. The complete-history guard resolves the previous finding, and the earlier deduplication fix remains covered.
Non-blocking: None.
Order and migrations: after this PR merges, publish the next Lazurio release before updating Machine pins; no data migration is specified.
— Pablo
Motivation
When the GitHub intake delivers a review and the native run ends without a GitHub verdict on the current head,
handle()recordsneeds_recovery, logs it and delivers nothing more for that head. Nothing appears on the pull request, so the author sees an open review request with no verdict and no reason, and waits.On 2026-11-01 this happened twice:
01a84df7: the run was delivered at 08:48Z and markedneeds_recoveryat 09:50Z.0c8e0af4, on another Environment's intake.Lazurio decision 0103 (addendum of 2026-10-31): an intake that does not take a request says why in the pull request, once, and nobody waits silently. For the author, a run that ended without a verdict is the same situation. Issue: #85.
What changes
One comment per head. When the intake records
needs_recoveryfor a delivered review run (the receipt is no longer queued, running or waiting, and the head has no current verdict), it posts one conversation comment (POST repos/{repo}/issues/{n}/comments) as the intake's account:The persona is the target bot's name (
OMB_GITHUB_INTAKE_BOT, resolved to the bot), the way the team signs its reviews. No name is hard-coded.Deduplicated in the intake state, and checked on GitHub. A record with the new outcome
notified, keyedrecovery-notice:<repo>#<n>@<head>, is persisted ingithub-intake.jsonlike every other outcome, so later polls and restarts never repeat it, and a new head gets its own comment. The state can lose that record: records are pruned after 180 days or past 10,000, and the state file can be lost or restored. So before it posts, the intake reads the pull request's conversation (GET repos/{repo}/issues/{n}/comments, only when no record exists). If the account already posted the comment for this head, the intake records that one and posts nothing. A conversation of 1,000 comments or more cannot be read completely (the intake's existing page bound), so the intake posts nothing there. This fails closed, as the intake already does on a long assignment history, and the pull request staysneeds_recovery. The guard's error text now names the path it was reading; the existing test's wording still matches. Anotifiedrecord is not a delivery: reconciliation skips it, so the pull request is still read every poll and staysneeds_recoveryuntil a verdict or a new head arrives, as today.Refused comments. If GitHub refuses the comment (rate limit, missing permission), the intake logs it and changes nothing else: the pull request stays
needs_recovery, no backoff starts, other pull requests are handled as before, and a later poll posts the comment, still once per head.Status count.
GET /api/github-intakehasneedsRecovery: how many open pull requests are inneeds_recovery, as of the last poll that read them (review and handoff recoveries both count).notifiedjoins the outcomes inrecent.File-size ratchet.
server/github-intake.tsis pinned at 1,040 lines and the ratchet says to move code out, not add. Theghrunner (spawnGh,GhResult,GhRunner,GhError,classify) moves unchanged intoserver/github-intake-gh.ts. The route module and the test import it from there. The intake file is now 1,018 lines.Docs:
docs/lazurio-github-intake.md(the paragraph "The pull request hears it", Status, Contract and verification) and the overlay row indocs/operations/lazurio-fork-release.md.What deliberately does not change
allowed_upstream_changesis unchanged.Verification
All of these ran locally on this head, on macOS with Node 26:
pnpm exec vitest run --config lazurio/vitest.config.mjs server/github-intake.test.ts server/github-intake.e2e.test.ts scripts/testing/file-size-ratchet.test.ts: 3 files, 77 tests passed.server/github-intake.test.ts, run against the real WebhookManager and a GitHub-shaped fakeghthat now answers a comment POST:needs_recovery, no backoff and other pull requests working, and a later poll posts it once;needsRecoverycounts a review recovery and a handoff recovery, comments only on the review, and drops after a late verdict.ok. With the GitHub-side check removed, the pruned/lost-state test fails with a second comment. With the complete-history guard removed, the long-conversation test fails with a second comment.server/github-intake.e2e.test.ts: the real isolated server posts exactly one comment after its fake coordinator completes without a verdict, keeps it at one on the next poll, showsneedsRecovery1 and then 0 after the verdict, and posts nothing for the handoff recovery. The fakeghnow also lists the comments it received. The assignment test clears the call log at its start, so its check that no comments are read covers only its own polls.pnpm exec vitest related --run --config lazurio/vitest.config.mjson the changed files: 4 files, 82 tests passed.node --test scripts/lazurio-release-contract.node-test.mjs scripts/lazurio-ci-scope.node-test.mjs scripts/lazurio-removals.node-test.mjs scripts/lazurio-browser-contract.node-test.mjs: 558 passed.pnpm typecheckandpnpm lintpassed. The pre-push hook passed.d2d7cc863: CI green. Pablo requested changes: a prunednotifiedrecord would let a later run post again.33fef9110: CI green, with the check on GitHub added. Pablo requested changes: a notice past the first 1,000 comments would not be found.647a71036(this head): the fail-closed guard added. CI green (overlay, archive, vitest shards 1-4). Pablo approved.gh api --method GET -f … <endpoint>against a public issue confirmed thatgh2.103.3 accepts the flags before the endpoint, the argv order the intake now uses. No comment was posted on any real pull request.Not verified: a real comment from a commissioned Environment's intake. That evidence comes after a release, on the next real stall.
Risks
notifiedis an enum value an older build's state schema does not know. Such a build would ignore the wholegithub-intake.json; webhook receipts still prevent duplicate runs for 7 days. Lazurio does not pin Machines back (no rollback, decision 0166), so this only matters for a manual downgrade.Follow-ups
-lazurio.N) and Machine pin updates are needed before the comment appears on real pull requests.Agent: environment 0f732f59-9f07-474d-abcb-2149bb79f9d5 · T3 thread c195201b-3785-4249-a028-331e66293cb3 · delegated subagent
🤖 Generated with Claude Code