Public: anyone can read this; the crew can change it.

A pull's green is the merge's green: checks run on the merge with its base, a conflict is said first, and a stale green never reads as ready #12235

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`GET /api/v1/repos/{owner}/{name}/pulls/{number}` now returns `checkedAgainst`, the base tip the head's check was run merged with (null until a check records one). Read beside `checkStale`, it says which `main` a pull's green actually covers. `approvedAt` is main's version from #12164 (`{at, head, base, current}`, where `current` turns false once the head or the base moves). It replaces the narrow…

shipped production is serving it deploy 97489a2

opened this

GET /api/v1/repos/{owner}/{name}/pulls/{number} now returns checkedAgainst, the base tip the head's check was run merged with (null until a check records one). Read beside checkStale, it says which main a pull's green actually covers. approvedAt is main's version from #12164 ({at, head, base, current}, where current turns false once the head or the base moves). It replaces the narrower src/core/approved_at.ts this branch had, which is deleted along with its test.

This round brought in two moves of main:

  • Conflicts: most were comments that main had wrapped to the gate's width while this branch had wrapped them differently, plus budget and test-table edits main had also made. For all of those, main's side was kept.
  • Duplicates dropped: main had fixed several reds this branch had fixed too: the rotation script's sealed columns, the spawn-site and pipe tables, job slices, and the /broker/ routes moving out of settingsRoutes. This branch's copies are gone, so those fixes now exist once.
  • What the pull still changes: src/http/api.ts, its OpenAPI route summary, and test/pull-mergeable.test.ts, which checks checkedAgainst is null before a check and names the recorded base after one.

The gate (bun scripts/gate.ts), bun x tsc --noEmit and bun scripts/design-lint.ts pass. I ran the tests this merge touches, not the full suite; 629 passed, 0 failed. They cover pull-mergeable, approval-base, OpenAPI, rotate-secret-key, settings-routes, cookie-flags, design-audit, board-grid-style, door-ssh-fingerprint, data-browser, home-kpi-row, job-slices, spawn-pipes, spawn-sites, app-home-batched and surfaces-adversarial.

To try it: curl $PREVIEW/api/v1/repos/<owner>/<name>/pulls/<n> on the preview and read checkedAgainst, checkStale and approvedAt.

🤖 Generated with Claude Code

Continues #12235

4 earlier comments · 1 event
commented

GET /api/v1/repos/:owner/:name/pulls/:number now returns checkedAgainst: the base commit the head's check was merged with. It is null until a check has recorded one. Next to checkStale, it tells a client which main the green was tested on. This means a green from an older base never reads as ready (#1133).

This push merges main (69 commits). After the merge, the branch differs from main in three files:

  • src/http/api.ts: the head's check summary is read once and used for both check and checkedAgainst.
  • src/contract/openapi/routes/getApiV1ReposOwnerNamePullsNumber.ts: the summary describes checkedAgainst, and gives approvedAt as main's {at, head, base, current}.
  • test/pull-mergeable.test.ts: asserts checkedAgainst is null before any check and names the base commit once a check records one.

What changed in the merge:

  • approvedAt: this branch had its own approvedAt, built from the head alone (src/core/approved_at.ts and its test). Main's version from #12164 also records the base and reports when either one moved, so I deleted mine and kept main's.
  • Earlier fixes for main's failures: fixes this branch carried for when main was red (secret-key rotation columns, listed spawn sites, the broker's settings routes) now come from main's own versions. Keeping this branch's copies duplicated properties, which the type check caught, and failed the rotation and settings-route tests.

How the merge-base check and the stale-check signal work is unchanged from earlier pushes on this branch.

Checks: bun scripts/gate.ts and bun x tsc --noEmit are clean. These test files pass: pull-mergeable, approval-base, openapi-pulls, openapi-drift, openapi-coverage, rotate-secret-key, settings-routes. The full suite is left to the forge's check.

Try it: curl "$FORGE_URL/api/v1/repos/trevato/forge/pulls/<n>?diff=0" and read check, checkStale, checkedAgainst and approvedAt.

🤖 Generated with Claude Code

commented

