feat(worktree): resume into a preserved worktree on re-invocation#76
feat(worktree): resume into a preserved worktree on re-invocation#76pro-vi wants to merge 2 commits intokunchenguid:mainfrom
Conversation
When gnhf preserves a worktree on exit (any run that lands a commit), subsequent invocations with the same prompt currently fail in `git worktree add -b` with "a branch named 'gnhf/...' already exists" because the branch is still registered against the preserved worktree. Detect that case and resume the existing run instead of re-creating: - New `worktreeExists(baseCwd, worktreePath)` helper reads `git worktree list --porcelain` and returns true when the path is already registered. - `initializeWorktreeRun` checks for the preserved worktree plus the matching `.gnhf/runs/<runId>/` directory, and when both are present calls `resumeRun` instead of `createWorktree` + `setupRun`. - On the resume path the call site reads `startIteration` from the preserved run and skips installing the worktree-cleanup handler, so Ctrl-C on a resumed invocation cannot take down a preserved worktree carrying earlier iterations' commits. Adds an e2e test that runs `--worktree` twice with the same prompt and asserts the second invocation (a) does not mention "already exists", (b) logs "resuming preserved worktree", (c) lands an additional commit on the same branch, and (d) continues iteration numbering (gnhf kunchenguid#2:).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a130f76d2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
P1 (worktree path normalization): `worktreeExists` compared raw strings from `git worktree list --porcelain` against `path.join` output, which could miss real matches on Windows where git emits forward slashes and path.join uses backslashes. Resolve both sides with `path.resolve` before comparing so the lookup is platform-agnostic. Unit tests in git.test.ts cover the match, non-match, normalize, and command-failure paths. P2 (branch verification before resume): if a user manually switched the preserved worktree to a different branch (or detached HEAD) between invocations, the previous resume path would run the orchestrator against the wrong ref and silently write new commits there. Read the worktree's current branch before resuming and throw a clear error when it does not match the expected `gnhf/<runId>`, pointing at the commands the user can run to restore or remove the worktree. An e2e test creates a preserved worktree, switches it to a sideways branch, and asserts the second invocation fails with the branch name in the error message.
|
Thanks for the review. Both items addressed in e3625a7: P1 (path normalization): P2 (branch verification): All 359 tests + lint + typecheck + prettier remain clean. |
|
thanks for the solid work here, especially the P1/P2 follow-ups. reviewed the diff and ran everything locally - all green (363 tests, lint, typecheck, format:check). ready to merge once the conflict with main is sorted. it's a trivial one: #77 added closeSync / createReadStream / createWriteStream to the same fs import block in src/cli.ts where this PR adds existsSync. a merge or rebase against main should resolve it cleanly. |
Problem
When gnhf preserves a worktree on exit (any run that lands a commit), subsequent invocations with the same prompt fail:
The branch is still registered against the preserved worktree, so
git worktree add -bcannot re-create it. The user's only options today are to merge the branch and remove the worktree, or delete the branch manually. There's no way to resume and keep iterating on the same preserved worktree.Change
Detect the "worktree already preserved" case and resume the existing run instead of re-creating.
worktreeExists(baseCwd, worktreePath)insrc/core/git.tsreadsgit worktree list --porcelainand returns true when the path is already registered.initializeWorktreeRuninsrc/cli.tschecks for the preserved worktree plus the matching.gnhf/runs/<runId>/directory. When both are present it callsresumeRunrather thancreateWorktree+setupRun, and returns aresumed: trueflag.startIterationfrom the preserved run viagetLastIterationNumberand skips installing theworktreeCleanuphandler on the resume path, so Ctrl-C on a resumed invocation cannot take down a worktree that already carries earlier iterations' commits.The non-worktree resume path (existing behavior when running gnhf from an already-checked-out
gnhf/*branch) is untouched.Example
Test plan
npm run typechecknpm run lintnpm run format:checknpm test(354/354 passing, including new e2e)npm run test:e2e(7/7 passing)New e2e test runs
--worktreetwice with the same prompt and asserts: the second invocation does not mention "already exists", logs "resuming preserved worktree", lands an additional commit on the same branch, and continues iteration numbering (gnhf #2:).Compatibility
createWorktree+setupRunpath).worktreeExistsreturns false and the current path runs, matching existing behavior as closely as possible.