Skip to content

pr:review over REST only, with --once for cloud sessions - #224

Merged
thejackshelton merged 31 commits into
masterfrom
pr-review-rest
Oct 8, 2026
Merged

thejackshelton merged 31 commits into
masterfrom
pr-review-rest

Conversation

@thejackshelton

@thejackshelton thejackshelton commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Cloud sessions reach GitHub through a proxy that allows REST only. pnpm pr:review used GraphQL (gh repo view --json, gh pr view --json three times), so it failed there. This PR ports it to REST and adds a single-poll mode. land.ts, land-lib.ts and merge-train*.ts are untouched; their call sites get ported in a later PR (#220 and #222 are changing land.ts).

What changed

  • scripts/gh-rest.ts (new): typed helpers over gh api, REST only. The repo comes from GH_REPO ([HOST/]OWNER/REPO) or from the origin remote URL (https, ssh, scp-style, or a proxy URL ending in /owner/repo). Helpers: repo, prView (state OPEN/CLOSED/MERGED, head sha and ref, base, mergeable, labels, body, draft, fork), prFiles, prCommits, prList(base?), prForBranch, prCreate, addLabels, removeLabel, issueComment, prSetBase, prMerge(pr, sha) (PUT pulls/<n>/merge with the sha guard, so GitHub refuses a moved head), checkRuns(sha), prComments, and reviewComments. All list endpoints use --paginate --slurp.
    • Mergeable mapping: mergeable_state: "dirty" or mergeable: false maps to CONFLICTING, mergeable: null to UNKNOWN, and anything else to MERGEABLE. On 12 live PRs it matched gh pr view --json mergeable: 216, 214, 210, 208, 206, 203 and 200 are CONFLICTING; 218, 212, 198, 196 (unstable) and 194 are MERGEABLE.
    • Validation: every answer is validated, and an unexpected shape throws with the field's name (for example gh-rest: unexpected pull.head.sha).
    • Retries: reads retry transient failures 5 times, the same as the old gh() wrapper. Writes never retry, since a retried POST could post twice. A malformed answer is never retried.
  • scripts/pr-review.ts: every gh call now goes through gh-rest. The no-argument form finds the current branch's PR with pulls?head=owner:branch, taking the open one or else the newest. The verdict logic is unchanged and still comes entirely from pr-review-vouch.ts: CI wait, Macroscope review of the latest commit, vouched skips, UNREVIEWED, unanswered findings, --conflicts-ok, and exit codes.
    • New --once flag: polls a single time and exits 0 if clean, 1 if not clean, or 2 if pending. Pending means not settled(), the point where --wait would poll again. A cloud session can loop on it within its 30-minute command limit. --once and --wait together is an error.
    • One clock: the settled test and the verdict now read the same now, so --once can never print a clean verdict and also exit 2.
  • scripts/pr-review-vouch.ts: adds onceExit, plus the UNKNOWN and IGNORED handling described in round 2. parsePrHead, parsePrCommits and parseCrossRepository stay because land.ts and merge-train.ts use them.

Round 2: an UNKNOWN mergeability is pending (Claude review, Medium)

This bug predates this PR. While GitHub is still computing mergeability (REST mergeable: null, GraphQL UNKNOWN), settled() and outcome() treated it as clean, so pr:review could exit 0 on a conflicting PR.

Equivalence on real PRs (read-only, head 8a353d9)

The old script is origin/master's scripts/pr-review.ts, run from the main checkout with its own pr-review-vouch.ts (unchanged on origin/master). Both ran back to back without --wait, then the new one ran again with --once. "Output" compares stdout and stderr byte for byte.

PR (args) state old exit new exit new --once output findings verdict lines (new)
215 merged 0 0 0 identical 0 UNREVIEWED
213 merged 0 0 0 identical 0 UNREVIEWED
2 merged, reviewed clean 0 0 0 identical 0 –
219 open, clean 0 0 0 identical 0 UNREVIEWED
221 open, clean 0 0 0 identical 0 UNREVIEWED
222 open, clean 0 0 0 identical 0 UNREVIEWED
214 --conflicts-ok conflicting, ignored 0 0 0 identical 0 UNREVIEWED
216 --conflicts-ok conflicting, ignored 0 0 0 identical 0 UNREVIEWED
30 findings, no CI run 1 1 2 identical 2 Still running: Correctness; Failed: no CI run
25 "Diff unchanged" skip (vouch path) 1 1 1 identical 0 Failed: Correctness (not vouched: empty diff)
29 "No code objects reviewed" skip (vouch path, fork check) 1 1 1 identical 0 Failed: Correctness
31 correctness never started 1 1 2 identical 0 Still running: Correctness
1 no CI run 1 1 2 identical 0 Failed: no CI run
212, 196, 214, 216, first run just after master moved open, dirty 0 1 1 differs (the bug above) 0 CONFLICTING, or Still running: mergeability
212, 196, 214, 216, 30 s later open, dirty 1 1 1 identical 0 Failed: PR head … is CONFLICTING

PRs 30, 31 and 1 report pending under --once because settled() is false there: CI or the correctness check is still missing. --wait keeps polling in exactly those states.

With no PR number, both versions resolved #224 from the branch. The new script also ran over every PR from 1 to 212 (round 1) with no errors.

Tests

  • New packages/parity/test/gh-rest.test.ts:
    • remote URL parsing (https, ssh, scp, proxy, and malformed URLs) and GH_REPO precedence
    • mergeable mapping
    • pull shape validation naming each field
    • multi-page flattening for comments, check runs, files, PR lists and commits
    • branch lookup
    • reads retry but malformed answers and writes do not
    • write payloads go as JSON on stdin
    • the merge sha guard and a refused merge
    • PR-number checks
  • packages/parity/test/pr-review.test.ts: the judgedHead test now expects IGNORED instead of UNKNOWN; that was the requested change. New tests cover UNKNOWN as pending (settled false, exit 1, --once 2, a failure still ends the wait) and IGNORED from --conflicts-ok or a closed PR passing on the checks alone. It also adds "pr-review over REST", using the existing run() and HEAD fakes and a fake gh that serves REST JSON with no network. It covers clean, an unanswered finding, an answered finding, pending (exit 1, --once 2), dirty with and without --conflicts-ok, UNREVIEWED, and malformed REST answers. It also adds a grep test that fails if pr-review.ts or gh-rest.ts names a GraphQL-using gh subcommand ('pr', 'view'-style argument pairs, gh pr … text) or "graphql". This test fails on origin/master's pr-review.ts.

No existing test, tolerance or check was changed or removed.

What passed

  • pnpm typecheck
  • pnpm exec vitest run on these test files, plus land and merge-train, 232 tests:
    • packages/parity/test/pr-review.test.ts
    • packages/parity/test/gh-rest.test.ts
    • packages/parity/test/chrome-ports.test.ts
    • packages/parity/test/registry-claims.test.ts
    • packages/dragon/test/registry-claims.test.ts
    • packages/parity/test/land.test.ts
    • packages/parity/test/merge-train.test.ts

This PR was itself opened with ghRest().prCreate (POST repos/…/pulls).

🤖 Generated with Claude Code

thejackshelton and others added 30 commits October 8, 2026 00:45
…h pnpm ci:test-files to dispatch and read it over REST
…ems 1 and 4)

