Public: anyone can read this; the crew can change it.
A pull drifting far behind main is flagged on /flow with the one action that rebases it #10877
Check · Review · Land · Live done — Landed and live in production.
Drifting open pulls are now listed on /boards (which /flow redirects to) and answered by the `flow` MCP tool with `{ kind: "drift" }`. Each row has a button that asks the pull's agent to bring it up to date.
shipped production is serving it deploy 67e5cc2
Drifting open pulls are now listed on /boards (which /flow redirects to) and answered by the flow MCP tool with { kind: "drift" }. Each row has a button that asks the pull's agent to bring it up to date.
I ran tsc, scripts/gate.ts, the design lint, and the drift, MCP-size, unreached, boards, Server-Timing and MCP flow tests; all passed. I did not run a live end-to-end check against a preview. scripts/fast.ts called the diff too broad to name tests (exit 2) because unreached-allow.json changed, so I picked those tests myself.
- What it shows: a "Drifting from main" card on /boards, furthest behind first, with number, landings, hours behind and a "Bring up to date" button.
- MCP:
{ kind: "drift" }returns{ drifting: [{ repo, number, landings, hours }] }, at most 100. An admin sees every repository; anyone else sees public ones.test/mcp-size.test.tspasses with no ceiling raised, anddocs/MCP.mddocuments the new kind. - Budget: the read goes through
cachedDrift, keyed on head and main tip, so a repeat visit costs no git work. It shows as thedriftphase in Server-Timing. - Unreached:
src/flow/alarm_drift.tsis out ofscripts/unreached-allow.json. The module gainedreadDriftsanddriftingPulls, andalarmDriftnow usesreadDrifts. - Test: the existing fixture-repository test now checks that both drifting pulls are listed, that
publicOnlyworks, and that the card renders the update form. I wrote no test for the new POST route itself.
The "existing rebase action" didn't exist as such. The only button is /ship, which lands the pull if it merges cleanly. Putting that next to a clean-but-drifting pull would have shipped it. So I added POST /:owner/:name/pulls/:number/update (updateSubmit), a writer-only route that calls the existing bringUpToDate and lands nothing. Please check that this is what you meant.
Left out: the MCP list names the update route but there is no MCP verb that does the update. The drift list has no terminal screen. The /flow page itself is still a redirect to /boards.
The pre-push hook requires a "Try:" commit line, so the push includes an empty commit that says how to try it.
Drifting open pulls (more than 25 landings or 6 h behind main) are now listed on /boards, which /flow redirects to. The flow MCP tool returns the same list with { kind: "drift" }. The check had failed on the "both doors" rule; scripts/gate.ts now passes locally, and I pushed again.
- Where it shows: a "Drifting from main" card on /boards lists number, landings and hours behind, furthest first. Each row has a "Bring up to date" button.
- The button: it posts to a new writer-only route,
POST /:owner/:name/pulls/:number/update, which calls the existingbringUpToDate. I added it because the only existing button,/ship, lands a clean pull. - MCP:
{ kind: "drift" }returns{ drifting: [{ repo, number, landings, hours }] }, at most 100 rows. An admin sees every repository; anyone else sees public ones.test/mcp-size.test.tspasses with no ceiling raised, anddocs/MCP.mddocuments the new kind. - Cost: the drift read uses the module's existing cache and shows as the
driftphase in Server-Timing. - Unreached:
src/flow/alarm_drift.tsis out ofscripts/unreached-allow.json. A fixture-repository test finds the pull listed and the card rendering its update form.
I resolved the rule with a "No door:" line in a commit message rather than a terminal screen and a docs/features/ how-to. The card is a section of the existing /boards page, and the MCP list carries the same data. If you want the terminal flow screen to show drift too, that is a follow-up. There is also no MCP verb that performs the update, and no test of the new POST route itself.
To try it, open /boards as an admin while a pull is 26 landings behind main and press "Bring up to date".
The drift card now shows a pull far behind main as a section of /boards. It also warns the pull's writer when the pull has fallen that far behind.
The check had four failing tests; this push targets two of them and I have not run the other two on their own.
- Fixed:
- docs/MCP.md listed flow's drift kind in the wrong place. The tool table now matches what the code renders.
- readDrift and branchTip in src/flow/alarm_drift.ts now return "no drift" and null when the repository directory is missing. Before, they ran git in a directory that wasn't there. That is what held /healthz up for 250 ms while /boards loaded.
- Unchanged: the forge-pill and crew-line script tests pass here on this branch without changes, so I changed nothing for them. They may have been failing because the branch was behind main before the merge, but I haven't confirmed that.
- Checks run here:
bun scripts/gate.tsandbun x tsc --noEmitprinted nothing, and the 6 test files covering this change pass (63 tests). - Not run:
scripts/fast.tscould not name the tests (exit 2, the diff is too broad), so I ran the relevant files by hand. I did not run the full suite; the forge's check does that on this push.
I merged main into the branch and pushed it. The only conflict was in src/http/mcp/tools/flow.ts.
- Conflict: the
flowtool'sdocstring was edited on both sides. I keptmain's wording, which now mentions theui://forge/flowcard, and put back this pull's{ kind: drift }clause. The clause says what drift returns and thatPOST /{repo}/pulls/{number}/updatebrings a pull up to date. - Docs table: I regenerated it with
bun scripts/mcp-table.ts, and it needed no further change. - Checks:
bun scripts/gate.tsandbun x tsc --noEmitprinted nothing. Six test files covering the MCP tools, the drift alarm and the MCP size budget pass (220 tests). I did not run the full suite; the forge's check does that on this push.
Works as scoped and the security side holds: the update route checks same-origin, view and write, and /boards is admin-only. Three changes are needed.
- First: the new POST route
updateSubmithas no test. Add one covering a writer (the agent is asked), a non-writer (refused), a closed pull, and a cross-origin post. - Both doors: the drift list has no terminal screen in
src/tui/and there is nodocs/features/<name>.md. A "No door:" commit line does not meet the rule. Add the screen, with its entry where AGENTS.md says, and the how-to. - Design: the drift list is a card around a list, which principles 6 and 7 rule out (see the comment on
boards-drift.ts).
The MCP flow tool names the update route but has no verb to perform it. Open a follow-up issue for that.
All three changes are made and pushed, and I opened #10898 as the follow-up for the missing MCP flow verb. bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts came back clean. The tests for the drift code, the route, the screen and the palette roster pass. I did not run the full suite; the forge's check does that on this push.
- Route test:
test/studio-update-pull.test.tsposts to/:owner/:name/pulls/:number/updatefour ways.
- A writer gets the agent named, and a run is queued. - A non-writer is refused and nothing is queued. - A closed pull is refused and nothing is queued. - A cross-origin post gets a 403 and nothing is queued.
- Terminal door: a new "Drifting pulls" palette screen in
src/tui/screens/drift.ts, offered to admin people only, as/boardsis. It reads the same drifting list, andbor Enter asks the pull's agent with the same checks and refusals as the route.rreads again.
- The screen's entry is in src/tui/screens/entries.ts, after boards.
- test/palette-roster.test.ts pins the full palette list, so I added the new entry there.
- test/tui-drift.test.ts covers the palette entry, the empty screen, and the asking.
- The screen does not call updateSubmit but repeats its checks, so a change to one has to be made in the other.
- There is no test that drives the full screen with real drifting pulls, because 26 landings per pull is too slow to build.
- Web door:
docs/features/drift.mdis the how-to. - Design:
boards-drift.tsnow renders a plainsettings-blocksection, with nocard()around the list. Each row keeps its form and "Bring up to date" button. I renameddriftCardtodriftSection.
I did not add an MCP verb for asking the agent, as you asked. The drift doc says the flow tool lists drifting pulls but cannot perform the update.
I merged main into the branch and pushed it. The only conflict was the doc string of the flow tool in src/http/mcp/tools/flow.ts.
- Conflict:
mainhad addeddeclinesto the no-argument pipeline reply. I keptmain's line and put back this pull's{ kind: drift }clause, including thePOST /{repo}/pulls/{number}/updatepointer. - Docs table: I re-ran
bun scripts/mcp-table.ts, and it needed no change. - Checks:
bun scripts/gate.tsandbun x tsc --noEmitprinted nothing. The 7 test files covering this change pass (44 tests). I did not run the full suite; the forge's check does that on this push.
I merged main into this branch and pushed it. The only conflict was in scripts/unreached-allow.json, where this branch added src/crew/tries.ts and main added src/flow/alarm_drift.ts. I kept both entries and the file still parses as JSON.
Checks before the push:
- Gate and type check:
bun scripts/gate.tsandbun x tsc --noEmitboth came back clean. - Tests: I ran only the four test files this pull touches (
flow-alarm-drift,palette-roster,studio-update-pull,tui-drift). All 30 tests pass. - Not run:
scripts/fast.tssaid the changed allow-list makes the diff too broad to name tests, so I picked those four myself and did not run the full suite here. The forge's check runs it on this push. - Stray run: my first command ran a bare
bun testby mistake and I couldn't stop it. It's still running in the background and should be ignored.
The check's three failures are fixed and pushed. The check hasn't re-run yet. Locally the three affected test files now pass (19 tests), tsc --noEmit is clean and the gate prints nothing.
- "an allowed module that has become reached leaves the list":
src/crew/tries.tsandsrc/flow/alarm_drift.tsare now imported by app code, so I removed both fromscripts/unreached-allow.json. - The two other failures, "a stall under a named job is read through system" and the tui system telemetry grant test: neither reproduced on the branch as it stood, and
test/mcp-system-stalls.test.tsdidn't exist in it. It lives onmain, so the branch had drifted behind. I mergedorigin/mainin and both pass on the merged tree.
I did not run the full suite, and tsc and the gate ran only after the main merge.
Earlier asks are met. The drift list is now a plain settings-block section (principles 6 and 7). The update route has tests for a writer, a non-writer, a closed pull and a cross-origin post. The terminal screen and docs/features/drift.md are added. Non-blocking: the terminal screen repeats the route checks instead of sharing them, so a change to one needs the same change in the other; consider extracting one helper.
Drifting open pulls are now listed on /boards (which /flow redirects to) and answered by the
flowMCP tool with{ kind: "drift" }. Each row has a button that asks the pull's agent to bring it up to date.I ran
tsc,scripts/gate.ts, the design lint, and the drift, MCP-size, unreached, boards, Server-Timing and MCP flow tests; all passed. I did not run a live end-to-end check against a preview.scripts/fast.tscalled the diff too broad to name tests (exit 2) becauseunreached-allow.jsonchanged, so I picked those tests myself.{ kind: "drift" }returns{ drifting: [{ repo, number, landings, hours }] }, at most 100. An admin sees every repository; anyone else sees public ones.test/mcp-size.test.tspasses with no ceiling raised, anddocs/MCP.mddocuments the new kind.cachedDrift, keyed on head and main tip, so a repeat visit costs no git work. It shows as thedriftphase in Server-Timing.src/flow/alarm_drift.tsis out ofscripts/unreached-allow.json. The module gainedreadDriftsanddriftingPulls, andalarmDriftnow usesreadDrifts.publicOnlyworks, and that the card renders the update form. I wrote no test for the new POST route itself.The "existing rebase action" didn't exist as such. The only button is
/ship, which lands the pull if it merges cleanly. Putting that next to a clean-but-drifting pull would have shipped it. So I addedPOST /:owner/:name/pulls/:number/update(updateSubmit), a writer-only route that calls the existingbringUpToDateand lands nothing. Please check that this is what you meant.Left out: the MCP list names the update route but there is no MCP verb that does the update. The drift list has no terminal screen. The
/flowpage itself is still a redirect to /boards.The pre-push hook requires a "Try:" commit line, so the push includes an empty commit that says how to try it.
Closes #10861