Skip to content

fix(worktree): update local main safely, once per branch, alongside the checkout - #23698

Merged
brennanb2025 merged 17 commits into
mainfrom
brennanb2025/create-local-base-refresh
Oct 1, 2026
Merged

brennanb2025 merged 17 commits into
mainfrom
brennanb2025/create-local-base-refresh

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 17 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2662 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1076 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1586
Prod 16 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​678 $\color{#cf222e}{\Huge{\mathbf{−}}}$​534 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​144

ELI5

With "keep local main up to date" on, Orca fast-forwards your local main to match origin/main every time it creates a workspace, so agents that compare against main see only their own changes. Two things went wrong. When two workspaces were created at once, both tried to update the same checkout, one lost the race for git's lock file, and you got a "Local main was not refreshed" warning even though main ended up correct. And the way Orca moved main (check that the checkout is clean, then force it with git reset --hard) could silently destroy your work in the gap between the check and the reset. Now one update of main runs per repo at a time, creates from the same base that arrive during it share one follow-up update, and every create reports the result for the base it was created from; the update uses git's own safe fast-forward, which refuses instead of overwriting anything; and it runs alongside the checkout instead of before it.

What Changed

What you saw before:

  • Two creates close together: a sticky "Local main was not refreshed for " warning, although main was updated (2026-09-28 incident: the other create's reset --hard held .git/index.lock for 8.3 s on a loaded machine).
  • Every create waited for the refresh to finish before git worktree add started.
  • Every create reset the primary checkout even when main was already current, and warned about "uncommitted changes" if that checkout had edits, even though there was nothing to update.
  • Silent data loss was possible. Orca checked git status (tracked files only) and then ran git reset --hard. An untracked file at a path the new main adds was overwritten; an edit saved, or a commit made, between the check and the reset was discarded. I reproduced the untracked-file case with real git; the new tests reproduce all three against the old command.

What you see now:

  • Creates at the same time: no false warning. The first create's update runs; creates from the same base arriving during it wait for one follow-up update, which sees main already current and does nothing.
  • Creates from different remotes' main at the same time (say origin/main and upstream/main in a fork): each gets the result for the base it was created from.
  • The update runs alongside git worktree add (the new workspace is created from origin/main, so its contents never depended on local main).
  • main already current: nothing runs, nothing warns.
  • A real problem (uncommitted changes where main is checked out, local-only commits on main) still warns, once per repo and branch, not once per create.
  • Your work is never overwritten: if an edit, an untracked or ignored file (such as a local .env), or a new commit would be lost, git refuses and you get the "uncommitted changes" or "cannot be fast-forwarded" warning instead.

Mechanism:

  • One routine, run where git runs. src/shared/worktree/local-base-branch-fast-forward.ts holds the whole policy: read local and remote commits, count how far behind, find the checkout that has the branch, check it is clean, move it. The main process runs it for local and WSL repos; the SSH relay runs it on the remote host. The app-side SSH copy of the checks, the queue and the lock retry are deleted, so local and SSH can no longer disagree.
  • A checked-out branch moves with git merge --ff-only <commit> instead of git reset --hard. Git applies the move under its own index lock and refuses to overwrite an edit or an untracked file, or to drop a commit that is not in the target; --no-overwrite-ignore makes it refuse to replace an ignored file too (a plain fast-forward, like the old reset, silently replaces an ignored .env at a path the new main starts tracking). Orca first confirms the checkout still has that branch (git symbolic-ref HEAD).
  • The move is a plain fast-forward whatever the user's merge settings say. git merge reads settings that reset never did, and some would turn it into something else: a branch's mergeOptions of -s ours (or pull.twohead=ours) made a merge commit that dropped upstream's changes, --squash staged upstream without moving main, and a signed-merges requirement refused the unsigned origin/main tip the new workspace was already created from. Orca clears the branch's mergeOptions for this one command and passes the strategy (-s recursive) and --no-verify-signatures on the command line, which beats any config. The user's hooks are off (core.hooksPath=/dev/null; the old reset ran no hooks either, and the create waits on this), and so are auto-gc and autostash. After the move Orca confirms main is exactly the target before reporting it updated; otherwise it warns. A branch whose name contains = cannot be passed this way and is left alone with a warning.
  • A branch that is not checked out moves with git update-ref, as before: it only moves if the branch is still where Orca read it. It now writes a reflog message ("orca: fast-forward to refs/remotes/origin/main") so you can see what moved your branch.
  • One update per repo and branch at a time (src/shared/coalescing-keyed-runner.ts). Creates toward the same remote's branch that arrive while an update runs share a single queued update, which uses the newest request. A create toward a different remote's branch (origin/main vs upstream/main) gets its own queued update, and queued updates run one after another, so every create reports the result for the base it was created from. A burst of creates costs one update per distinct base on top of the one already running. Nothing ever starts a second update of the same branch in parallel.
  • Bounded wait. A create waits at most 30 s for the update result (for SSH, the existing 30 s request timeout), then finishes without a result and logs it. The update itself keeps running; it is never interrupted mid-move.
  • Other git processes. A move that fails on a held lock (a terminal git, an editor) is retried after 50, 150 and 400 ms. If it still fails, Orca checks whether main already contains the target (someone else updated it) and reports success in that case.
  • The cleanliness check no longer takes .git/index.lock (git --no-optional-locks status), so it cannot make your own git commit in that checkout fail.
  • No refresh when the create makes the branch itself (workspace feature-x from origin/feature-x): there is no local branch to update, and probing it while the checkout creates it caused a false "not fast-forward" warning.
  • Warning toast is keyed by repo and branch, because creates toward one target share an update, so its result is one fact, not one per create.
  • Settings text is unchanged: it already says Orca "safely fast-forwards" and "skips the update if that branch has uncommitted changes or local-only commits", which is what this now enforces.

Why

Root cause: each create ran its own update of the same branch, and the update itself was a check followed by a forced overwrite. Two creates meant two overwrites racing for one lock, and any change between the check and the overwrite was lost. An earlier version of this PR serialized the updates inside the app with a 30 s cap and a lock retry; review showed that past the cap it still started a competing update (the same false warning), that the SSH path kept a second copy of the whole policy in the app, and that the unsafe overwrite remained.

Why this design:

  • Git's own fast-forward is the only place the "is it safe to move?" check and the move happen together. Any check Orca does first is a snapshot; merge --ff-only makes a race lose nothing. This is the standard way tools that move a checked-out branch for the user do it.
  • Owning the update per branch (instead of queuing one per create) matches what the user asked for: "make main current", once. It removes the race instead of retrying around it, and bounds the work under a burst.
  • Running it on the host that runs git keeps SSH repos correct when more than one client creates on the same host, and leaves one copy of the rules.

Alternatives considered:

  • git reset --keep: refuses to overwrite edits and untracked files and runs no hooks, but still moves the branch if you committed in the meantime, dropping that commit from main. Rejected.
  • git merge --ff-only with hooks on: would run a repo's post-merge hook (often npm install) in your checkout on every create, and the create would wait for it. Rejected; the old reset never ran hooks.
  • Updating main whenever Orca fetches origin/main: would move your checkout at arbitrary times. Out of scope; the setting is "on workspace create".
  • Reporting the result later instead of on the create: needs a new event that older paired clients would silently drop. Not worth it: the update runs alongside the checkout, and the checkout (writing a whole tree) is almost always slower than the fast-forward (writing only the changed files).

Known limits:

  • If main is being rebased or bisected in some checkout, git lists that checkout as detached, so Orca treats main as not checked out and can move it with update-ref. Git's own check at the end of the rebase then refuses, so nothing is lost, but that rebase has to be redone. This existed before this PR.
  • Two Orca repo entries that point at the same git repository through different paths are updated independently; git's lock, the retry and the compare-and-swap still keep that safe.
  • A move that never finishes (for example a large-file download from a server that stopped answering) keeps later creates of that repo waiting 30 s each for its result, until Orca restarts; they never start a second move. Before this PR the same stuck reset --hard ran before the checkout and held every create forever.
  • Once local main has moved to upstream/main, a create from origin/main warns that main "cannot be fast-forwarded", because main is now ahead of origin/main. Nothing is wrong and nothing moves. This existed before this PR; a burst of creates from both remotes now shows it the same way creates made one after another do. This is left for a follow-up: when local main already contains the target, Orca will report nothing to do instead of a warning, and warn only when local and the target have truly diverged.

Differences from the common pattern

Matches the common pattern: a checked-out branch is moved only by git's own merge --ff-only after a cleanliness and ancestry check, so a race loses nothing and git's refusal becomes the warning; an already-current branch does nothing even if its checkout is busy or dirty; transient lock failures are retried after 50, 150 and 400 ms; one update of a branch runs at a time; and the work runs on the host that runs git.

  • Intended: Orca moves the user's own local main when a workspace is created; the common pattern only moves checkouts the app created itself. Kept because agents compare their work against local main, and a stale one makes their diffs wrong; the setting is opt-in and every move is a fast-forward git itself checks.
  • Intended: the merge is pinned to a plain fast-forward (hooks off, the branch's merge options cleared, a fixed strategy, no signature check, no overwrite of ignored files); the common pattern runs a bare merge --ff-only. Safe and necessary because this is the user's checkout: their merge settings turned the fast-forward into a merge commit that dropped upstream, a squash, or a refusal, and a plain fast-forward silently replaced an ignored .env.
  • Intended: the cleanliness check counts tracked changes only, where the common pattern also counts untracked files. Safe because git still refuses to overwrite an untracked file at a path the new main adds (shown in the screenshots), and it matches the setting's text, so an unrelated untracked file in that checkout doesn't block every update.
  • Intended: creates toward the same remote branch share one queued update, where the common pattern queues every caller separately. Safe because a create only ever shares an update toward its own base and gets that update's result, and it bounds a burst of creates to one update per distinct base.
  • Intended: a create stops waiting for the result after 30 s but never interrupts the update, where the common pattern kills the git process at 30 s. Killing it can leave git's lock file behind in the user's checkout, and other creates share the same update.
  • Intended: a branch that isn't checked out anywhere moves with a compare-and-swap update-ref; the common pattern never moves a branch with no checkout. With no working tree there is nothing to overwrite, and the swap only applies if the branch is still where Orca read it.
  • Temporary: when local main already contains the remote's commits (for example after a create from another remote moved it ahead), Orca warns "cannot be fast-forwarded", where the common pattern treats it as already up to date. This predates this PR; the follow-up reports nothing to do in that case and warns only when the two have truly diverged.
  • Temporary: updates are serialized per repository path, where the common pattern keys on the git repository's identity, so two Orca entries for one repository through different paths can update at the same time. Git's own lock, the retry and the compare-and-swap keep that safe (listed under Known limits); keying by the repository's identity is the follow-up.

Linked Issue

None. This comes from an internal incident report (slow creates and a false warning on 2026-09-28); there is no public issue.

Visual Proof

Live Electron QA on a separate Mac (M4Air, heavily loaded: load average 19-29) against a local fixture repo app cloned from a local bare origin, local main 2 commits behind origin/main, setting on. Before = the refresh code as on origin/main (no refresh-related file changed between the build's base and current origin/main); after = this PR at 3613960, proven by strings only it contains in the built main bundle. Creates were started through the same store action the create dialog calls; every toast ever shown was recorded.

Scenario Before After
Two creates at once, 3 runs false "Git returned an error" warning 3/3, though local main ended up correct no warning 3/3, local main correct
An untracked notes.txt in the primary checkout, and the new main adds notes.txt the user's file silently replaced by upstream's, no warning file kept, "uncommitted changes" warning, main left alone
Local main behind, primary checkout has edits, two creates two identical warnings one warning
Local main already current, primary checkout has edits false "uncommitted changes" warning nothing to do, no warning
A post-merge hook in the primary checkout not run (reset) not run, main updated
Plain create, same machine load, create to workspace shown 728-1298 ms (checkout phase 373-655 ms) 572-951 ms (checkout phase 185-339 ms)

Two creates at once. Before: a false warning, even though local main was updated.

Before: two creates at once show a false Local main was not refreshed warning

After: both workspaces are created and no warning appears.

After: two creates at once, no warning

An untracked file where the new main adds that path. Before: no warning, and the user's file content was replaced by upstream's.

Before: untracked notes.txt silently overwritten, no warning

After: the file is kept and the user is told why main was not moved.

After: untracked file kept, uncommitted changes warning

Local main behind with edits in its checkout, two creates. Before: two warnings for one fact.

Before: two identical warnings

After: one warning per repo and branch.

After: one warning

Local main already current, checkout has edits. Before: a false warning.

Before: false uncommitted changes warning although main is current

After: nothing to do, no warning.

After: no warning

Re-run on the final code (M4Air, isolated dev app built from 822bad1, same fixture approach). Two creates at once from origin/main: no warning 3/3, local main == origin/main. Untracked notes.txt in the way: file kept, "uncommitted changes" warning, main not moved. New: a repo with an upstream remote whose main is ahead of origin/main; creates from upstream/main, upstream/main and origin/main at once (3 runs), and from origin/main and upstream/main at once (3 runs): no create from upstream/main ever warned, and local main ended at upstream/main in all 6 runs. On the code before this fix, the same three-create burst gave one of the upstream/main creates a false "cannot be fast-forwarded" result in 1 of 3 runs.

Two creates at once from origin/main: both created, no warning.

Two creates at once, no warning

Untracked file where the new main adds that path: file kept, and the user is told why main was not moved.

Untracked file kept, uncommitted changes warning

Creates from origin/main and upstream/main at once: both created, no warning, local main at upstream/main.

Creates from two remotes at once, no warning

Creates from upstream/main, upstream/main, origin/main at once: no warning for the upstream/main creates. The origin/main create warns because local main is now ahead of origin/main; that rule predates this PR and is listed under Known limits.

Three creates from two remotes; only the origin create warns

Testing

  • src/shared/worktree/local-base-branch-fast-forward-real-git.test.ts (real git): an untracked file at a path the new main adds is kept and main does not move; a post-merge hook does not run (and does run for a plain merge, as a control); an edit made just before the move is kept; a commit made just before the move stays on main; a free branch moves with the reflog message; an ignored file at a path the new main adds is kept and main does not move (fails without --no-overwrite-ignore; a plain fast-forward in the same test replaces it). With the old reset --hard put back, the untracked-file, edit and commit tests fail; with core.hooksPath removed, the hook test fails; with merge.verifySignatures=true, branch.main.mergeOptions of --verify-signatures, -s ours or --squash, or pull.twohead=ours, main still lands exactly on the target with one parent and a clean index, and a plain git merge --ff-only in the same test shows each setting is live (the -s ours, --squash and pull.twohead cases fail without the pins).

  • src/main/git/worktree-base-refresh-lock-real-git.test.ts (real git, held index.lock): retry after release; another process already moved main → updated; lock outlives the retries → warning; three concurrent creates → exactly one merge and no warning (fails without the one-update-per-branch rule); creates from origin/main and upstream/main in one burst each get the result for their own base, with never two merges at once (fails when the runner ignores the base, and when it runs different bases in parallel).

  • src/main/git/worktree-base-refresh-contention.test.ts: late creates fold into one follow-up; different repos do not wait on each other; a stuck update makes a later create stop waiting at 30 s with no second update started (fails without the rule); the shared update ignores one create's cancel signal.

  • src/shared/worktree/local-base-branch-fast-forward.test.ts: every status the routine can return, the exact git arguments, retry and failure classification, and validation of the relay's reply.

  • src/shared/coalescing-keyed-runner.test.ts: one run; a burst toward one base shares one follow-up that uses the newest request; a different base queues its own run behind the one running, never beside it; a mixed burst costs one run per distinct base; failures do not block; keys are independent.

  • SSH: src/relay/git-handler-worktree-provisioning.test.ts runs the relay handlers against real git (owner fast-forward, free branch, missing branch, dirty checkout kept, diverged branch not moved, invalid requests rejected, concurrent requests give one merge, concurrent requests from origin/main and upstream/main each get their own base's result). src/main/ipc/worktrees-ssh-local-base-refresh*.test.ts: the app maps the relay's result, runs no git of its own for this, treats a timed-out or lost request as "unknown" (no warning) and other failures as a warning, and starts the checkout while the update is in flight.

  • src/renderer/src/store/slices/worktrees-create-base-status.test.ts: one warning id per repo and branch.

  • Wider run: all worktree* git tests, relay git handler tests, local create flow, SSH setup, Windows worktree and runtime create suites pass (595 tests).

  • Typecheck: no errors in changed files (node and web projects). Changed-lines quality gate: 0 new findings.

  • Platforms: unit and real-git tests on macOS. Git arguments are the same on every platform; core.hooksPath=/dev/null works on Windows because no hook can exist under that path.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

AI Disclosure

Written with Claude.

Review

An independent architecture review challenged the earlier version of this PR; this redesign follows from it. Three review rounds plus a second architecture challenge of the move command (which found the merge-settings cases); results are in the status comment.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

  • Git 2.25 baseline: the exact move command now runs in src/shared/git-binary-compatibility.test.ts, which CI runs against Git 2.25.5 (also run locally against 2.25.5: passes). merge --ff-only -s recursive --no-verify-signatures --no-overwrite-ignore, symbolic-ref -q, update-ref -m, --no-optional-locks (2.15) and core.hooksPath (2.9) all predate it. maintenance.auto and merge.autoStash are config keys older git ignores.
  • Mixed versions: the app and its SSH relay always run the same build (the relay refuses a client from a different build), so the relay request can change: it now takes only the repo and the two refs and returns the result. Paired clients see no change: localBaseRefRefresh on the create result keeps its shape and its four statuses.
  • Folder workspaces: not affected; the refresh only runs for git repos created from a remote-tracking base.
  • Performance: locally, an already-current main now costs two rev-parse calls and nothing else (the SSH relay also validates the two ref names first, as before). A behind main adds one rev-list, one worktree list, one status and the move, all overlapped with the checkout.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: ready for a maintainer

Problem this PR fixes: with "refresh local main when creating a workspace" on, every create waited for the local main update to finish before checking out the new workspace. When two creates (or any other git process) touched the repo at the same time, one of them lost a race for git's index.lock and showed a sticky "Local main was not refreshed" warning, even though local main ended up correct.

What users get now: the local main update runs at the same time as the checkout. Orca's own updates for a repo take turns, so the second one finds main already current and does nothing. A short lock held by another git process is retried. No warning appears when local main already matches the target. Real problems still warn, for example uncommitted edits in the checkout that has main, or a lock that outlasts the retries.

Review: three independent full passes.

  • Pass 1 found a real regression that this PR had introduced, and fixed it. Creating a workspace whose branch is the base branch itself (feature-x from origin/feature-x) raced the checkout and showed a false "cannot be fast-forwarded" warning: 15/25 and 24/25 times on this PR before the fix, 0/25 on main, 0/25 after (commit 31c427c). The refresh is now skipped in that case, because git's -b already proves no such local branch existed.
  • Passes 2 and 3 found no defects.
  • Follow-ups from the passes:
    • the comment on the 30 s wait limit now describes what actually happens after it (67b1674);
    • new tests cover SSH updates taking turns per repo, confirmed by temporarily breaking the queue (76c885b);
    • the new test file no longer uses type assertions, which is what failed the changed-code quality gate (3564650).

Live QA (Electron, on a separate Mac): before = this PR's merge-base, after = this PR. The after arm was built at 31c427c; later commits changed only a comment and tests.

Scenario Before After
Two creates at once, 5 runs false warning 5/5 0/5 warnings
main already current, checkout dirty false warning no warning
External lock held 500 ms warning in 7/8 no warning 6/6
Workspace named after its base branch no warning no warning 5/5
Real failures (dirty checkout with main behind; lock held 2 s) not run warning still shown

Screenshots are embedded in the PR body. The QA seat was a Claude Opus agent, because the planned Grok seat had no usage left (402).

Known limits, stated in the PR body:

  • If the refresh ahead of it is still running after 30 s, the next refresh runs alongside it and can still warn.
  • On case-insensitive file systems, a workspace branch whose name differs from its base branch only in letter case can still show the old false "not fast-forward" warning.
  • Repos using git's newer "reftable" ref storage on git 2.45–2.47 are untested.

Not caused by this PR:

  • If the prepared-checkout reset --hard fails, the failed-finalize cleanup is skipped. That code is the same on main.
  • Edits made between the clean-checkout check and the reset can be overwritten, as before. The PR re-checks before every attempt, so it is no worse.
  • The floating layout button covers the right edge of warning toasts.

Validation:

  • Local typecheck (node project) at 76c885b: no new errors.
  • The changed test files and related suites pass: 186 tests across 17 files.
  • oxlint is clean on the changed files.
  • CI: all required checks green at 3564650: typecheck, static analysis, tests 8/8, relay integration, Git compatibility, package and package (windows).
  • Not exercised live: SSH relay, WSL, Windows, Linux, folder workspaces, and the spare-checkout preparation path. These are covered by unit and real-git tests only.

@brennanb2025
brennanb2025 marked this pull request as ready for review September 29, 2026 00:19
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High risk] Refactors git worktree base branch refresh with locking and coalescing.

The PR appears safe to merge; the remaining already-contained-target warning is non-blocking.

Findings

  1. P2 Already-contained target triggers warning ▶

Summary

The PR replaces forced local-base resets with safe Git fast-forwards, runs refresh alongside checkout, and coalesces concurrent refreshes by repository, branch, and target.

  • Local and SSH creation use the same refresh policy, with bounded waits and result reporting for each target.
  • No new actionable issue was established in the changes since the previous review.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  C[Create worktree] --> A[Resolve remote-tracking base]
  A --> R[Queue refresh by repo and branch]
  A --> W[Add worktree from remote-tracking base]
  R --> I[Inspect local branch]
  I --> M{Checked out?}
  M -->|Yes| F[Git merge --ff-only in owner checkout]
  M -->|No| U[Compare-and-swap update-ref]
  F --> J[Report refresh result]
  U --> J
  W --> J
Loading

Reviews (8) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..."

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: e627f879-a490-48a8-96b2-501b63c59249

📥 Commits

Reviewing files that changed from the base of the PR and between e5011cd and 52e48a8.

📒 Files selected for processing (7)
  • src/main/git/worktree-base-refresh-lock-real-git.test.ts
  • src/main/git/worktree-base-refresh.ts
  • src/relay/git-handler-local-base-ref-refresh.ts
  • src/relay/git-handler-worktree-provisioning.test.ts
  • src/shared/coalescing-keyed-runner.test.ts
  • src/shared/coalescing-keyed-runner.ts
  • src/shared/worktree/local-base-branch-fast-forward.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/shared/worktree/local-base-branch-fast-forward.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds shared local-base inspection and fast-forward operations, including lock-contention retries and per-key refresh coordination. Local and SSH worktree creation now start eligible refreshes alongside worktree addition and await their outcomes. The relay exposes inspection and refresh operations, and the renderer keys refresh-failure notifications by repository and local branch.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 52e48

The change refreshes the local base branch safely during worktree creation. The supplied evidence shows no remaining merge-blocking issue. The earlier concern about test isolation from global Git config appears fixed, because the test now stubs those settings.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 52e48

Normal branch updates gain stronger protections against overwriting work. However, read-only checks from older clients may instead update a branch during mixed-version upgrades.

Retained concerns

  • Medium · security · inferred: The existing refresh RPC no longer honors checkOnly:true. The base client sends this flag while generating a suggestion with automatic refresh disabled; the new relay instead schedules a mutating fast-forward. If older clients can connect to the new relay, a read-only request becomes a branch and possibly checkout update without the requested write intent. Current clients avoid this through the new inspection RPC, but a connection-level version barrier was not established.
Security review details

Security Blast Radius

  • inferred — A refresh can modify the supplied local branch and its owning checkout through the execution host's Git process. The repository/ref selection authority predates this PR. The maximum accessible repository set and authenticated caller population were not established, so exposure cannot be bounded to a particular tenant or selected workspace.

Security Findings and Attack Paths

  • inferred — The supported introduced concern is loss of read-only intent across versions: an older client requests a suggestion with automatic refresh disabled, sends checkOnly:true, and the new relay ignores that flag before reaching branch mutation. This requires an admitted mixed-version client and a fast-forwardable branch; it does not establish privilege escalation or remote code execution.

Trust Boundaries and Controls

  • observed — The relay validates string types, local/remote ref namespaces, and Git ref syntax. Independent repository and ref inputs were already accepted by the base implementation; no newly introduced repository-binding bypass is established. The new policy discovers the owning worktree host-side rather than accepting its path from the current client.

Resilience and Maintainability Implications

  • observed — Checked-out updates recheck HEAD before each merge attempt and verify the requested branch afterward. The source acknowledges a remaining branch-switch window between those operations. The base reset also acted on the owner checkout's current HEAD, so this residual ownership race is not treated as a newly introduced concern.

Hardening Proposals

  • proposed — Preserve legacy checkOnly requests as inspection-only, or reject incompatible refresh requests before mutation through an explicitly versioned contract. A client-side response parser cannot restore read-only intent after a server has already written state.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.87% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 77 functions across 37 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: safe, per-branch local main updates during checkout.
Description check ✅ Passed The description is comprehensive and follows the required structure. It explains the user impact, implementation, rationale, testing, visual proof, review notes, compatibility considerations, and know…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

Read the full 15-file diff end to end, then validated the runtime behavior against the relay and renderer consumers and ran the touched/adjacent suites.

  • Overlapped refresh — resolveWorktreeAddBaseContext now starts the local base refresh and returns it as pendingLocalBaseRefRefresh instead of awaiting it, so git worktree add runs concurrently from the remote-tracking commit. performAddWorktree, finalizePreparedWorktree, and SSH createRemoteWorktree each settle the promise on their exit paths, including failure/rollback paths.
  • One refresh per repo — the new keyed-serial-runner serializes refreshes for the same repo (and the same SSH connection+path) with a 30s max wait; the Codex home lock now uses the same runner.
  • Lock-contention retry — git-lock-contention.ts retries git lock errors only (not CAS "is at X but expected Y"), re-checks owner cleanliness per attempt, and treats "local already at target" as success rather than a warning; the SSH path retries the relay call with the same already-current check.
  • Already-current and self-branch creates — an up-to-date local base now returns undefined (no owner check, no reset, no toast), and a create whose -b is the base's own local branch skips the refresh entirely.
  • Tests — new contention, real-git lock, SSH-overlap, runner, and lock-matcher suites; existing refresh/preparation suites updated for the overlap.

Verification this run: the 12 touched/adjacent test files pass, pnpm tc:node and pnpm run check:code-quality:changed are clean, and I confirmed the SSH lock matcher is not theatre — the relay error is reconstructed client-side from msg.error.message, which carries git stderr, so isGitLockContentionFailure sees real lock text.

The two documented limits (queue-wait gating the create return; case-insensitive filesystems not recognizing Feature-X as the self-branch) are real and correctly called out in the PR description rather than hidden.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 force-pushed the brennanb2025/create-local-base-refresh branch from 3564650 to e616bc1 Compare September 29, 2026 05:33
@brennanb2025
brennanb2025 marked this pull request as draft September 29, 2026 05:33
@brennanb2025 brennanb2025 changed the title fix(worktree): refresh local main alongside the checkout and stop false 'not refreshed' warnings fix(worktree): update local main safely, once per branch, alongside the checkout Sep 29, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

Re-read the full 31-file diff end to end (no incremental diff was computable because the prior head 3564650 was never fetched into this shallow checkout). The head commit replaced the previously reviewed design wholesale, so the delta is effectively that redesign:

  • Unsafe move replaced with Git's own fast-forward — a checked-out branch now moves with git merge --ff-only under Git's own index lock (hooks, auto-gc, autostash off, after re-confirming symbolic-ref HEAD), which refuses to overwrite an edit/untracked file or drop a commit; a free branch moves with a CAS update-ref carrying a reflog message. Failure classification is exact (would be overwritten by merge → dirty, Not possible to fast-forward → not-ff, else error), and a failed move where local already contains the target reports updated.
  • One policy, running where Git runs — src/shared/worktree/local-base-branch-fast-forward.ts is injected into the main process (local/WSL) and the relay; the app-side SSH duplicate of the checks, queue and retry is deleted, along with worktree-base-refresh-analysis.ts.
  • One coalescing run per repo and branch — coalescing-keyed-runner.ts gives a leading run plus one shared trailing run that uses the latest requester's work; creates wait at most 30 s and never start a competing mutation. Lock contention retries on .lock: File exists only, deliberately not on the CAS is at X but expected Y mismatch.
  • Overlap — the refresh is started (not awaited) in resolveWorktreeAddBaseContext and settled in performAddWorktree, finalizePreparedWorktree, and the SSH createRemoteWorktree finally before any sparse rollback; the checkout always builds from the remote-tracking ref, so it never depended on local base.
  • False-alarm reductions — already-current bases return nothing_to_do with no owner check or toast, self-branch creates skip the refresh, status reads use --no-optional-locks, and the warning toast is keyed by repo+branch.

I traced the failure/retry/coalescing paths and the relay wire handling (parseLocalBaseBranchFastForwardOutcome rejects unrecognized replies; relay runGitToTermination preserves numeric code/stderr, so isShowRefNoMatchError and the lock matcher behave the same as local). The new real-Git and contention suites assert exact arguments and statuses and would fail against the old reset --hard path.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/git/worktree-base-refresh.ts Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 0dfaeeb8-61cf-46d7-a158-b7121a5fd142

📥 Commits

Reviewing files that changed from the base of the PR and between 3564650 and e616bc18884eefe187c98573e4fc867af77d93c0.

📒 Files selected for processing (28)
  • src/main/git/exact-ref-probe.ts
  • src/main/git/worktree-add-local-base-refresh.test.ts
  • src/main/git/worktree-add-local-base-suggestion.test.ts
  • src/main/git/worktree-add.ts
  • src/main/git/worktree-base-refresh-analysis.ts
  • src/main/git/worktree-base-refresh-contention.test.ts
  • src/main/git/worktree-base-refresh-lock-real-git.test.ts
  • src/main/git/worktree-base-refresh.ts
  • src/main/git/worktree-preparation-base-oid.test.ts
  • src/main/ipc/worktree-remote.ts
  • src/main/ipc/worktrees-ssh-local-base-refresh-overlap.test.ts
  • src/main/ipc/worktrees-ssh-local-base-refresh.test.ts
  • src/main/providers/ssh-git-provider-api.test.ts
  • src/main/providers/ssh-git-provider-worktree.test.ts
  • src/main/providers/ssh-git-worktree-provider.ts
  • src/relay/git-handler-local-base-ref-refresh.ts
  • src/relay/git-handler-registration.ts
  • src/relay/git-handler-worktree-operations.ts
  • src/relay/git-handler-worktree-provisioning.test.ts
  • src/relay/git-handler.test.ts
  • src/renderer/src/store/slices/worktrees-create-base-status.test.ts
  • src/renderer/src/store/slices/worktrees/create/local-base-ref-refresh-toast.ts
  • src/shared/coalescing-keyed-runner.test.ts
  • src/shared/coalescing-keyed-runner.ts
  • src/shared/git-show-ref-no-match.ts
  • src/shared/worktree/local-base-branch-fast-forward-real-git.test.ts
  • src/shared/worktree/local-base-branch-fast-forward.test.ts
  • src/shared/worktree/local-base-branch-fast-forward.ts
💤 Files with no reviewable changes (1)
  • src/main/git/worktree-base-refresh-analysis.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

… branch itself

Creating a workspace named feature-x from origin/feature-x runs `worktree add -b feature-x`,
which now overlaps the refresh. The refresh's drift probe could see refs/heads/feature-x
missing and its presence probe then see it (the add just wrote it), which reported
"not fast-forward" and showed a sticky "Local feature-x was not refreshed" warning.
`-b` refuses an existing branch, so there is nothing to refresh in that case: skip it on
the local, prepared-checkout and SSH create paths.

The SSH overlap tests move to their own file so the existing suite stays under the line limit.
…-only per branch

Moves the whole local base refresh into one shared routine that runs on the
execution host (main process for local and WSL repos, the relay for SSH), so
the app no longer keeps a second copy of the checks, queue and retry.

A checked-out branch now moves with merge --ff-only (hooks, auto-gc and
autostash off) instead of status-then-reset --hard, which silently overwrote an
untracked file the new commit adds and could discard an edit or a commit made
after the check. A free branch moves with a compare-and-swap update-ref that
writes a reflog message. Status reads no longer take index.lock.

Creates of one branch share one run plus at most one trailing run; a create
waits at most 30 s and never starts a competing mutation. The failure toast is
keyed by repo and branch because every create that joined a run reports the
same fact.
…gned merges

With merge.verifySignatures=true, the owner-checkout fast-forward refused an
unsigned origin/main tip, so every create warned "Local main was not
refreshed" where the old reset moved main. The new workspace is already
created from that same unsigned commit, and a branch that is not checked out
moves without a signature check, so the refusal protected nothing. Turn the
setting off for this one merge, like the hooks, gc and autostash overrides.
…lands late

The update of local main can finish after a create stopped waiting for it, so
the shared run now invalidates git read caches itself. The index.lock real-git
test also no longer reads the developer's global git config.
…ocal main

A plain `git merge --ff-only` silently replaces an ignored file (for example a
local `.env`) at a path the new commit starts tracking. Pass
`--no-overwrite-ignore` so git refuses instead and the create reports the
checkout as having local changes. Supported on the fast-forward path since
well before Git 2.25.

Also make the relay test for one-refresh-per-branch hold the first merge until
the second request has reached the relay, so it fails without the coalescing.
…er the user's merge settings say

A per-branch mergeOptions such as '-s ours' or '--squash', or pull.twohead=ours,
made the update create a merge commit that dropped upstream, or stage upstream
without moving main, while reporting success. The command now clears the
branch's mergeOptions and passes the strategy and signature choice on the
command line, which beats any config. After the move Orca confirms local main
is exactly the target before reporting it updated. The exact command also runs
in the Git 2.25 compatibility suite.
… rerun on every change to it

The new real-Git contract for the local main fast-forward wrote a post-merge hook into .git/hooks, which does not exist when the repo is created by the uninstalled Git 2.25.5 build CI uses (no templates), so the Git compatibility check failed. Create the directory first.

The Git compatibility check also did not run when only the fast-forward module changed, so a later edit to its merge arguments (for example a flag Git 2.25 lacks) would skip the one check that tests them. Add the module to the check's paths.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/create-local-base-refresh branch from 71af170 to 3613960 Compare September 29, 2026 06:50
@brennanb2025

brennanb2025 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Review status: ready (head 0e81766)

The problem. With "keep local main up to date" on, two workspace creates at the same time showed a false "Local main was not refreshed" warning, even though main ended up correct. The old refresh (check git status, then git reset --hard) could also silently overwrite an untracked or ignored file, or drop an edit or commit made between the check and the reset. This review round also found that creates from two different remotes' main at the same moment (for example origin/main and upstream/main in a fork) could still get a false warning, or no result at all, because they shared one update meant for only one of them.

What changes for the user. Creates at the same time no longer warn falsely, including creates from different remotes' main. Every create reports the result for the base it was created from. The refresh runs alongside the checkout, so creates aren't slower. Orca never overwrites your files or commits to update main: if it would, git refuses and you get a warning instead.

Fixed in this round

  • Brought up to date with main (e5011cd). The only conflict was a method-name list test that main deleted on purpose; the method this PR adds is covered by a behavioral test.
  • Creates from different remotes' main at the same moment now each get an update toward their own base (52e48a8). Only one update of a branch ever runs at a time; creates toward the same remote share one queued update. Real-git tests for both the app and the SSH relay fail without this fix, and also fail if the two updates are allowed to run side by side.
  • The comment on the branch re-check before the move now says honestly that it narrows, but can't close, a branch-switch window; the check after the move reports any mismatch as a warning, never as "updated" (52e48a8).
  • Tests now also assert the third create's result in the mixed-remote cases; a stale toast comment is corrected; the PR body describes the new rule (822bad1, 0e81766).

Checked and not changed

  • "Older relays report false failures": this mix can't happen. The app only ever talks to an SSH relay built from its own bundle; the relay version is a content hash, each version has its own install folder and socket, and the relay closes any connection from a different version. Remote Orca servers, where versions really can differ, get an unchanged result shape.
  • "Retries skip the cleanliness check": a retry only happens after another process held git's lock, so nothing was touched; merge --ff-only keeps unrelated edits and refuses edits to files it changes, like git pull --ff-only.
  • Test fixture isolation from the user's git config was already done and re-verified with a hostile global and system config.

Deferred

  • A create from origin/main warns "cannot be fast-forwarded" when local main is already ahead of origin/main (for example, just moved to upstream/main), although nothing is wrong. This rule already exists on main; it's listed under Known limits, with a follow-up to report "nothing to do" when local main already contains the target and warn only when they have truly diverged.
  • Unchanged from before: a wedged merge makes later creates wait the full 30 s until restart; a later "updated" doesn't clear an earlier warning toast; small cleanups (a re-export shim, unfinished rebase states on the update-ref path).

Verified

  • CI at 0e81766: 44 pass, 7 skipped, 0 failing. One end-to-end SSH terminal-restore test failed once; it never creates a workspace, and it passed on rerun.
  • Targeted unit and real-git tests: 261 passed.
  • Live app on a separate Mac with an isolated profile, built from the final code: two creates at once no warning 3/3; untracked file kept; creates from upstream/main and origin/main at once never warned for upstream/main and ended at upstream/main in 6 of 6 runs. The same burst on the code before this fix reproduced the false result in 1 of 3 runs.

Two creates at once, no warning

Creates from two remotes at once, no warning

Not verified

  • No live SSH-remote or Windows run this round (the relay path is covered by real-git relay tests).
  • The Git 2.25 contract runs only in CI's Git compatibility check, which passed; it wasn't run locally.
  • The live app check used a Claude agent, because the planned Grok agent's account balance was used up.

@brennanb2025
brennanb2025 marked this pull request as ready for review September 29, 2026 07:35

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ Minor suggestions only.

Reviewed changes

Read the full 34-file diff at 3613960 end to end. The prior pullfrog review was at e616bc1; the branch was rebased after it, so this covers the whole head range ed4da90…3613960, with the newest commits being the merge-settings/ignored-file/signed-merge hardening and the Git 2.25 CI contract.

  • One shared refresh policy on the execution host — src/shared/worktree/local-base-branch-fast-forward.ts moves local main with git merge --ff-only when it is checked out, or a compare-and-swap update-ref when it is free; the app-side SSH copy of the checks, queue and retry is deleted.
  • One run per repo+branch — new src/shared/coalescing-keyed-runner.ts collapses a burst into a leading run plus one shared trailing run; the main process and the relay each own one.
  • Overlapped with the checkout — the refresh is started, not awaited, and settled on every exit path (worktree-add.ts, worktree-create-preparation.ts, worktree-remote.ts); the relay now performs the whole refresh host-side and returns the outcome.
  • Safety hardening — --no-overwrite-ignore, cleared branch.<name>.mergeOptions, --no-verify-signatures, hook/auto-gc/autostash suppression, a post-move exact-OID check, --no-optional-locks status, lock-contention retry, and a repo+branch-keyed warning toast.

I checked the move command against Git's manual and source (--no-overwrite-ignore, --no-verify-signatures, the -c branch.<name>.mergeOptions= parse for dotted and slashed branch names, and the refusal messages the classifier matches), and the real-git tests carry live controls, so they can fail against the old command. One residual race and one leftover are below.

ℹ️ Nitpicks

  • probeWorktreeBaseRefPresence (src/main/git/worktree-base-ref-probe.ts:101) lost its last production caller in this PR — worktree-remote.ts now routes through the shared routine — and survives only via its unit test. Consider deleting it (and its isShowRefNoMatchError import) or leaving a note on why it is retained.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

…ocal-base-refresh

# Conflicts:
#	src/main/providers/ssh-git-provider-api.test.ts
…s own base

Creates from different remotes' main (origin/main and upstream/main) shared one queued update
per repo and branch, which ran only the latest caller's target: a create could get no result
for its own base, or a false "not refreshed" warning computed for another remote's main.

The per-branch runner now queues one run per distinct target, still one at a time per branch,
and only callers toward the same target share a queued run. Applied in the app and the relay.
Comment on lines +33 to +40
const joinable = queue?.find((run) => run.shareKey === shareKey)
if (joinable) {
joinable.work = work
return joinable.result
}
const run = createQueuedRun(shareKey, work)
if (queue) {
queue.push(run)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Already-contained target triggers warning

When overlapping creates request origin/main, then a newer upstream/main, then origin/main again, this queue runs all three targets in order. If upstream contains origin, the upstream run moves local main past origin/main. The final origin run then reports skipped_not_fast_forward, showing a “Local main was not refreshed” warning even though local main already contains that create’s requested base.

…burst tests and update the toast id rationale

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found. The new commits keep the fast-forward correct and make every create answer toward its own base.

Reviewed changes

Reviewed the delta since the prior pullfrog review at 3613960 (head is now 52e48a8): one substantive commit plus the earlier merge-only sync. The substantive commit reworks the coalescing runner so a create is never answered with a different remote's refresh outcome.

  • Coalescing runner now shares per shareKey — createCoalescingKeyedRunner gained a third shareKey argument: a caller joins a queued (not-yet-started) run with the same shareKey and the run uses the latest joiner's work; otherwise it queues its own run. Runs drain in arrival order, so a burst costs at most one run per distinct shareKey beyond the one in flight, and distinct remote targets no longer overwrite each other's work.
  • Refresh callers pass the remote-tracking ref as the share key — worktree-base-refresh.ts and the relay's git-handler-local-base-ref-refresh.ts pass remoteTrackingRef, leaving the per-repo+branch key unchanged so moves of one local branch still never overlap.
  • Tests — real-git fork-repo cases for two remotes (each asserting maxConcurrent === 1 and exact outcomes, and each failing against the old shared-trailing-run behavior), relay provisioning cases holding the first merge open while later clients queue, and runner unit cases for cross-shareKey ordering/bounding.
  • Comment honesty — the owner fast-forward comment now states the symbolic-ref re-check narrows but cannot close the branch-switch window, retiring the prior review's residual-race thread.

I traced the runner's queue/drain interaction (a run is only joinable while queued, queues.delete runs synchronously after the loop drains, settled swallows rejections) and found no interleaving that drops a queued run or runs two moves of one branch concurrently. The new tests discriminate against the previous behavior rather than passing under it.

ℹ️ The PR description's cross-remote "Known limit" is now stale

The "Known limits" bullet still says two creates from different remotes "share one update, which uses whichever request came last." This commit removes exactly that behavior, so the description now understates the design and contradicts the code.

Technical details
# Stale PR description for the cross-remote refresh behavior

## Affected sites
- PR body, section `## Known limits`, final bullet — states creates toward different remotes "share one update, which uses whichever request came last".
- `src/shared/coalescing-keyed-runner.ts` — now keys sharing by `shareKey` (the remote-tracking ref), so creates toward different remotes queue independent sequential runs.

## Required outcome
- The description should describe the current behavior: one sequential run per distinct remote target, with local ending at the last-arriving target; a create toward a remote that local has already moved past then reports `skipped_not_fast_forward` rather than being answered with another remote's result.

## Open questions for the human
- Is the third-create warning in `worktree-base-refresh-lock-real-git.test.ts:176` (a create toward `origin/main` after local moved to `upstream/main`) the intended signal, or should an already-contains-target branch read as `nothing_to_do` instead of `skipped_not_fast_forward`?

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

…ocal-base-refresh

# Conflicts:
#	src/main/git/worktree-add-local-base-refresh.test.ts
#	src/main/git/worktree-add.ts
#	src/main/ipc/worktree-remote.ts
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.

1 participant