scripts/cloud-setup.sh, run by the project SessionStart hook only when CLAUDE_CODE_REMOTE=true, installs the Node every
workflow pins and the pnpm of packageManager, then pnpm install --frozen-lockfile and pnpm setup:git, which now also turns
rerere off. DRAGON_REQUIRE_NATIVE=1 turns a native run with a missing toolchain from blocked (owner tooling) into a failure
naming the tool, in runTarget and runHostLane, as CI's "No native run was blocked" step does.
…group; ci:test-files reads reports strictly and removes them
…then the merge drivers)

floor-merge.test.ts pins pnpm setup:git to exactly what the landing driver sets; rerere.enabled false joins both, so the
exact-equality check stays and covers the new line.
…_NATIVE=1, prove the Linux path in CI

Review of 892a687: the hook now has no matcher, so it reruns after compact, clear and fork (compaction drops CLAUDE_ENV_FILE
exports); in a cloud session the script writes export DRAGON_REQUIRE_NATIVE=1 to CLAUDE_ENV_FILE first (PM ruling) and fails
without that file; only the summary line reaches stdout; npm --prefix; curl --retry-connrefused. ci.yml's checks job runs on
ubuntu-24.04 and runs cloud-setup.sh twice before setup-node, checking Node, pnpm, the export, typecheck and no reinstall.
…oid ABI rebaseline

- LAND_DEVICES, LAND_TEST and LAND_REGEN accept ci-only; LAND_CI=only sets all three. Under ci-only
  GitHub Actions not running a step is a CiOutage: the driver stops, fails no PR, and every PR not
  landed stays queued (status: STOPPED BY A CI OUTAGE). All three ci-only skips the priority and
  quiet files; the leases are only on local paths, which ci-only never reaches.
- awaitOnCi tells a run queued for runners (jobs queued, or the run pending) from one that never
  started: queued runs are waited for up to LAND_CI_QUEUE_WAIT (default 3 h); the run's own wait
  counts from its first job's start.
- LAND_CI_MAX_INFLIGHT (default 2) caps the prepared CI regens a batch dispatches at once.
- R3: a CI device run whose only model changes are Android image ABI changes is an architecture
  rebaseline, logged loudly and recorded in the landing; each changed lane must keep the previous
  state and exactly its failures. Under LAND_DEVICES=ci a local fallback onto records of another
  ABI stops the driver instead of blaming the PR.
…re; a nonce finds the dispatched run; every test vitest collects must run; the setup test covers env, vitest command and blocked scan
…review)

A macOS job waiting for a runner while the Ubuntu jobs ran is queued, under LAND_CI_QUEUE_WAIT from
when it was first seen waiting; a running job has the step's wait from its own start; a run with
jobs done but not completed is waited for up to the queue wait. Under LAND_CI=only, the PR's own CI
never running or never finishing is a CiOutage. Unreadable previous device records in the ci-mode
fallback refusal stop the driver (Fatal) instead of blaming the PR.
Commands: pnpm regen; pnpm run parity:devices; pnpm regen
…s on the clean head; audit before the PR; direct-push rebase
Cloud session bootstrap and DRAGON_REQUIRE_NATIVE (cloud migration items 1 and 4)
…missing mergeable, GH_REPO host, open PR only)
test-files.yml: run chosen test files on their full-test runners, dispatched and read over REST by pnpm ci:test-files
Land: ci-only mode (LAND_CI=only), queued CI runs waited for, CI Android ABI rebaseline
Lane contract and AGENTS.md steps 2-4 for cloud lanes (cloud migration item 5)
@thejackshelton
thejackshelton merged commit 8a3e9d2 into master Oct 8, 2026
4 checks passed
@thejackshelton
thejackshelton deleted the pr-review-rest branch October 8, 2026 08:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant