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

Merged opened this in answer to

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

Merged

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

opened this

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, converge and the follow pages and API (src/actions/upstream_sync.ts) use the repo_upstream follow 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_forge now forks the origin, pushes main as forge/<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-sync covers 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 main takes main in 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.

Closes #12108

8 earlier comments · 3 events
commented

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, converge and the follow pages and API (src/actions/upstream_sync.ts) use the repo_upstream follow 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_forge now forks the origin, pushes main as forge/<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-sync covers 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 main takes main in 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.

commented

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: mergePull skips 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.ts and test/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

commented

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-lint also fails on a clean origin/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/main and 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.ts and bun x tsc --noEmit are 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.ts named 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.

the crew's run was interrupted: restart, resumes on the next pass — cancelled
commented

Parked by the crew: the check stayed red through 2 automatic tries

commented

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. main added apart: pull.sealed ? "sealed" : undefined. This branch added the overlap words to the row's meta. I kept both.
  • Checks: bun x tsc --noEmit and bun scripts/gate.ts are clean. I ran five test files that cover the changed code, and all 40 tests pass: overlap, studio-changes, keys-pulls-list, sealed-private-work and pulls-list-timing.
  • Not run: scripts/fast.ts named 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 — before the latest push

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.

  1. 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 mergePull and 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.
  2. Untested behaviour. proposeToFollowed and the MCP and API propose branches have no test, which the pull itself admits. overlapsOf and 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.
  3. Both doors. The page gains Take, Converge and Offer buttons, but there is no src/tui/ screen and no docs/features/<name>.md how-to.
  4. 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.
  5. Security and robustness. refreshOpenPulls writes 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.
  6. Clean. followed! non-null assertion in following.ts. as any in test/pull-refresh.test.ts. The .catch(() => []) calls swallow errors silently.
commented

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.ts has takeFromFollowed and convergeFollowed. 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 MCP converge tool 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=take and do=converge on a followed repo, and a repo that follows nothing gets "follows nothing".
  • The terminal door: on the forks screen of a followed repository, t offers to take after a confirm. The how-to is docs/features/follow-an-origin.md.
  • The followed! assertion is gone. The silent .catch(() => []) calls went with the refresh. as any was in pull-refresh.test.ts, which is deleted.

Taken out

  • The refresh of every open pull. This removes src/actions/pull_refresh.ts, its test, the mergePull hook and the three test edits made to fit it. pulls.ts, issues-words, pulls-list-timing and studio-words match main again.
  • The overlap mark: pull_overlap.ts, git/touched.ts and 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.
commented

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.

commented

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.

Changes requested — before the latest push

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:

  1. 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, using convergeFollowed and convergeWords, and put it in the how-to.
  1. Untested behaviour. checkTarget in src/actions/upstream_follow.ts now calls settleOntoFollowed on 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 for checkUpstream with forkFollowMode set to take-updates and one with it unset. The behaviour should also be in the docs: a check can move main even when nobody pressed Take.
  1. Untested paths. upstream-sync.test.ts has no test for the queued or working outcomes of convergeFollowed, nor for forbidden or not_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.

commented

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 a c converge key next to t for a followed repository. It is offered to a person who writes the repository, asks for a confirm first, calls convergeFollowed, and shows convergeWords in a message, like the page. The how-to (docs/features/follow-an-origin.md) names both keys.
  • A check moves main: new tests in test/upstream-sync.test.ts cover checkUpstream:

- 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

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs