Skip to content

fix(init): retire side worktrees before replacing repo - #1327

Merged
SaulMoro merged 10 commits into
Tencent:mainfrom
XY-code1:fix/init-replace-worktrees
Oct 24, 2026
Merged

SaulMoro merged 10 commits into
Tencent:mainfrom
XY-code1:fix/init-replace-worktrees

Conversation

@XY-code1

@XY-code1 XY-code1 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Retire the old team repository's teamai-reports and teamai-learnings worktrees before init replaces its clone. This prevents the new clone from inheriting orphaned sibling checkouts that block member registration and later pulls.

The replacement path now:

  • settles the previous install's queue and config immediately before the old clone is removed or relocated;
  • operates only on side checkouts registered to the clone being replaced, leaving stale or foreign paths to the existing [bug] self mode: second worktree cannot publish learnings or reports, and loses its queue #808 handling;
  • acquires both existing per-branch writer locks and holds them through clone removal or relocation;
  • preserves dirty checkouts by moving their backing clone aside and repairing their worktree pointers;
  • fetches the remote branch before deciding whether a clean checkout needs to be pushed;
  • removes behind-origin checkouts when all local commits are already preserved remotely;
  • pushes clean local-only commits and refuses diverged histories for manual reconciliation.

Both --force and the interactive confirmation flow use the same replacement logic.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature
  • Breaking change
  • Documentation only
  • Refactor / internal cleanup

Test Plan

Verification at d8f810ed:

  • npx vitest run src/__tests__/branch-worktree-retire.test.ts — 9 passed
  • npm run test:e2e -- src/__tests__/e2e/init-replace-worktrees-1320.test.ts — 2 passed using the compiled CLI and local Git remotes
  • npx tsc --noEmit — passed
  • npm run lint — passed
  • npm run build — passed
  • git diff --check — passed

The focused tests cover local-only, behind-origin, diverged, dirty, symlinked, unregistered, and lock-lifetime cases. The compiled-CLI E2E verifies both clean retirement and dirty-checkout preservation. The existing self-mode-worktrees-808 suite remains the regression gate for the mode-switch queue/config contracts.

Related Issues

Fixes #1320

Notes for Reviewers

Please pay particular attention to the handoff order: retirement/preservation is proven first, the previous install's queue and config are then settled while both writer locks remain held, and only then is the old clone removed or relocated.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Avoid pushing commits already preserved remotely — src/utils/branch-worktree.ts:405: If another team member advances the remote branch while this clean checkout remains behind, the unconditional push is rejected as non-fast-forward and init --force aborts. All local commits are already preserved in this scenario. Fetch and check whether local HEAD is reachable from the remote branch; require a push only when local commits need preservation.

The PR description includes sufficient testing: a successful build and one representative compiled-CLI E2E run. I did not run, build, or install anything.

@SaulMoro
SaulMoro self-requested a review October 23, 2026 12:31

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

@XY-code1 The basic replacement case works, but a clean side checkout behind origin prevents switching teams at 80b294e1.

  • [P1 blocking] retireImpl unconditionally pushes HEAD:refs/heads/<branch> in src/utils/branch-worktree.ts. After one member pulls and another member advances teamai-reports, teamai init <other-team> --force exits 1 with a non-fast-forward rejection even though every local commit is already preserved on origin. It tells the member to push commits they do not have. Pulling first clears the refusal. Fetch and distinguish already-preserved history from unpublished/diverged history before deciding whether a push is needed.

The built-CLI fixture reproduced this failure and verified that unpushed commits are pushed and dirty side worktrees are retained. The focused E2E test passed, but does not exercise the behind-origin case. Current CI jobs passed.

CI review reported this same blocking finding. We agree, based on the real-CLI reproduction.

#1328 is an alternative for #1320. This PR owns push/removal of the side worktrees; #1328 owns archiving the old clone and its worktrees. Only one path should land. #1328 also covers the issue's insteadOf same-repository trigger, which this PR leaves unchanged.

@XY-code1

Copy link
Copy Markdown
Contributor Author

@SaulMoro I addressed the behind-origin blocker in d13f8ce. Retirement now inspects and fetches the remote branch, skips the push when origin already contains every local commit, pushes only local-ahead history, and fails closed on divergence or an unreadable remote. I added focused regression cases for behind-origin and diverged histories, and extended the compiled-CLI E2E fixture so a second clone advances teamai-reports before init --force replaces the repository. Build, typecheck, lint, 5 focused unit tests, the real-CLI E2E test, and git diff --check all pass. Could you please take another look?

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Hold the branch locks until clone replacement completes — src/utils/branch-worktree.ts:446, src/init.ts:1758: Reports retirement releases its lock before learnings retirement and clone deletion. A concurrent reports writer can then recreate reports-wt against the old clone while learnings retirement performs network operations. Deleting the clone subsequently orphans that new checkout, reproducing the member-registration/pull failure this PR fixes. Hold both branch locks through clone deletion, including when a checkout does not yet exist.

The earlier unconditional-push finding is resolved: the current diff fetches origin and skips pushing when local commits are already preserved remotely.

The PR description documents sufficient testing: a successful build and one representative compiled-CLI E2E run. I did not run, build, or install anything.

@XY-code1

Copy link
Copy Markdown
Contributor Author

@SaulMoro The new lock-lifetime blocker is fixed in 10ecd1b. Clone replacement now nests reports and learnings retirement, so both writer locks remain held until the old clone is deleted. Lock acquisition happens even when a checkout does not yet exist, preventing a concurrent writer from recreating a side worktree during replacement. I added a regression test that attempts to reacquire both locks inside the replacement callback and verifies both are released afterward. Build, typecheck, lint, 6 focused unit tests, the compiled-CLI E2E test, and git diff --check pass. Could you please re-review the updated head?

@github-actions

Copy link
Copy Markdown

No new findings I’m confident are real in the current diff.

Both earlier findings are resolved:

  • Retirement fetches origin and skips pushing when local commits are already preserved remotely.
  • Nested retirement callbacks hold both writer locks through old-clone deletion, including when checkouts do not exist.

The PR description documents sufficient testing: a successful build and one representative compiled-CLI E2E run. The record names an older commit, which is non-blocking under the review rules.

I did not run, build, or install anything from the PR.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

@XY-code1 Thanks, approving at 10ecd1ba. This replaces our Request changes from 80b294e1. The blocking case is fixed.

We rebuilt this head and reran the scenario with the real CLI. Two members, teamai-reports advanced by the other member, and a clean side checkout that is behind origin with nothing unpublished. teamai init <other-team> --force now exits 0 without pushing, and origin's head is unchanged. The other cases also hold:

  • a local-only commit is pushed;
  • a diverged checkout stops with exit 1 and keeps its commit;
  • a dirty checkout stops with exit 1 and is kept;
  • an unreachable origin stops with exit 1 and a clear message.

tsc, lint, branch-worktree-retire.test.ts (6/6) and the #1320 e2e (1/1) pass.

Suggestions:

  • [P2 non-blocking] If the fetch fails after ls-remote succeeds, retireImpl prints the raw git error without the "left unchanged, run init again" guidance the other refusals give, because fetchTrackingRef isn't wrapped like ls-remote and push. Nothing is lost.
  • [P2 non-blocking] The diverged message says "Reconcile and push the branch" but not how. Naming the checkout path and the git pull --rebase / git push steps would help.
  • [P3 nit] retireImpl returns before whileLocked on the http guard, which would skip the clone removal. It is unreachable today, because init.ts drops repo.kind when it builds previous.

CI review: it reported no new findings. We agree with its points on the fetch-before-push logic, the nested locks and the testing record. We disagree that the record names an older commit: the body names 10ecd1ba. CI jobs at this head need a maintainer's approval to run, so only the review job ran.

Of the two fixes for #1320, this one lands. #1328 conflicts with it in src/init.ts.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Withdrawing my approval. Before merging I ran this head together with current main on macOS with the default temp dir, and the retire path refuses its own checkout whenever the team repo path goes through a symlink.

[P1 blocking] retireImpl compares an unresolved path with git's resolved one. In src/utils/branch-worktree.ts, retireImpl checks path.resolve(registered) !== path.resolve(wt). git worktree list --porcelain reports the real path, while wt keeps the symlinked one. On macOS /tmp and /var are symlinks to /private/..., and a symlinked home does the same. In those cases init --force stops with "exists but is not registered to ... as its teamai-reports checkout", although it is this repository's own checkout, and the side worktree is never retired.

Repro on this head (macOS, TMPDIR unset):

npx vitest run src/__tests__/branch-worktree-retire.test.ts
  Tests  4 failed | 2 passed (6)   # ForeignCheckoutError: .../reports-wt exists but is not registered ...
npm run build && npm run test:e2e -- src/__tests__/e2e/init-replace-worktrees-1320.test.ts
  Tests  1 failed (1)

With TMPDIR pointing at a directory without a symlink, both pass. That explains why it passed in my earlier run.

Comparing real paths fixes both. The same file already resolves git dirs with fse.realpath:

-      if (registered === null || path.resolve(registered) !== path.resolve(wt)) {
+      if (registered === null || (await fse.realpath(registered)) !== (await fse.realpath(wt))) {

With that change: branch-worktree-retire.test.ts 6/6 and init-replace-worktrees-1320.test.ts 1/1 pass, and npx tsc --noEmit is clean.

CI review: it reported no new findings at this head. It did not catch this, since it only shows up with a symlinked path. The CI jobs at this head were approved and are still queued; I expect the macOS legs to show this failure.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Follow-up to my request for changes. CI at 10ecd1b has finished. The three macOS Lint & Test legs fail in branch-worktree-retire.test.ts with the exists but is not registered error described above, now under /var/folders/.... The realpath fix covers those.

[P1 blocking] E2E shard 2/3 is a separate failure that the realpath fix does not cover. Three existing tests in src/__tests__/e2e/self-mode-worktrees-808.test.ts fail on this head, both on Linux CI and locally. On main all 29 pass:

npm run build && npm run test:e2e -- src/__tests__/e2e/self-mode-worktrees-808.test.ts
  main (a890d966):            Tests  29 passed (29)
  this head, alone:           Tests  3 failed | 26 passed (29)
  this head + realpath fix:   Tests  3 failed | 26 passed (29)

In these scenarios the side checkouts belong to team A's clone, and init is pointing the project at team B. The new retire step now runs before the existing #808/#823 handling and stops init --force:

  • reports-wt is a teamai-reports checkout teamai cannot show to be .../team-repo's
  • learnings-wt has uncommitted changes, so teamai left it and the current clone unchanged

Those tests expect init to set team A's queue and config aside, or to keep team A's dirty learnings checkout and still finish. Either retire needs to leave alone the checkouts that path already handles (a team switch rather than a reclone of the same team), or, if refusing is the intended new behavior, the PR needs to change those tests and say why the #823 contract changes. As it stands it is a regression in existing coverage.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Do not delete the clone backing a dirty checkout — src/utils/branch-worktree.ts:402: When switching repositories, keepDirty runs the replacement callback despite uncommitted changes. That callback deletes the old clone, leaving the retained checkout’s .git pointer dangling. If it also contains unpushed commits, their Git objects are deleted without preservation. Refuse replacement or safely detach and preserve the checkout before deleting its backing clone; retaining its files alone is insufficient.

Both earlier findings are resolved: already-preserved commits avoid a push, and both writer locks remain held through clone deletion.

The PR description documents sufficient testing: a successful build and one representative compiled-CLI E2E run. Its older commit reference is non-blocking under the review rules.

I only read the scoped diff; I did not run, build, or install anything from the PR.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Lock the dirty-checkout preservation path — src/init.ts:1765: This path renames the backing clone and repairs worktrees without acquiring either writer lock. If a reports/learnings writer holds its lock and has written uncommitted changes, switching repositories can rename its Git directory mid-operation; Git commands during the rename-to-repair interval fail because the checkout’s .git pointer is dangling. Acquire both writer locks before inspection and hold them through relocation and repair.

Earlier findings: unnecessary pushes and dirty-checkout object deletion are resolved. The clean retirement path holds both locks, but the new dirty-checkout path bypasses that protection.

The PR description documents sufficient testing: a successful build and representative compiled-CLI E2E run. Its older commit reference is non-blocking under the review rules.

I only read the scoped diff; I did not run, build, or install anything from the PR.

@github-actions

Copy link
Copy Markdown

No new findings I’m confident are real in the current diff.

All earlier findings are resolved:

  • Retirement fetches origin and skips pushing commits already preserved remotely.
  • Both writer locks remain held through clone deletion.
  • Dirty-checkout preservation retains the backing clone and repairs worktree pointers.
  • The dirty-checkout path now acquires both locks before inspection and holds them through relocation and repair.

The PR description documents sufficient testing: a successful build and one representative compiled-CLI E2E run. Its older commit reference is non-blocking under the review rules.

I only read the scoped diff; I did not run, build, or install anything from the PR.

@XY-code1

Copy link
Copy Markdown
Contributor Author

@SaulMoro The blocking findings have been addressed in the latest commits. The review bot now reports no new findings and confirms all earlier findings are resolved. Could you please take another look when convenient? Thanks!

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Re-checked at 043e89c.

The realpath blocker is fixed. With TMPDIR unset on macOS, branch-worktree-retire.test.ts passes 8/8, and init-replace-worktrees-1320.test.ts, including the new team-switch test, passes. tsc and the build are clean.

[P1 blocking] still open: the #808/#823 e2e regressions. On this head, src/__tests__/e2e/self-mode-worktrees-808.test.ts still fails 3 of 29. On main it passes 29/29.

npm run build && npm run test:e2e -- src/__tests__/e2e/self-mode-worktrees-808.test.ts
  Tests  3 failed | 26 passed (29)

CI looked green because the CI and Code Erosion runs at this head were waiting for maintainer approval, so only the review job had run. I have approved them now. The CI review says the earlier findings are resolved, but it only read the diff and ran nothing. The e2e run above shows the #808 finding is not resolved.

This head also conflicts with main in src/init.ts after #1263 and #1280. Please rebase when you push the fix.

@XY-code1
XY-code1 force-pushed the fix/init-replace-worktrees branch from 043e89c to d8f810e Compare October 24, 2026 06:19
@XY-code1

Copy link
Copy Markdown
Contributor Author

@SaulMoro I rebased the branch onto current main and addressed the three #808/#823 regressions at d8f810e. The previous install's queue and config are now settled before its clone is removed or relocated, so team-switch handoff data is preserved. Replacement also probes whether each managed checkout is actually registered to the current clone; stale or unregistered paths are left to the existing #808 foreign-checkout handling instead of blocking the switch. The focused retirement suite passes 9/9, the compiled-CLI #1320 E2E passes 2/2, and typecheck, lint, build, and diff-check pass. Could you please rerun the Linux self-mode-worktrees-808 suite and re-review the updated head?

@github-actions

Copy link
Copy Markdown

No new findings I’m confident are real in the scoped diff.

All earlier findings are resolved:

  • Commits already preserved remotely no longer require a push.
  • Both writer locks remain held through clone deletion, including when checkouts do not exist.
  • Dirty-checkout preservation retains the backing clone and repairs worktree pointers.
  • The dirty-preservation path acquires both locks before inspection and holds them through relocation and repair.

The PR description documents sufficient testing: a successful build and one representative compiled-CLI E2E run. Its older commit reference is non-blocking under the review rules.

I only read the scoped diff; I did not run, build, or install anything from the PR.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Re-checked at d8f810e.

#823 item 17 (init reusing a clone of team B) passes now. init-replace-worktrees-1320.test.ts passes 2/2, and tsc and the build are clean.

[P1 blocking] still open: two #808 e2e tests fail. self-mode-worktrees-808.test.ts fails 2 of 29 on this head, both on macOS with TMPDIR unset and in CI on Linux (E2E (fork-safe, no credentials, shard 2/3), which is why the E2E (fork-safe, no credentials) check is red). On main the file passes 29/29.

npm run build && npm run test:e2e -- src/__tests__/e2e/self-mode-worktrees-808.test.ts
  Tests  2 failed | 27 passed (29)
  • sets aside the queue when init points the project at another team repo... ([bug] Follow-ups to the worktree fixes: import --from-mr never shares its learning, and push can still revert a teammate's update #823 item 13). The step that fails is the re-init with the same team repo written another way (teamA.url with .git replaced by /, [bug] The index shrink guard blocks legitimate rebuilds, so recall returns resources the member no longer has #1006). After that init and a pull, team A's queued note is gone from the queue (expected [] to include 'team-a-note-….md', line 1537). On main it stays queued and indexed. Going by the diff, a clean replacement now runs retireWhileLocked on both side checkouts before it removes the clone, where main only removed the clone. That would explain it, but I have not traced it. A same-repo re-init should leave the queue as it was.
  • refuses and keeps team A's learnings checkout once init reclones team B at the same path.... The draft is kept and contribute still says Saved locally, but the warning has changed. It used to say the checkout is one "teamai cannot show to be team-repo's". It now says the checkout belongs to team-repo.previous, because the dirty path renames the clone and runs git worktree repair. If this new message is what you intend, update the test in this PR and say why in the description. Otherwise keep the old outcome.

The CI review at this head reports no new findings and calls every earlier finding resolved. We agree that the earlier findings are fixed. It only read the diff, though, and the E2E job above is red at the same head.

@github-actions

Copy link
Copy Markdown

No new findings I’m confident are real in the scoped diff.

All earlier findings are resolved:

  • Commits already preserved remotely no longer require a push.
  • Both writer locks remain held through clone deletion, including when checkouts do not exist.
  • Dirty-checkout preservation retains the backing clone and repairs worktree pointers.
  • The dirty-preservation path holds both locks through inspection, relocation, and repair.

The PR description documents sufficient testing: a successful build and representative compiled-CLI E2E runs. Its older commit reference is non-blocking under the review rules.

I only read the scoped diff; I did not run, build, or install anything from the PR.

@XY-code1

Copy link
Copy Markdown
Contributor Author

@SaulMoro I addressed both remaining #808 regressions in 41ef6332:

  • same-repository re-init now compares both Git's expanded remote URL and the literal remote.origin.url, so url.*.insteadOf rewrites no longer cause a false clone replacement or retire the existing queue/worktrees;
  • the dirty-checkout E2E assertion now follows the intentional preservation behavior: the warning names team-repo.previous, and the test verifies that the preserved backing clone still exists.

Validation completed locally:

  • npm run typecheck
  • npm run lint
  • git diff --check

The full E2E run needs the hosted Linux/macOS jobs; the local Windows runner hits the repository's Git hook/Cygwin fork limitation. The scoped Codex review and open-source scan are green at this head. Could you please approve the remaining workflows and re-check the two focused #808 cases?

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Re-checked at 41ef633. I approved the pending CI and Code Erosion runs for this head.

The source change works: the same-repo re-init now reuses the clone (Repo already exists at …/team-repo, using existing clone). branch-worktree-retire.test.ts passes 9/9, also with TMPDIR set to a symlink. init-replace-worktrees-1320.test.ts passes 2/2.

[P1 blocking] self-mode-worktrees-808.test.ts still fails 2 of 29 on macOS with TMPDIR unset. Both failures are now test expectations rather than product behavior:

npm run build && npm run test:e2e -- src/__tests__/e2e/self-mode-worktrees-808.test.ts
  Tests  2 failed | 27 passed (29)
  • sets aside the queue … (#823 item 13), line 1537. On main, the re-init with team-a/ instead of team-a.git replaces the clone by mistake, so the pull after it cannot prove the checkout and the note stays queued. The test was written against that. With this PR the clone is reused, and that pull prints Published 1 queued learning(s) to team A, the same team, now accepting pushes. That is the better outcome, but the assertion expect(partitionQueue(install)).toContain(name) still expects the old one. Please update the step to what now happens: the note is in team A's bare repo, and the index still has it. Then say so in the description.
  • refuses and keeps team A's learnings checkout …, line 1770. The expected text in the test does not match what the CLI prints. The test looks for … is a teamai-learnings checkout teamai cannot show to be <partition>/team-repo.previous's. The CLI prints … is a teamai-learnings checkout of <partition>/team-repo.previous, not of <partition>/team-repo, left by an install in another mode. teamai will not use or remove it. The draft is kept and Saved locally is printed, so only the expected string needs fixing.

Since Windows cannot run this file locally, the Linux and macOS E2E jobs now running at this head will show the same two failures.

The CI review at this head reports no new findings and calls the earlier ones resolved. We agree on the source. It did not run the e2e file, which is still red as shown above.

@github-actions

Copy link
Copy Markdown

No new findings I’m confident are real in the scoped diff.

All earlier findings are resolved:

  • Commits already preserved remotely no longer require a push.
  • Both writer locks remain held through clone deletion, including when checkouts do not exist.
  • Dirty-checkout preservation retains the backing clone and repairs worktree pointers.
  • The dirty-preservation path holds both locks through inspection, relocation, and repair.

The PR description documents sufficient testing: a successful build and representative compiled-CLI E2E runs. Its older commit reference is non-blocking under the review rules.

I only read the scoped diff; I did not run, build, or install anything from the PR.

@XY-code1

Copy link
Copy Markdown
Contributor Author

@SaulMoro Updated both expectations in 0a2b37fa:

  • the same-repository re-init case now expects the queued learning to be published to team A, removed from the local queue, and retained in the search index;
  • the dirty-checkout case now matches the actual foreign-checkout warning naming team-repo.previous as the owner and the current team-repo as the non-owner.

npm run typecheck, npm run lint, and git diff --check pass locally. Please re-check the focused #808 E2E file on Linux/macOS.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Re-checked at 0a2b37f, merged onto current main (e3ac431). I approved the pending CI and Code Erosion runs for this head.

The dirty-reclone case passes now. tsc, lint and the build are clean, and the full unit suite passes (15440). init-replace-worktrees-1320, init-project-all and workspace-children-913 pass.

[P1 blocking] #823 item 13 still fails, further down. self-mode-worktrees-808.test.ts is 1 failed / 28 passed with TMPDIR unset on macOS:

Error: ENOENT: … pending-learnings.git-git.example.com-team-team-a/team-a-note-….md
 ❯ src/__tests__/e2e/self-mode-worktrees-808.test.ts:1548

The same-repo step now publishes team A's note, as the new assertions say, so nothing is queued when init switches to team B. The rest of the test still expects that note to be set aside. Please queue a fresh team A note with pushes rejected after the same-repo step and before initB, using the same withPushesRejected + contribute pattern as at the top of the test, and point the set-aside assertions at that note. Without a queued note at switch time, the set-aside assertions cannot pass, and the final check that team B's log has no team-a-note- proves nothing. Please don't drop those assertions: they are what #823 item 13 covers.

The CI review at this head reports no new findings. We disagree only on this e2e case, which it did not run.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

One more note on 0a2b37f, non-blocking, in case you touch src/init.ts again for the item 13 fix:

[P2 non-blocking] A failed git worktree repair hides where the old clone went. In src/init.ts (dirty path), the clone is renamed to <localPath>.previous[.n], then worktree repair runs, and only after that does init warn Kept the old clone … at <aside>. I made git worktree repair exit with an error right after the rename. Init exited 1 with only Git's error. The renamed clone and the dirty checkout were both kept, so no data was lost. A normal retry of init then succeeded but left the old checkout unopenable by Git. It came back only after a manual git -C <aside> worktree repair. Printing the warning right after the rename, and on a repair failure naming git -C <aside> worktree repair, would let the user recover without guessing.

Other paths I exercised with the built CLI behaved well. A push rejected during retirement exits 1 and keeps the learnings checkout at its exact HEAD, and the retry pushes it to team A, not team B. A dirty A → B → A switch keeps the commit and the draft, and team B receives neither.

@github-actions

Copy link
Copy Markdown

No new findings I’m confident are real in the scoped diff.

All earlier findings are resolved:

  • Commits already preserved remotely no longer require a push.
  • Both writer locks remain held through clone deletion, including when checkouts do not exist.
  • Dirty-checkout preservation retains the backing clone and repairs worktree pointers.
  • The dirty-preservation path holds both locks through inspection, relocation, and repair.

The PR description documents sufficient testing: a successful build and representative compiled-CLI E2E runs. Its older commit reference is non-blocking under the review rules.

I only read the scoped diff; I did not run, build, or install anything from the PR.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Approving at 50fcefd. This supersedes our earlier request for changes.

Checked with this head merged onto current main (841beed):

  • tsc, lint and the build are clean. The full unit suite passes (15440).
  • With TMPDIR unset on macOS, these e2e files pass 81/81: self-mode-worktrees-808 (29/29, matching main), init-replace-worktrees-1320, init-project-all and workspace-children-913.
  • branch-worktree-retire.test.ts passes 9/9, also with TMPDIR set to a symlink.

The two last points are fixed. Item 13 queues a fresh team A note before switching to team B and still checks that it is set aside. A failed worktree repair now names the preserved clone and the repair command, and the warning prints before repair runs. On earlier heads I also ran the risky paths with the built CLI:

  • A push rejected during retirement keeps the learnings checkout at its exact HEAD, and the retry pushes it to team A, not team B.
  • A dirty A → B → A switch keeps the commit and the draft, and team B receives neither.

[P3 nit] The last check in item 13, not.toContain('team-a-note-') against team B's log, does not match the new team-a-switch-note-… file. 'team-a-' would cover both notes.

The CI review at this head reports no new findings and calls the earlier ones resolved. We agree now. On earlier heads it did not run the #808 e2e file, which was red until this push.

@SaulMoro
SaulMoro merged commit 6ebb31c into Tencent:main Oct 24, 2026
18 checks passed
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.

[bug] init --force to another team repo orphans reports-wt and learnings-wt

3 participants