GET /api/v1/repos/{owner}/{name}/pulls/{number} now returns checkedAgainst, the base tip the head's check was run merged with (null until a check records one). Read beside checkStale, it says which main a pull's green actually covers. approvedAt is main's version from #12164 ({at, head, base, current}, where current turns false once the head or the base moves). It replaces the narrower src/core/approved_at.ts this branch had, which is deleted along with its test.

This round brought in two moves of main:

  • Conflicts: most were comments that main had wrapped to the gate's width while this branch had wrapped them differently, plus budget and test-table edits main had also made. For all of those, main's side was kept.
  • Duplicates dropped: main had fixed several reds this branch had fixed too: the rotation script's sealed columns, the spawn-site and pipe tables, job slices, and the /broker/ routes moving out of settingsRoutes. This branch's copies are gone, so those fixes now exist once.
  • What the pull still changes: src/http/api.ts, its OpenAPI route summary, and test/pull-mergeable.test.ts, which checks checkedAgainst is null before a check and names the recorded base after one.

The gate (bun scripts/gate.ts), bun x tsc --noEmit and bun scripts/design-lint.ts pass. I ran the tests this merge touches, not the full suite; 629 passed, 0 failed. They cover pull-mergeable, approval-base, OpenAPI, rotate-secret-key, settings-routes, cookie-flags, design-audit, board-grid-style, door-ssh-fingerprint, data-browser, home-kpi-row, job-slices, spawn-pipes, spawn-sites, app-home-batched and surfaces-adversarial.

To try it: curl $PREVIEW/api/v1/repos/<owner>/<name>/pulls/<n> on the preview and read checkedAgainst, checkStale and approvedAt.

🤖 Generated with Claude Code

commented

I fixed the one failure the check counted against this pull: test/stall-in-flight.test.ts ("a real request held open across a stall…"). The fix is in the test, which was wrong; the pull's code is unchanged. The other three failures are in test/first-ask-new-app.test.ts, test/retry-after.test.ts and test/deadends.test.ts. They fail the same way on main at 674862e, so the forge didn't count them against the pull, and I left them alone.

Cause. In a shard, many test files run in one process and share one set of open requests. The test checked how big that whole set was: it waited for it to grow before starting its stall timer, and checked it had shrunk back at the end.

  • Another file's request could make the set grow before the test's own /mcp request had started. The stall then reached back before /mcp began, so /mcp wasn't counted as running during it.
  • For the same reason, the closing check fails if any other request is still open when the test ends.

Running alone, the test passes every time. In a busy shard it failed now and then.

Change. Both checks now look only for the test's own /mcp: it waits until /mcp appears in inFlight.since(now) before timing the stall, and checks that /mcp is gone afterwards. What the test verifies is the same.

Checks run. bun test test/stall-in-flight.test.ts passed several times in a row (10/10 each run), and bun scripts/gate.ts and bun x tsc --noEmit are clean. The forge's check is the real test, since the failure only showed up under shard load.

🤖 Generated with Claude Code

Changes requested — before the latest push

The API change is small and correct, and the test covers null and the recorded base. One change is needed.

  1. checkedAgainst is only on the REST pull read. checkStale, the field it sits beside, is also in the MCP pull tool (src/http/mcp/tools/pull.ts), the terminal screen (src/tui/screens/pull.ts) and the web page (src/web/pages/pull.ts). An agent or a person in the terminal can see that a green is stale but not which base it was tested on. Surface checkedAgainst in the MCP pull tool and on the TUI pull screen, and show it on the web page next to the stale marker. Add a test for each that fails without it. Both doors must show it, and the MCP tool is a third.
  2. Add a short how-to for the field in docs/features/ (the existing pull or checks doc). Do not put it in README.md.
  3. I could not check the preview. GET /api/v1/repos/trevato/forge/pulls/12235 returned not_found there, since the preview app has no such repo. Show it working in the pull description with a pull that has a recorded check.

The stall-in-flight test change is a reasonable de-flake. The first two checks above are required, because without them a stale green still reads as ready in two of the four doors.

commented

A pull's read now names the base tip its check was merged with, as checkedAgainst, beside checkStale. It does this in all four places a pull is read: the REST API, the MCP pull tool, the web page and the terminal. A stale green can never pass for a ready one.

What changed

  • REST, GET …/pulls/:number (src/http/api.ts): adds checkedAgainst, the full base oid the head's check merged with, or null before a check recorded one. It also adds approvedAt, the standing approval with its head and base, and documents both in the OpenAPI route.
  • MCP pull tool (src/http/mcp/tools/pull.ts): adds the same checkedAgainst field beside checkStale, and the tool's doc names both.
  • Web page and terminal: these already printed checked against main at abc1234 next to the "Checked against an older main" marker; that came in with #1942. Nothing pinned it, so test/check-stale-gate.test.ts now expects the stale green's recorded sha on both. These two are regression guards: they would fail only if that text were removed, because the behaviour already existed before this pull.
  • Docs: docs/features/pull-verdict.md has a new section, "Which base the green was checked on", covering all four places and how to compare checkedAgainst with the base's tip.
  • Test fix: test/stall-in-flight.test.ts was flaky when sharded. It waited for the shared process's whole set of open requests to grow or shrink, so another file's request could stand in for its own. It now waits on its own /mcp request. The test was wrong; the code under test is unchanged.

Tests

  • test/pull-mergeable.test.ts covers checkedAgainst as null with no check, and as the recorded base once main has moved, over both REST and MCP.
  • The MCP assertion fails with the tool change reverted.
  • The targeted tests pass (pull, stale-gate, MCP tool and size, docs, features), and so do bun scripts/gate.ts and bun x tsc --noEmit.
  • scripts/fast.ts named 2039 files (about 172 minutes), so I picked the covering tests by hand; the forge's check runs the rest.
  • Three failures in the previous check are also red on main and aren't touched here: first-ask-new-app, retry-after and deadends.

Seen on the preview The preview has no pull with a recorded check: both sample pulls report check: none, and a check run can't be recorded through the API there. So the preview shows only the null case, on GET /api/v1/repos/preview-admin/sample/pulls/3?diff=0:

{'number': 3, 'check': 'none', 'checkStale': False, 'checkedAgainst': None, 'approvedAt': None}

The non-null case, with a recorded base on a stale green, is shown by the REST and MCP assertions in test/pull-mergeable.test.ts and the page and terminal assertions in test/check-stale-gate.test.ts.

To try it: open a pull whose green predates a push to main. The checks line reads "Checked against an older main · checked against main at <sha7>", and GET …/pulls/N and MCP pull return "checkStale": true, "checkedAgainst": "<that oid>".

🤖 Generated with Claude Code

commented

A pull's green check now records which base commit it ran against (checkedAgainst). It appears next to checkStale, so a stale green names the base it was checked on. The web page, the terminal and the MCP pull tool all show it. There is also a how-to in docs/features/pull-verdict.md.

This push fixes the one check failure that came from this pull, test/mcp-table.test.ts. Adding checkedAgainst to the pull tool changed its description, but I hadn't updated the tool table in docs/MCP.md. I regenerated it with bun scripts/mcp-table.ts, and only the pull row changed. That test file now passes, along with bun scripts/gate.ts and bun x tsc --noEmit.

The other failures in the check (test/retry-after.test.ts, test/first-ask-new-app.test.ts, test/deadends.test.ts, and the parent-forge MCP proposal tests in the same shard) fail on main at a67717b in the same way. The forge's log says they are not counted against this pull, and I left them alone.

To try it: open a pull whose green check ran before main moved. The page, the terminal and the MCP pull tool should each name the base commit the check ran against.

🤖 Generated with Claude Code

Approved

Approved. Changes since 3dc792c072a8: checkedAgainst is now on REST and the MCP pull tool (null before a check recorded a base, the full oid after), with the OpenAPI summary, the MCP doc and the docs/MCP.md row updated to match. The how-to is in docs/features/pull-verdict.md, not README. Tests cover null and recorded base over both REST and MCP, and the page and terminal assertions pin the stale green's sha. No new input, write path or secret, so nothing to raise on security. The stall-in-flight change narrows the test to its own /mcp request instead of the shared set size, which removes a shard flake and matches the direction on tests that never flake. Non-blocking: the page and terminal assertions are regression guards, since that behaviour already existed.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs