* fix(persistence): worktree meta and lineage writes take the row's host, not the id (R4-2b)
* fix(persistence): review fixes for host-targeted worktree writes
* test: SAFETY notes on partial runtime store mocks
* test: drop imports duplicated by the rebase onto #30797
requireRuntimeGitProvider now returns a provider for the local host too (the
local git provider, built per call from the worktree's WSL options and shared
link paths), so the runtime git commands drop their "SSH provider, else local
free function" branches. Push-target materialization stays host-specific and
moves into one helper. Branch-diff keeps its two arms because the local git
function returns one pinned diff, not the contract's list.
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
`RuntimeGitTarget` carried `connectionId?: string` and no host id, so `undefined`
spelled three different answers at once — "runtime: host", "unresolved", and
"genuinely local". Its sole resolver read `store.getRepo(worktree.repoId)?.connectionId`
and never looked at `worktree.hostId`, which outranks every repo row, so one
arbitrarily chosen row decided the execution host for 36 downstream dispatches.
The target now carries `executionHostId: ExecutionHostId` (never null, never
optional), resolved through the shared rule that landed with #17909/#17919 and
dispatched through the host-keyed routes from #18296. Dispatch sites call
`requireRuntimeGitProvider`, where `null` means exactly one thing: the host is
`local` and the command runs here as free functions.
Four answers that used to collapse into one:
- `ssh:x` with a rival row on `ssh:y` — routes to x. Previously the first row won,
which is the reproduced cross-host leak.
- `local` with a surviving `connectionId` — a row contradicting itself; no SSH
connection is handed out.
- `runtime:<env>` — throws `ExecutionHostNotDispatchableError`. Its repo row's
connection names a target in the *server's* namespace; dialling it here reaches a
same-named target on this client.
- rival rows disagreeing with no worktree host — `worktree_execution_host_unresolved`,
matching the launch path rather than guessing a row.
An unreachable SSH host still throws `SSH_GIT_PROVIDER_UNAVAILABLE_MESSAGE`; loss of
contact is never evidence of locality (docs/reference/ssh-execution-boundary.md).
`resolveWorktreeLaunchHost` keeps its exact signature and now delegates to
`resolveWorktreeHostRouting`, the same resolution answering "which host is this on"
rather than "what may this client dial" — the git target needs the first question
because `local` and `runtime:` are two different non-SSH answers.
No wire change: `RuntimeGitTarget` is main-process internal, and the SSH and local
model-discovery host keys are byte-identical to before.
`RuntimeFileTarget` has the same defect in ~30 filesystem dispatches and is
deliberately left for a follow-up.
* perf(worktree): defer fork-PR remote creation from create-time to first use
Fork-PR review worktrees eagerly ran `git remote add` + `git fetch` for the
contributor's fork (and pinned branch.<x>.remote) at create time, even for a
read-only review. That grows remote count unboundedly with review volume and
pays a network fetch nobody asked for yet.
Defer prepareWorktreePushTarget(Ssh) and the --set-upstream-to configure step
at create time (local + SSH, IPC + runtime create paths); persist the
pushTarget metadata untouched. Materialize the remote on demand the first
time push/pull/fetch/fast-forward actually needs it, via two shared
functions (materializeWorktreePushTargetRemote(Ssh)) reused across the
legacy IPC handlers and the RPC runtime sync commands. A cheap
`remote get-url <name>` probe keeps steady-state calls down to one extra
subprocess once materialized, instead of repeating the O(remotes) scan.
Add repo-local `remote.<name>.orca-created` config provenance, written when
the remote is added, so cleanup can recognize ownership of a remote that was
lazily materialized (and therefore never round-tripped through the store's
`remoteCreated` flag).
Refs #17828
* perf(worktree): materialize a deferred fork-PR remote on terminal spawn
An agent running raw git in a freshly opened fork-PR review terminal has no
usable upstream until an Orca-driven sync happens -- "sync through Orca
first" isn't available mid-task, and git pull/log @{u}.. hard-fail without
one (verified against real git). Fire the same on-demand materialization
used by push/pull/fetch/fast-forward from the single terminal-spawn
resolver (resolveTerminalWorkspaceLaunchTarget), fire-and-forget, so a
newly opened terminal gets a working upstream without blocking spawn.
* fix(worktree): retest deferred fork-remote CI failures, fix SSH provenance-marker RPC
Rewrites the 5 CI failures on the deferred fork-remote change (#17828) as
evidence, not fixtures: the SSH relay-upgrade/rollback/sibling-ownership
tests move to materializeWorktreePushTargetRemoteSsh, where that
unchanged logic now actually runs (create defers it to first sync).
While writing a stricter test that routes its mock exec through the
relay's real validateGitExecArgs, found that the SSH provenance-marker
write (`git config remote.<name>.orca-created true`) was unconditionally
rejected by the relay's generic git.exec (it blocks all non-read-only
config writes) -- a real bug that would break every SSH fork-remote
materialization against a live relay. Fixes it with a narrow
git.markRemoteOrcaCreated RPC, mirroring renameCurrentBranch, with a
graceful no-op fallback for relays that predate it.
* fix(worktree): scope post-#17887 test assertions past narrow-refspec config calls
Rebasing onto #17887's narrow-refspec `remote add` broke two broad `['config']`
call-filters into false positives/negatives, and the local materialize test still
asserted the pre-#17887 wide `remote add`/fetch-refspec forms.
* fix(worktree): restructure upstream restore, persist provenance, widen short-circuit refspec (#17828 review)
- Move upstream restoration to the materializer level so it runs on both the
remoteAlreadyMatchesUrl short-circuit and the full-prepare path, not just
buried inside prepare*.
- Persist {remoteCreated, remoteName} to the store on materialize so #17842's
orphan sweep can see a lazily-created remote, including via desktop IPC,
terminal-spawn, and the RPC host-callback paths.
- Widen the refspec on the local short-circuit path too (SSH's bare `remote
add` refspec gap remains a documented, pre-existing limitation).
- Fetch the branch's tracking ref before restoring upstream when the
short-circuit widens onto a *new* branch on an already-existing remote --
a bare refspec-config widen never itself imports anything, so
`branch --set-upstream-to` was hard-failing for a sibling worktree's first
materialize (found via a real-git fixture, not just mocked unit tests).
Skipped when the ref already exists so the common repeat-call case stays a
local-only probe with no network round-trip.
* fix(worktree): merge duplicate shared/worktree/types import
oxlint --deny-warnings flags the split import as no-duplicates; full pnpm lint
was failing on it after the #17828 review restructuring.
* fix(worktree): scope the deferred fetch timeout to fetch calls, retarget stale create-time assertions
CI on the previous push failed 3 shards, all argument-shape mismatches:
- worktrees-wsl-runtime-routing.test.ts: the "restructure upstream restore" commit
wrapped every call `prepareWorktreePushTarget` makes (remote, remote add, config,
fetch) with DEFERRED_PUSH_TARGET_FETCH_TIMEOUT_MS, not just the network fetch. Local
git subprocesses never need a timeout; scope it to `args[0] === 'fetch'` only,
matching the short-circuit path's existing pattern. Updated the test to expect the
timeout on the fetch call specifically (point 5 legitimately adds it there), while
every other call stays untimed.
- worktrees-create-metadata-persistence.test.ts (2 tests): stale from before this
session -- create no longer mints a fork remote at all (#17828 deferred that to
first sync), so asserting `remote add`/`fetch`/`remoteCreated: true` at create time
no longer matches reality. Retargeted both tests to assert the deferred contract
(no remote add at create, pushTarget persisted unmaterialized); minting itself
stays covered by worktree-remote-push-target-materialization.test.ts and
worktree-push-target-setup.test.ts.
Re-verified all 5 fixture points (mint upstream, store persistence, single-flight,
short-circuit refspec widen + fetch-missing-ref for local and SSH, finite timeout)
against a real git fixture after this fix -- all still pass.
* fix(worktree): hook pty:spawn into deferred push-target materialization (#17828)
triggerTerminalSpawnPushTargetMaterialization only fired for agent/background/
mobile terminals; the desktop GUI's own pty:spawn path (new tab, split,
reattach) never materialized a deferred fork-PR remote before raw git
commands could run there. Add a small wrapper that resolves the worktree's
push target and owning repo from args.worktreeId via the store, and
fire-and-forget delegates to the existing materializer, wired as the first
statement of runPtyIpcSpawn. Degrades silently (optional chaining + catch)
so a partial/fake Store in existing spawn tests can't turn this into a
spawn-blocking throw.
* test(worktree): retarget stale editor-remote-branch assertions for worktreeId threading
runtime-git-sync-client's local-path fetch/pull/fastForward/push calls now
forward context.worktreeId (needed by the main-process handlers to key
deferred push-target materialization). Update the 17 call-site mocks across
15 tests in editor-remote-branch-actions.test.ts to expect worktreeId: 'wt-1',
matching the already-correct source behavior -- no assertion was loosened.
* fix(worktree): give a materialize joiner its own branch wiring
The materialize single flight is keyed on the remote, but everything after
the remote add is per-branch. A sibling worktree joining an in-flight mint
for a different branch received the minter's target and skipped its own
refspec widen, tracking-ref fetch, and upstream link, so its branch ended
with no upstream at all.
Wait for the remote, then run the per-branch work against the joiner's own
target -- the same path the already-exists short-circuit takes, now shared
rather than duplicated. Adopting a remote a sibling minted also stamps
ownership, so removing the minter cannot strand the survivor's metadata
outside the orphan sweep's reach.
* fix(worktree): stop a failed mint from leaving a config-only fork remote
Review of the joiner fix found it made things worse in three ways.
Swallowing the mint's rejection let a joiner adopt a remote the rollback
had already removed, writing remote.<name>.fetch with no URL. Verified on
real git: that ghost section breaks `git fetch --all`, forces every later
mint to a `-2` name, and cannot be removed by `git remote remove`.
Propagate instead; the in-flight map is already cleared, so a retry
re-mints.
The SSH twin still returned the minter's target to a joiner, so the
original per-branch bug survived there. It now adopts against its own
target through a twin helper.
The ownership stamp was unreachable: it required both a store and a repo
id, and no caller passes both. Derive the repo id from the worktree id.
Adopters also write remote config, and concurrent `git config --add` has
no lock retry -- 135 of 160 writes failed at 8-way concurrency, and equal
values duplicate the refspec. Chain adoptions per remote.