Commit Graph
19 Commits
Author SHA1 Message Date
Neil 55ae3b393c fix: make git grep directory filters recursive 2026-09-16 16:55:29 -07:00
Neil a63a4579cf Let worktree creation proceed during stale preparation reclamation (#18967)
* Stop obsolete worktree preparations when evicted or expired

* Let worktree preparation proceed during stale reclamation

* Verify creation during stalled stale worktree reclamation

* Preserve preparation ownership until Git removal starts

* test: keep artifact share fixtures unexpired across calendar dates (#18955)
2026-09-05 22:30:01 -07:00
Neil d7767fb196 perf(worktree): remove redundant creation and terminal startup work (#18793)
* perf(worktree): remove redundant creation and terminal startup work

* test(worktree): cover optimized creation call signatures

Preserve explicit branch adoption, WSL callback routing and sparse cleanup expectations.

* perf: preserve user Git checkout worker settings

* perf(git): skip malformed remote base probes

* perf(cli): avoid loading other agent hooks for Codex preflight

* fix(build): retain Codex preflight entry for packaged CLI

* test(ssh): wait for replacement PTY before lease recovery input

* test(ssh): verify recovered shell execution and lease ownership

* test(electron): reap isolated macOS crash reporters on teardown

* test: allow either observed self-exit snapshot ordering

* test: capture frozen-host input recovery evidence
2026-09-05 15:08:22 -07:00
Neil 53adf5e2e6 fix(git): share one failed-command error-text reader between local and the SSH relay (#18398)
* fix(git): share one error-text reader between the local and relay branch-delete fallbacks

The relay and the desktop each carried their own `getErrorText`, and they had
drifted: the relay read `message` + `stderr` + `stdout`, the desktop only
`message` + `stderr`. A `git branch -d` refusal arriving on `stdout` therefore
routed the SSH removal through prune-and-retry while the local removal gave up
and preserved the branch.

Against a real binary the two agree, because Git prints the refusal through
`error()` on every supported version — verified on 2.25.1, 2.38.1, 2.49.1 and
2.55.0, none of which put a byte of it on stdout. What the desktop copy actually
missed is that Orca classifies errors it built itself, with the Git output on
`.stdout`: `worktree remove`'s submodule retry attaches `git status --porcelain`
that way on both paths. The stdout-reading form is also already the shared
spelling — `isSubmoduleWorktreeRemovalRefusal` uses it for both hosts — so this
converges on it rather than on the shorter one.

Move the reader to src/shared/git-command-failure-text.ts and the predicate it
feeds to src/shared/git-branch-delete-refusal.ts, and delete all three copies.
The predicate carries both refusal wordings live in the supported range: Git
through 2.40 says "checked out at", 2.43+ says "used by worktree at".

The real-binary contract now pins that boundary: the refusal is recognized, it
lands on stderr, and stdout stays empty on every Git in the matrix.

* fix(test): consolidate the duplicate worktree import in the parity test
2026-09-03 14:42:54 -07:00
Neil 104f9655e4 perf(git): answer remote-URL questions from one subprocess, not one per remote (#18158)
Four copies of the same loop ran `git remote` and then a serial
`git remote get-url <name>` per remote to answer "which remote has this
URL". On a repo with 58 remotes that is 59 subprocesses -- measured at
1083 ms -- for one question, and worktree create asks it several times.
`git remote -v` answers for every remote from one child, reporting the
same insteadOf-expanded first fetch URL `get-url` prints.

The batched `cat-file --batch-check` branch-conflict probe decides from
stdout, but its WSL route was unfenced, so a login-shell fallback printed
the distro banner onto the stream it parses. That broke the
one-line-per-ref contract, made every batch undecided, and fell straight
back to one `show-ref` per remote -- the cost the batch exists to remove.

Measured at 58 remotes / 4346 branches, spawns and wall time:
  push-target remote scan      59 -> 1  (1083 ms -> 8 ms)
  branch-conflict probe        60 -> 3  (984 ms -> 43 ms)
  configured push target      123 -> 6  (2707 ms -> 157 ms)
2026-09-02 12:53:48 -07:00
Neil 6c8eea5ebe perf(worktree): fix the prepared-checkout hit rate and make misses visible (#17863) 2026-09-01 20:26:00 -07:00
Neil d7123591ce perf(git): pack the loose refs Orca's own fetches leave behind (#17857)
* perf(git): pack the loose refs Orca's own fetches leave behind

Orca strips git's auto-maintenance off every fetch it issues
(GIT_FETCH_SKIP_AUTO_MAINTENANCE_CONFIG_ARGS) and never compensated, so
nothing in an Orca-driven checkout ever packs refs. One real machine
reached 36,574 loose refs, where `git show-ref -- main` costs 5.2s and
every worktree create pays for it.

Add an idle-time, per-repo `git pack-refs --all --prune`, armed by the
fetches that create the debt. It runs only after ten minutes of quiet on
that repo, only above 1000 loose refs (probed with a walk bounded by that
threshold, not by the backlog), one at a time across the whole app, at
the background admission tier, and never while an agent is working, a
create is prepared or in flight, a worktree removal is deleting refs, the
app is quitting, or the machine is on battery. A user who set
`maintenance.auto=false` or `gc.auto=0` has opted out.

Measured on a 36,001-loose-ref fixture (macOS/APFS, git 2.44):
`show-ref` 5.5-12.2s -> 30-49ms, `for-each-ref` 4.0-10.8s -> 43-48ms.

Also fixes a pre-existing bug the split exposed: `--path-format=absolute`
is ignored before git 2.31, and taking rev-parse's stdout raw collapsed
every repo on such a host onto one fetch-serialization key.

Refs #17828

* perf(git): make idle ref maintenance preemptible and cheaper to probe

The idle veto was one-directional: it stopped a pack from starting during
a create, removal, or agent work, but nothing stopped those from starting
during a pack. A user-clicked Fetch, a branch delete, or a worktree
removal that needed `packed-refs.lock` mid-rewrite could fail with
`unable to create packed-refs.lock` -- a git error with no visible cause.

Make the pack cancellable end to end. An AbortSignal now reaches the
`pack-refs` child and both pre-pack probes, and `pause()` aborts what is
running, waits for it to actually stop, and holds a suspension count so
nothing new starts until the caller releases. Every entry point that
deletes a ref takes that pause: gitFetch, gitPull, gitFastForward,
removeWorktree, forceDeleteLocalBranch, prepareWorktreeCreateCheckout,
addWorktree. Five more triggers close the rest of the window: battery
drop, window focus, quit, the attempt deadline, and any other git command
queueing for an admission slot.

Judge a pack by re-probing the backlog rather than by the child's exit
code. Measured in the field: another Orca session moved a branch
mid-pack, git reported `cannot lock ref`, skipped that ref and packed the
rest -- 36,688 loose refs down to 3. On a machine running several
sessions that is the normal case, and retrying it would be wrong.

Probe with one batched `readdir` per directory instead of streaming
`opendir`, which issues a thread-pool round trip every 32 entries: 177ms
-> 23ms on a real 36,600-ref repository, with half the event-loop lag.
The walk stays strictly sequential so it can never occupy more than one
of libuv's four filesystem threads.

`PackRefsLockOwnership` makes a lock left by SIGKILL attributable, and
only reclaims one when a marker exists, the lock is older than any
pack-refs could run for, and the recorded process is gone.

Refs #17828

* fix(git): wait out the packed-refs lock instead of killing the pack

Measured on Git 2.55/APFS with 37k loose refs: a full `pack-refs --all
--prune` takes 23-32s but holds `packed-refs.lock` for only 0.03-1.37s of
it. The other ~95% is the prune phase, during which a concurrent `fetch
--prune`, `branch -D` or `update-ref` succeeds every time -- per-ref locks
last microseconds and git retries for `core.filesRefLockTimeout`.

So the abort-on-everything design was strictly harmful. SIGTERM into the
prune loop strands an empty `refs/**/*.lock` about one time in five
(9/30, 5/40, 6/30 kills): `tempfile.c` opens the lock O_EXCL before
`activate_tempfile()` links it into the list the signal handler walks,
and a pack does ~36k lock cycles. Afterwards `update-ref -d` on that ref
fails with `cannot lock ref ... File exists`, permanently. On Windows
`taskkill /f` never runs git's handlers at all, so an abort inside the
rewrite strands `packed-refs.lock` every time.

Never signal the child. `packRefs` no longer takes an abort signal; it
polls `packed-refs.lock` and reports the window through a
`PackedRefsLockReporter`. `pause()` resolves when the lock is released --
bounded, and free during the prune -- while the suspension counter still
blocks new attempts. Battery and window-focus become do-not-start rather
than stop-what-is-running, and quit waits for the lock and lets the child
finish orphaned.

For strands that already exist, `PackRefsLockOwnership` now also reclaims
`refs/**/*.lock` under the same three conditions plus a 0-byte check, and
a lock carrying our own not-yet-reclaimable marker records `locked` with
a 30min retry instead of the 6h failure cooldown -- so a Windows strand
self-heals in half an hour rather than six.

Reverts the git admission-scheduler event bus, which existed only to
drive the abort this removes.

Refs #17828

* test(git): make the ref-maintenance waits survive a loaded runner

CI shard 4/8 failed on `restarts every armed countdown when the user does
ref work themselves`, which passes locally. The `until()` helper spun a
fixed 200 event-loop turns and then returned silently, so on a contended
runner the filesystem probe had not finished and the assertion that
followed failed with an unrelated message.

Bound the wait by wall clock instead and throw a named error, which
immediately exposed a second latent bug: the single-flight test's second
wait could never succeed, because the deferred repo's retry is on a faked
`setTimeout` that spinning the real loop never advances. It had been
passing only because the old helper gave up quietly. Add a timer-aware
variant for those, and have the countdown test await a signal the fake
pack resolves rather than polling at all.

Verified stable across five sequential runs and once under load average
32 with six concurrent suites.

Refs #17828
2026-09-01 19:06:44 -07:00
Neilandkaluli123123 80a52bb9b3 fix(git): recover commit ref badges on Git older than 2.43 (#17923)
GIT_HISTORY_COMMIT_FORMAT asked for decorations with %(decorate:…), which
Git 2.43 introduced. Older Git prints the placeholder verbatim and exits
zero, so nothing raised and every commit in the Source Control panel
silently lost its branch, remote and tag badges.

The record now also carries %D (Git 2.10) on its own line, selected by an
exact match against the unexpanded placeholder — a ref name can never
contain the \x1f that Git expands inside the echoed text. %n emits the %D
line on both sides of the boundary, so the message index is fixed and a
missed match degrades to no badges rather than a corrupted message.

The decoration separator is now bound to the field that produced the text
instead of sniffed from it. A lone decoration carries no separator, so the
old sniff split `refs/heads/feat,one` into two bogus refs.

Verified against real Git 2.38.1 and 2.49.1.

Co-authored-by: kaluli123123 <295758798+kaluli123123@users.noreply.github.com>
2026-09-01 17:14:57 -07:00
Neil 8ac1c6e2ac perf(git): bound ref and worktree scans (#17655)
* perf(git): bound ref and worktree scans

* fix(repo-search): clamp oversized ref limits

* fix(worktree): keep strict worktree listing unshared

The shared-scan re-export flipped every `listWorktreesStrict` caller from an
isolated subprocess to the coalesced scan. `git worktree prune` in the removal
recovery path does not bump the scan generation, so a post-prune verification
could join a pre-prune scan, see the stale row, and report a successful removal
as a stale registration. The same gap defeats the post-archive-hook rechecks
that exist to catch an external Git client locking the row.

Restore the unshared export and make coalescing opt-in via
`listWorktreesSharedStrict`, which existing callers already use deliberately.

* fix(git): separate a proven absent ref from a failed probe

`show-ref --verify --quiet` exits 1 for a missing ref, but so does `wsl.exe`
when its own launch fails, so reading any exit 1 as absence collapsed
`unverifiable` into `exited`. A genuine miss prints nothing while a wrapper
failure always explains itself, so require empty stderr alongside the exit
code; a runner that reports no stderr at all keeps its exit-code contract.

That same signal removes a spawn regression: `show-ref` is a direct-git read
under WSL, and the runner retried any numeric exit through the user's
interactive login shell. The replaced `for-each-ref` exited 0 on a miss, so
absence never retried; every absent probe now would. Treat a quiet exit 1 as
Git control flow and skip the fallback.

Also narrow the hosted-review suffix fallback: the replaced
`refs/remotes/*/<base>` could not cross a slash, but `show-ref -- <base>`
matches at any depth, so `origin/feature/main` answered a query for `main`
and submitted a review against a base the provider rejects.

Refresh the real-binary compatibility contract to the shipped excludes, and
assert exact probe concurrency rather than an upper bound so a regression to
serial probing fails.
2026-08-31 16:27:28 -07:00
Neil 3ab9766e38 perf(worktree): prepare checkouts while the composer is open
Squashed merge of PR #17290.
2026-08-30 12:12:04 -07:00
Jinjing 7e76bb3aec Fix rebase race by fetching to private ref before rebasing (#15990)
* Fix rebase race by fetching to private ref before rebasing

`git pull --rebase` is vulnerable to concurrent fetches modifying remote-tracking refs during execution. Fetch to a temporary private ref (refs/orca/rebase/*) first, then rebase from that stable ref to avoid the race condition.

* Fix rebase race by fetching to private ref with timeout

Concurrent fetches can interfere with remote-tracking refs between
fetch and rebase. Use a unique private ref and 60-second timeout to
isolate each rebase operation and prevent hangs on stalled remotes.
Extract gitPullRebaseFromBase to a dedicated module.

* fix rebase race by fetching to private ref with timeouts

Concurrent fetches can replace FETCH_HEAD and remote-tracking refs between
fetch and rebase, causing the rebase to fail. Fetch to a temporary private
ref instead, use --no-write-fetch-head when available (Git 2.29+), and
serialize FETCH_HEAD access for older versions. Add process termination
barriers to ensure proper cleanup and extend timeouts for SSH operations.

* Fix rebase race by fetching to both private and tracking refs

Concurrent fetches between source and rebase can replace remote-tracking refs,
causing rebases to use stale bases. Now fetch to both a private ref and the
remote-tracking ref simultaneously, ensuring the tracking ref stays current.

Also improves process termination for WSL guests with process-group tracking,
fixes process-tree termination timeouts on POSIX, and serializes FETCH_HEAD
operations for linked worktrees through their shared Git directory.

* Add WSL setsid --wait probe and barrier termination timeout

Probe for `setsid --wait` support and fall back to unwrapped execution for BusyBox compatibility. Add a deadline for process termination barriers to prevent hanging when tree termination cannot be verified. Update tests for cross-platform compatibility.

* Add wsl-process-group-termination to WSL invocation allowlist

* Serialize per-worktree git mutations to fix rebase race

Introduce operation locking for each worktree to prevent concurrent
mutations (like rebase) from interfering with each other. Ensures
rebasing a linked worktree doesn't affect the source worktree state.
Add SIGKILL fallback if process termination barriers cannot verify
tree termination.

* Serialize pull and fastForward operations per-worktree

- Extract generic git operation lock to reuse locking pattern
- Refactor existing locks to use the generic implementation
- Apply per-worktree serialization to pull and fastForward to prevent races

* Route WSL group termination through runWslProcess

ce743a4fd0 silenced the wsl-invocation boundary guard by appending
wsl-process-group-termination.ts to the allowlist. That fixture only
grows when the scanner learns to see a spawn it was blind to, and only
shrinks for a migration -- this was new code on this branch, so the
entry was the boundary regressing rather than the guard getting honest.

Migrate the kill instead. terminate() now calls runWslProcess with the
script form (`<shell> -c <script> -- <args>`), which keeps the group id
in $1, so the payload is unchanged. The script is plain POSIX, so it
must not pin shell: 'bash'; it calls only builtins and coreutils on the
default PATH and reads no login environment, so loginPath is 'none'.

wrapGuestArgs() is untouched: its argv is spliced into git/runner.ts's
own wsl.exe invocation, which is a long-standing allowlist entry.

The unit test now mocks runWslProcess and asserts the spec shape --
distro, loginPath, the group id in args -- so a regression back to a raw
spawn fails here as well as at the boundary guard.

* Assert cleanup is defined before accessing properties
2026-08-24 12:11:55 -07:00
Neil fdd4091ebd fix(hooks): isolate lint-staged backups per worktree (#15388) 2026-08-19 22:37:02 -07:00
Neil 3713dd7376 perf(git): reuse pinned OIDs for SSH file diffs (#13586)
Forward the renderer's already-pinned {mergeBase, headOid} to the SSH relay so a single-file branch diff reads the two blobs directly instead of rediscovering live HEAD. Six sequential git processes become two concurrent reads, and a branch move mid-review no longer changes which revision is displayed.

Equivalence with the legacy route is proven against real Git across 14 change types; wire compatibility is proven over a real SSH socket against relay bundles built from main and from the pre-merge-base.
2026-08-11 02:18:29 -07:00
Brennan Benson 9deee5ad2f perf(worktrees): delete worktree directories after the removal returns (#12416)
* perf(worktrees): delete worktree directories after the removal returns

`git worktree remove` deleted the whole checkout inline, so the remove IPC held the
watcher/PTY gate for the entire recursive delete (prod traces: worktree.remove.git_remove
p50 8-14s, p90 29s, max 34.7s). Local removals now rename the checkout into a hidden
sibling trash root, clear Git's registration for the missing path, and delete the moved
tree in the background. Renames that cannot run (WSL, other volume, Windows open handles)
fall back to the previous in-place removal unchanged.

* test(worktrees): keep no empty trash root when the rename cannot run

* fix(worktrees): harden deferred trash cleanup

* fix(worktrees): keep WSL trash on its owning host
2026-08-04 15:53:11 -07:00
NeilandOrca 7f3c95a585 fix(git-history): stop reading the option marker as the resolved ref name (#10906)
`rev-parse --verify` swallows --end-of-options, but --symbolic-full-name
deliberately echoes it -- on every git version tested, 2.25 through 2.49:

  $ git rev-parse --symbolic-full-name --end-of-options feature
  --end-of-options
  refs/heads/feature

resolveSymbolicFullName took the first non-empty line, so it returned the literal
string "--end-of-options" instead of the ref. That value flows into
gitHistoryRefFromFullName, matches none of the refs/heads, refs/remotes, or
refs/tags prefixes, and every named branch and tag in git history was silently
categorized as a plain commit with a garbage id.

Skip the marker line. Version-independent bug; no test covered it.

Also pins git's echo behavior in the real-binary compatibility suite, so if a
future git stops emitting the marker the reason for the skip gets re-read rather
than the assumption quietly rotting.

Co-authored-by: Orca <help@stably.ai>
2026-07-27 16:40:56 -07:00
JinjingandOrca 772081577e Fix fork PR/MR worktree creation race via durable review-head refs (#10429)
* Fix fork PR/MR worktree creation race via durable review-head refs

When creating a fork PR/MR worktree, concurrent `git fetch origin` operations
clobber the shared FETCH_HEAD, causing the wrong commit to be checked out.
Fetch PR/MR heads into dedicated per-review refs (`refs/orca/pull/<N>`,
`refs/orca/merge-requests/<N>`) that persist and isolate each head from other
fetches. Gracefully keep the compare-base when the fetch fails but the local
ref already exists, avoiding silent fallback to the wrong branch on transient
network errors.

* Bound PR/MR head fetches with 60s timeout

Prevent PR/MR creation from hanging when a remote is stalled or
unreachable. Both GitHub and GitLab head fetches now enforce a
60-second timeout, matching the bound used in the create-path
fetch. Durable refs (refs/orca/pull/*, refs/orca/merge-requests/*)
decouple the ref from FETCH_HEAD, preserving legacy client semantics.

* test: align CI expectations with main PowerShell/sparse regressions

PR checks merge into main, which recently changed PowerShell launch args
(cwd restore after profiles) and sparse-checkout detection (require
core.sparseCheckout). Derive PowerShell spawn args from the production
resolver, mock the sparse config flag, reset shared worktree list scan
cache between tests, and stop requiring floating polls to avoid getRepos
hydration.

* Address review follow-ups on durable review-head refs

- Unify PR review-head remote selection: local and SSH GitHub paths share
  resolveGitHubReviewHeadRemote, which prefers the remote mapping to the
  hosting GitHub project (upstream before origin, matching work-item/API
  candidate order) so contributor clones fetch refs/pull from the repo
  that actually hosts the PR.
- Soft-keep durable review heads: when the PR/MR head fetch fails but
  refs/orca/pull/<N> / refs/orca/merge-requests/<iid> still resolves,
  keep the pinned SHA (warn) instead of failing resolve, mirroring the
  compare-base fallback. Extracted shared compare-base soft-keep into
  compare-base-ref-fetch.ts.
- Extract fetchGitLabMergeRequestHeadRef (local + SSH) parallel to the
  GitHub helper; bound its local fetch with the shared 60s timeout.
- Share relay-style fetch validation (positive safe-integer id, remote
  not starting with "-") between relay and local helpers via
  review-head-tracking-ref.ts; move REVIEW_HEAD_FETCH_TIMEOUT_MS there.
- Drop the githubPullRequestHeadLocalRef re-export; resolve head SHAs via
  rev-parse --verify <ref>^{commit}.
- Add GitLab anti-FETCH_HEAD regression test plus durable-head soft-keep
  and remote-selection unit tests.

Co-authored-by: Orca <help@stably.ai>

* test: supply live getRepos for terminal-retirement hydrates

Main's headless tab hydrate (#9343) skips worktree keys whose repo is not
in getRepos. Retirement tests that rebuild mobile tabs from a persisted
session now advertise the fixture repo as live so PR Checks merge stays green.

* fix(editor): extract RichMarkdownEditor props to stay under max-lines

Main's SSH external-image wiring (#10323) pushed RichMarkdownEditor.tsx over
the 400-line tsx budget, failing PR Checks lint on every merge into main.
Move the props type into a sibling module so the component stays under the
limit without disabling max-lines.

* Make durable review-head refs remote-identity scoped

Embed remote name + URL hash into refs/orca/pull|merge-requests refs to prevent soft-keep from serving wrong project's PR/MR when FETCH_HEAD is clobbered by concurrent fetch. Fetch functions now return the written ref path (writer-authoritative) so callers rev-parse exactly what was fetched, not re-derive identity. Soft-keep only applies to transient errors (timeout, network); fails hard on missing refs, auth failures, and stale relay. Relay returns localRef so client avoids re-hashing (URL normalization can disagree).

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-24 12:53:18 -07:00
6e2a4a824d fix(worktrees): stop surfacing prunable git worktrees as live workspaces (#8409)
* fix(worktrees): stop surfacing prunable git worktrees as live workspaces

A worktree still registered in git but whose directory was deleted
(git's `prunable` state) was enumerated as a normal workspace,
producing repeated pty:spawn DaemonProtocolError / fs:readDir ENOENT
loops and a blank pane.

- Parse the `prunable` porcelain field (Git >= 2.36) in both the main
  and relay worktree-list parsers.
- For Git < 2.36 (no `prunable` field), probe each linked worktree
  path for existence on the fallback line-block path, skipping locked
  registrations to mirror git's own prunable rules.
- Omit prunable worktrees from the detected-workspace enumeration only;
  removal/cleanup flows keep seeing them.
- Extend the real-binary compatibility contract with the 2.36
  `prunable` boundary.

Fixes #8389

Claude-Session: https://claude.ai/code/session_018Rg1Bpq4GGwmz613hq6RSD

* fix(worktrees): pin the prunable/locked porcelain annotations to their real Git 2.31 boundary

The prunable and locked annotations landed in Git 2.31, five releases
before `worktree list -z` (2.36); only -z defines the capability
fallback boundary. Correct the compatibility contract so a future
matrix entry in the 2.31-2.35 range passes, and reword the fallback
comments: on 2.31-2.35 the annotations still parse and the existence
probe is a backstop; only Git <2.31 relies on it outright.

* fix(worktrees): omit prunable registrations from the Space scan

A prunable registration has no directory to size or reclaim, so Space
rendered it as a dead "Missing" row whose checkbox stayed disabled with
no prune/remove affordance (reported on macOS after a reboot cleared
/private/tmp under 16 registrations). Skip prunable entries in the scan,
matching the workspace enumeration; removal flows list worktrees
separately and still see them.

---------

Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-15 15:24:15 -07:00
Brennan BensonandBrennan Benson 1a6abc87d1 Suppress Git Credential Manager OAuth popup loop in Orca-run git — clone, terminals/agents, setup hooks (fixes #7652) (#7986)
* Suppress Git Credential Manager OAuth popup on git clone (fixes #7652)

Orca's git runner disables the interactive credential prompt on every git
call that goes through gitExecFileAsync/gitStreamStdout, but the two raw
'git clone' spawns (desktop repos:clone and the runtime clone path) passed
no env, so they inherited process.env with no guard. On Windows a clone
that needs GitHub auth then makes Git Credential Manager pop its
'Connect to GitHub' OAuth window, and in a network-restricted intranet the
browser/device flow never completes while git's credential retry re-pops it.

Apply nonInteractiveGitEnv() to both clone spawns so the prompt is
suppressed (GCM_INTERACTIVE=never, credential.interactive=false,
GIT_TERMINAL_PROMPT=0). The credential *helper* is kept, so cached-token
clones for private repos still work; only the interactive fallback popup is
disabled and the clone fails fast with a clear error instead.

* Suppress GCM OAuth popup in agent terminals and setup hooks too (#7652)

The clone-spawn fix stopped Orca's own managed git from popping Git
Credential Manager, but git run in terminals and setup scripts inherited
process.env with no guard. That is the more likely source of the reported
loop: agents are told to run 'git pull --rebase'/'git fetch'/retry 'git
push' (preamble + conflict/push-failure prompts), and each retry re-pops
GCM's 'Connect to GitHub' window in a network-restricted intranet.

Apply the credential-prompt guard to:
- setup/archive/hook scripts (hooks.ts non-WSL exec env), which run
  unattended on worktree create/archive.
- the shared PTY host env (buildPtyHostEnv), via a small
  applyTerminalGitCredentialPromptGuard helper. Agent terminals are
  guarded unconditionally (they cannot dismiss a GUI popup); user
  terminals are guarded by default via the new
  terminalSuppressGitCredentialPrompt setting so power users can opt out.

The credential helper is kept, so cached gh auth still works; only the
interactive fallback prompt is disabled. Verified end-to-end in a real
Orca terminal (GIT_TERMINAL_PROMPT=0 + GCM_INTERACTIVE=never by default;
absent when the opt-out is set).

* Scope user-terminal credential guard to Windows, add settings toggle, forward guard into WSL (#7652)

* Retrigger PR checks (Actions dropped the synchronize dispatch for 57e7ce249)

* Keep shell locale out of the terminal/hook credential guard (#7652 review fix)

* Fix Fable review findings: guard WSL hook branch, wire settings search, catalog keyword keys, sparse-env askpass, one-shot agent classification (#7652)

* fix(terminal): harden Git credential popup guard

* test(pty): cover SSH credential guard setting

* fix(git): guard remote clones and setup runners

* fix(git): scope credential guards to unattended work

---------

Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
2026-07-14 15:23:06 -07:00
Neil 533992bdda fix(git): cache unsupported capabilities per host (#8109)
* fix(git): cache unsupported capabilities per host

Old Git worktree, ref-search, and merge-tree fallbacks retried unsupported flags on recurring operations, flooding subprocess traces. Centralize capability probing per native, WSL, and SSH execution host, coalesce concurrent probes, and retry periodically for in-place Git upgrades.

* fix(git): recognize real old-Git merge-tree rejection

* test(git): enforce real binary compatibility matrix

* fix(ci): preserve Git compatibility test ownership

* fix(git): retain supported capability state
2026-07-10 18:19:36 -07:00