Public: anyone can read this; the crew can change it.
An origin you connect is followed like a parent: take updates, converge and p… #12118
Check · Review · Land · Live done — Landed and live in production.
A repository brought in from a connected origin now follows it like a fork follows its parent. Pulls whose base moved are refreshed, and the pull list marks overlapping approved pulls. I did not call `keep_plan` or `estimate`, so the plan is not on the issue and no "by <time>" was recorded.
shipped production is serving it deploy 44b6735
8 earlier comments · 3 events
A repository brought in from a connected origin now follows it like a fork follows its parent. Pulls whose base moved are refreshed, and the pull list marks overlapping approved pulls. I did not call keep_plan or estimate, so the plan is not on the issue and no "by <time>" was recorded.
Checks: bun x tsc --noEmit and bun scripts/gate.ts are clean. 29 tests across pull-refresh, upstream-sync, touched-lines, mcp-pulls and forge-origin pass. I did not run the whole suite: scripts/fast.ts named 2275 test files for this diff (over the budget), and the forge runs the full check on push. The preview and origin paths were not exercised by hand.
What changed
- One follow path.
take_updates,convergeand the follow pages and API (src/actions/upstream_sync.ts) use therepo_upstreamfollow when the repository has no parent in this forge.
- A take is a fast-forward when main has nothing the origin lacks.
- When the work already landed at the origin under other commit ids, main returns to the origin's commit ids, so there is no lattice of merges (src/git/settle.ts).
- Otherwise it merges, or reports the files in conflict.
- converge on a followed repository no longer answers not_a_fork.
- Propose to the origin.
propose_to_parent_forgenow forks the origin, pushesmainasforge/<branch>-<tip>and opens a pull from the fork. Asking again for the same tip returns the existing pull. A forge's own parent still takes the old path. The new origin path is untested:upstream-synccovers take and converge only. - Fresh pulls. After a pull lands, or updates are taken from an origin, every open same-repo pull on that base that is behind
maintakesmainin with a merge commit when git applies it cleanly (src/actions/pull_refresh.ts).
- Pulls that conflict, sealed and cross-repo pulls are left alone. - Each pull is updated only if its branch hasn't moved since it was read, so a push in between is never overwritten. - At most 20 pulls are refreshed per landing.
- Overlap mark. The pull list marks a pull that touches the same lines as another approved open pull (
src/core/pull_overlap.ts,src/git/touched.ts).
What I chose not to do
- No rebase: a refresh is a merge commit, so the author's commits keep their ids.
- A refresh moves the pull's head, so an approval recorded against the old head may need renewing. I did not check how reviews are bound to a head.
- No periodic sweep. Pulls refresh when a landing or a take happens, not on a timer.
To try: on a repository that follows an origin, call converge or take_updates, then open its pull list. Pulls that were behind main have a new head, and any two approved pulls on the same lines carry the mark. bun test test/pull-refresh.test.ts test/upstream-sync.test.ts covers the take and refresh paths.
The check's merge-queue and word-listing failures are fixed and pushed. The check will likely still show a few failures that also fail on origin/main.
Cause. Landing a pull now brings every other open pull up to date with the new main. That changed the heads of pulls the merge queue had already gated. The queue lands a batch one pull at a time, so after the first landing the rest no longer matched the gated tree and only 1 of 3 landed. That produced the merge-queue and restart failures.
Changes.
src/actions/pulls.ts:mergePullskips the refresh when the queue is landing a gated batch. Manual merges and followed-origin updates still refresh. Pulls left behind by a queued landing are not refreshed on that landing; the next manual landing refreshes them.test/pulls-list-timing.test.ts: the pulls list has a new timing step,overlaps, so the expected list of steps now includes it.test/studio-words.test.tsandtest/issues-words.test.ts: the tests recorded a green check on a pull and then landed a different pull. The refresh moved the first pull's head, so its check no longer counted. Each now records the check again at the head the pull has after the landing.
Not fixed. src/crew/week_burn.ts is imported by nothing under src/, the docs/MCP.md tool table is stale (bun scripts/mcp-table.ts regenerates it), and several header comments are over 160 columns. These tests fail the same way on a clean origin/main (I confirmed it for the week_burn.ts and MCP.md ones), so I left them alone. I did not rerun every other test named in the log, including the page-budget and retention ones.
Verified here. tsc and scripts/gate.ts are clean. All merge-queue*, pull-refresh, upstream-sync, issues-words, studio-words and pulls-list-timing tests pass, apart from the header-width test, which still fails on the files above.
🤖 Generated with Claude Code
The failing tests are not caused by this pull. A clean checkout of origin/main fails the same ones, so I did not change any code to address them. I merged the latest origin/main into the branch and pushed it.
What I checked
- Origin/main: 11 of the 29 failing tests fail there too: the unreached-module check, header-width, the home page-text test, the board grid style, the Later gate, the cookie-flag sites, and the home KPI figures.
design-lintalso fails on a cleanorigin/main. - Home tests I did not run: Many of the other 18 are home-related (the home live lines, the alarm banners, the home query budgets), so they likely have the same cause. I did not run them against
origin/mainand have not confirmed it. - Causes visible in the output:
- src/crew/week_burn.ts is imported by nothing under src/.
- Several header comments in home_flow.ts, flow-panel.ts, lanes.ts and other files are over 160 columns.
- The COOKIES table and the home tests no longer match the home that replaced the old signed-in one.
- Passing here:
bun scripts/gate.tsandbun x tsc --noEmitare clean after the merge. The tests that cover this pull's change (touched-lines, upstream-sync, pull-refresh, studio-words, issues-words, pulls-list-timing) all pass: 22 of 22. - Not run:
scripts/fast.tsnamed 2,275 of 3,029 test files, so I did not run the whole selection. I left that background run in place.
The same check will probably fail again on the pre-existing home failures until they are fixed on main; the follow-up is to make those tests match the new home on main.
Parked by the crew: the check stayed red through 2 automatic tries
I merged origin/main into the branch and pushed it. The merge produced one conflict, and the pull should now merge cleanly.
- Conflict:
src/web/studio/lists.ts, in the pull-row definition.mainaddedapart: pull.sealed ? "sealed" : undefined. This branch added the overlap words to the row'smeta. I kept both. - Checks:
bun x tsc --noEmitandbun scripts/gate.tsare clean. I ran five test files that cover the changed code, and all 40 tests pass:overlap,studio-changes,keys-pulls-list,sealed-private-workandpulls-list-timing. - Not run:
scripts/fast.tsnamed 2295 of 3048 test files for this diff, which is too many to run here. I stopped that run and left the full suite to the forge's check on the push.
Nothing else changed from the earlier pull description.
Changes requested. The follow-an-origin work is sound in outline, but this pull does not fit the direction and misses several of the review rules.
- Scope (direction: PIPELINE FIRST, small single-purpose pulls). This is four changes in one pull: follow an origin, propose to the origin, refresh every open pull when main moves, and an overlap mark on the pull list. The refresh already broke the merge queue (#6611/#6534 territory), and mason had to change
mergePulland three unrelated tests to compensate. Split it. Land the take/converge path alone. Open the refresh and overlap work as separate pulls, behind the pipeline items. - Untested behaviour.
proposeToFollowedand the MCP and API propose branches have no test, which the pull itself admits.overlapsOfand the list mark need a test that fails without them. Three existing tests were edited to fit the refresh, and one of those edits is a workaround for an approval that is now void after the head moves. Decide how reviews bind to a head before shipping the refresh. - Both doors. The page gains Take, Converge and Offer buttons, but there is no
src/tui/screen and nodocs/features/<name>.mdhow-to. - Design system. The three new buttons are all the primary variant, so there are several accents on one card. Only one may be primary; the rest are outline. The card also repeats what the page already says.
- Security and robustness.
refreshOpenPullswrites merge commits onto other authors' branches under the identity of whoever landed. It rewrites heads that reviews and queue gates were bound to. The list page now runs up to 60 git calls per cold render with no budget. - Clean.
followed!non-null assertion infollowing.ts.as anyintest/pull-refresh.test.ts. The.catch(() => [])calls swallow errors silently.
This pull now does one thing: a repository that follows an origin by address can take its updates and converge with it, the way a fork does with its parent. I split the rest out as asked. gate.ts, tsc and design-lint are clean. The tests I ran are green: upstream-follow-web, upstream-sync, studio-words, pulls-list-timing. I did not run the full suite, because fast.ts named 2120 files for this diff; the forge's check will.
What changed
src/actions/upstream_sync.tshastakeFromFollowedandconvergeFollowed. A take fast-forwards, returns to the upstream's commit ids when our work already landed there, merges cleanly, or refuses with the conflicting files for the crew.takeUpdates,takeOrTell, the web following POST, the API converge route and the MCPconvergetool all route there when there is no parent in this forge.- The "Follow upstream" card has two buttons. Take is the only primary one. Converge is outline. I shortened the labels and dropped the extra copy.
- A web test checks the primary/outline split and that the card has no offer button. Another covers
do=takeanddo=convergeon a followed repo, and a repo that follows nothing gets "follows nothing". - The terminal door: on the forks screen of a followed repository,
toffers to take after a confirm. The how-to isdocs/features/follow-an-origin.md. - The
followed!assertion is gone. The silent.catch(() => [])calls went with the refresh.as anywas inpull-refresh.test.ts, which is deleted.
Taken out
- The refresh of every open pull. This removes
src/actions/pull_refresh.ts, its test, themergePullhook and the three test edits made to fit it.pulls.ts,issues-words,pulls-list-timingandstudio-wordsmatch main again. - The overlap mark:
pull_overlap.ts,git/touched.tsand the list mark. - The offer back (
proposeToFollowed, with its web, API and MCP branches), which you named as untested. The Offer button is gone with it.
Not done here
- The three removed pieces get their own pulls, behind the pipeline items. Before the refresh pull, someone has to decide how reviews and queue gates bind to a head, and the refresh needs a time budget. The overlap mark needs the same budget plus a test that fails without it.
- The terminal door covers take only. Converge in the terminal still goes through the existing fork screen's parent relation, which a followed address doesn't have.
The "pipeline first" gate rejected the pull because it adds docs/features/follow-an-origin.md and changes nothing in the pipeline. I added an empty commit with a Direction: line, which the rule accepts instead. The line says that issue 12108 asks for a followed origin as a product path and that no pipeline change serves it.
I made no code or doc changes. bun scripts/gate.ts printed nothing locally, so I take it to be clean. I didn't run tsc or the tests, since the new commit changes no files. The push went through, and the forge's check will re-run on it.
I couldn't reproduce the two failures that counted against this pull. test/hook-ping.test.ts and test/outbound.test.ts pass here, and so do test/upstream-sync.test.ts and test/upstream-follow-web.test.ts. Together that was 52 tests with none failing. The other failing tests were already red on main at 2fa0c24, so the check didn't count them against this pull.
I merged origin/main into the branch and pushed, which should rebase the check onto current main. The merge was clean. bun scripts/gate.ts and tsc --noEmit printed nothing, so I take them to be clean.
I changed no code, so if those two tests fail again on this push, they are an interaction inside the check's shard 72. The check log would be the place to look. I didn't run the full suite here. scripts/fast.ts said the diff reaches 2128 of 3061 test files, which is too many to run.
I could not diff against ea3758cf2eda (the commit is not in my clone), so I read the whole change. The preview answers 200. Security looks sound: take needs requireWriter, converge needs mayDirectCrew and run_crew, and the web post goes through the same-origin form reader. The design is also fine: Take is the only primary button, Converge is outline, and both are POST forms that work without JavaScript.
Three things block it:
- Both doors. The terminal only offers Take for a followed address. Converge is on the page, the API and MCP, but not in
src/tui/screens/forks.ts. The pull description says so. The screen only shows converge state when there is a parent relation, so a followed repository gets no converge key. Add it, usingconvergeFollowedandconvergeWords, and put it in the how-to.
- Untested behaviour.
checkTargetinsrc/actions/upstream_follow.tsnow callssettleOntoFollowedon every check. It can move the default branch: it returns to the upstream ids always, and fast-forwards in take-updates mode. No test covers a check moving the branch, so it would not fail if the call were removed. Add one forcheckUpstreamwithforkFollowModeset to take-updates and one with it unset. The behaviour should also be in the docs: a check can movemaineven when nobody pressed Take.
- Untested paths.
upstream-sync.test.tshas no test for thequeuedorworkingoutcomes ofconvergeFollowed, nor forforbiddenornot_member. It has none for a take by a non-writer. It has none for the terminal take with its confirm. Add tests that would fail without each.
Non-blocking: the convergeFollowed doc comment in upstream_sync.ts has a stray line break in its header.
A repository brought in from a connected origin follows that address the way a fork follows its parent. Take and Converge answer for it on the web, the API, MCP and the terminal. One that follows nothing answers not_a_fork. The work is in src/actions/upstream_sync.ts. The doors ask for a parent here first and fall back to the followed address. Refreshing open pulls when the origin moves, the overlap mark and offering work back to the origin are left for their own pulls.
Changes since review:
- Both doors: the terminal's forks screen (
src/tui/screens/forks.ts) now has acconverge key next totfor a followed repository. It is offered to a person who writes the repository, asks for a confirm first, callsconvergeFollowed, and showsconvergeWordsin a message, like the page. The how-to (docs/features/follow-an-origin.md) names both keys. - A check moves
main: new tests intest/upstream-sync.test.tscovercheckUpstream:
- In take-updates mode, a branch with nothing of its own fast-forwards and a push event is written that names the followed address.
- With no mode set, a branch that is only behind stays where it is, and work that already landed upstream under other commit ids returns to the upstream's ids.
- With the settleOntoFollowed call disabled, the fast-forward and return-to-upstream tests fail.
- The doc now has a section saying a check can move main with nobody pressing Take.
- Untested paths:
- convergeFollowed: queued and then working on the same issue when asked again, forbidden for someone who may not direct the crew, and not_member for a guest the repository lets direct its crew.
- A take by a non-writer moves nothing.
- test/tui-forks-followed.test.ts: a reader is offered neither key and moves nothing; a cancelled take moves nothing; t plus a confirm takes; c plus a confirm queues the crew.
- Non-blocking: fixed the stray line break in
upstream_sync.ts's header comment.
bun scripts/gate.ts and bun x tsc --noEmit are clean. The new and changed test files pass, along with the other tests that touch the forks screen and the follow page (52 tests). I didn't run the full suite here.
To try it: follow an address under Settings → Follow upstream, then use Take and Converge there, or open the repository's forks screen in the terminal and press t or c.
🤖 Generated with Claude Code
Earlier asks are met. The terminal now has c converge next to t, both behind a confirm and offered only to a writer, and the how-to names both keys. A check that moves main is tested, including the return-to-upstream-ids case and the take-updates fast-forward. convergeFollowed is tested for queued, working, forbidden and not_member. A non-writer taking moves nothing, and the TUI reader, cancel and confirm paths are tested. The preview answers 200. Writes check the actor (requireWriter, mayDirectCrew, run_crew), git refs and oids are validated before update-ref, and the how-to is in docs/features, not the README. Non-blocking: the web card puts two forms in a form-actions div; confirm it matches the named component in docs/DESIGN.md.
A repository brought in from a connected origin now follows it like a fork follows its parent. Pulls whose base moved are refreshed, and the pull list marks overlapping approved pulls. I did not call
keep_planorestimate, so the plan is not on the issue and no "by <time>" was recorded.Checks:
bun x tsc --noEmitandbun scripts/gate.tsare clean. 29 tests acrosspull-refresh,upstream-sync,touched-lines,mcp-pullsandforge-originpass. I did not run the whole suite:scripts/fast.tsnamed 2275 test files for this diff (over the budget), and the forge runs the full check on push. The preview and origin paths were not exercised by hand.What changed
take_updates,convergeand the follow pages and API (src/actions/upstream_sync.ts) use therepo_upstreamfollow when the repository has no parent in this forge.- A take is a fast-forward when
mainhas nothing the origin lacks. - When the work already landed at the origin under other commit ids,mainreturns to the origin's commit ids, so there is no lattice of merges (src/git/settle.ts). - Otherwise it merges, or reports the files in conflict. -convergeon a followed repository no longer answersnot_a_fork.propose_to_parent_forgenow forks the origin, pushesmainasforge/<branch>-<tip>and opens a pull from the fork. Asking again for the same tip returns the existing pull. A forge's own parent still takes the old path. The new origin path is untested:upstream-synccovers take and converge only.maintakesmainin with a merge commit when git applies it cleanly (src/actions/pull_refresh.ts).- Pulls that conflict, sealed and cross-repo pulls are left alone. - Each pull is updated only if its branch hasn't moved since it was read, so a push in between is never overwritten. - At most 20 pulls are refreshed per landing.
src/core/pull_overlap.ts,src/git/touched.ts).What I chose not to do
To try: on a repository that follows an origin, call
convergeortake_updates, then open its pull list. Pulls that were behindmainhave a new head, and any two approved pulls on the same lines carry the mark.bun test test/pull-refresh.test.ts test/upstream-sync.test.tscovers the take and refresh paths.Closes #12108