Commit Graph
9822 Commits
Author SHA1 Message Date
Brennan BensonandMerge Sim 7314ada3fb fix(native-chat): ignore stale restored working status (#17995)
* fix(native-chat): ignore stale restored working status

* fix(native-chat): sample status freshness per epoch

* Revert "fix(native-chat): sample status freshness per epoch"

This reverts commit 8e49b1badd.

* test(native-chat): include hook status timestamps

* test(native-chat): include visibility hook timestamp

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-01 14:31:23 -07:00
Neil 28214e1ea1 fix(linux): stop re-extracting the AppImage on inode metadata churn
The extracted-payload cache key hashed ctime alongside dev/ino/size/mtime.
ctime moves on any inode metadata write -- `chmod +x`, which every AppImage
user is told to run, plus `chown`, an ACL or SELinux relabel, and a backup
restore -- none of which alter a byte of the payload.

Measured on Ubuntu 24.04: `chmod +x` leaves dev, ino, size and mtime
identical and moves ctime alone, so the key changed and the next launch paid
a full ~519 MB re-extraction and a multi-second stall to rebuild a payload it
already had, then pruned the old generation.

Key on content identity instead. An in-place content change moves mtime and
almost always size; a replacement moves the inode. The existing
replace-in-place test still passes.
2026-09-01 14:06:16 -07:00
Neil a310150e6a fix(linux): bound the CLI registration lock wait
`retries: 1000` caps the attempt count, not elapsed time, so at up to 1s per
attempt an IPC-driven registration could hang ~16 minutes against a wedged
holder with no feedback.

A legitimate holder is bounded by the extraction timeout, so wait that plus
slack and then fail with a message naming the lock file, rather than hanging.
`maxRetryTime` is forwarded verbatim to the `retry` package by proper-lockfile.
2026-09-01 14:06:16 -07:00
Neil 1767858ea7 fix(linux): reclaim superseded AppImage payloads and packaged symlinks
Pruning removed 3215 of 3216 files from a superseded generation and always
stranded resources/app.asar, leaking ~105 MB per version update. Electron's
asar shim reports a *.asar file as a directory, so the recursive remove tried
to rmdir a real file and failed with ENOTEMPTY; the .catch(() => {}) hid it.
Reproduced end to end on Ubuntu 24.04: 519M -> 623M across one update, and
519M again once the payload is actually reclaimed.

removeExtractedAppImagePayload holds process.noAsar for the removal, counted
so overlapping removals cannot hand the shim back early, and the prune site
now warns with the path instead of swallowing the rejection. All three
removal sites use it -- staging cleanup and displaced roots leaked the same
way.

Also reclaim symlinks left by a packaged deb/rpm install, which the
extracted-cache-only rule turned into a hard conflict on a deb -> AppImage
migration, and name the remedy in the conflict error.
2026-09-01 14:06:16 -07:00
Neil bbd2047066 refactor(linux): import bundled launcher directly 2026-09-01 14:06:16 -07:00
Neil 95212ef572 docs(linux): make headless AppImage extraction runnable 2026-09-01 14:06:16 -07:00
Neil a2c8859f76 fix(linux): accept extracted AppImage runtimes with APPDIR only 2026-09-01 14:06:16 -07:00
Neil d19a8cdf86 fix(linux): fence AppImage terminal shim mounts 2026-09-01 14:06:16 -07:00
Neil 0079fe2fa8 test(cli): assert registration lock serialization 2026-09-01 14:06:16 -07:00
Neil 4c24a28df0 refactor(linux): trim AppImage CLI registration seams 2026-09-01 14:06:16 -07:00
Neil da4a83bd22 fix(linux): give the CLI one entrypoint by extracting the AppImage once 2026-09-01 14:06:16 -07:00
Neil 2be53521a7 refactor(task-page): fold the task page into one provider-grouped tree
The page had two competing splits: an Aug-25 folder split that the Aug-30
oversized-surfaces split stranded, and the 47 flat files that replaced it. The
orphaned tree had no non-test importers, yet eight ratchet files still asserted
against it, so their invariants stopped constraining shipping code -- which is
how six regressions reached main unnoticed. Those ratchets were repointed and
the regressions fixed earlier; this removes the tree they were guarding.

Moves the live files into task-page/{github,gitlab,jira,linear} and drops the
now-redundant prefix, matching the new-workspace sibling.

Makes the source-family walker recursive first: it listed a single flat
directory, so moving the files under it would have emptied the family and turned
every ratchet built on it into a no-op without failing.
2026-09-01 13:16:43 -07:00
Neil 8fef5820ff fix(renderer): restore behavior the UI split dropped
The oversized-UI-surfaces split was cut from a stale branch and reverted merged
work. getClientCreationActionPolicy entered Terminal.tsx in #13909 and left in
the split, taking six call sites with it, so every action-time creation gate in
the terminal and floating surfaces was gone. Restores those and the other
behavior the split dropped, each ported from the pre-split reference:

- Cmd/Ctrl+S dispatched a bare Event with no detail, so the only listener always
  bailed on detail?.fileId and the chord never saved. Its resolver had been left
  orphaned, imported by nothing but its own test.
- Terminal and floating create actions lost their availability gates, their
  toasts, and their catch handlers; one path throws on unavailable, so it was a
  silent unhandled rejection.
- Both outermost workbench wrappers lost the browser guest paint retention
  branch, and the census entry covering them was deleted in the same commit.
- The Space Analyzer header counted omitted items the list no longer rendered,
  and a worktree whose items were all omitted showed the empty state.
- The terminal root lost its tab topology projection, so every tab-title update
  re-rendered it.
- The titlebar tab bar stopped being passed clientHostedBrowserRows, leaving
  client-hosted pages uncloseable before a worktree has a layout.
- Parking diagnostics lost their exempt-route counts and crash breadcrumb.
- A suppressed inherited-terminal frame began buying a freshness scan the
  pre-split early return skipped.

Adds regression tests for each, all verified to fail against the pre-fix code.
Restores three deleted assertions whose invariants are still live, and replaces
a concatenated source-boundary fixture with per-module pinning so a symbol is
again asserted against the module that must own it.

Deletes three orphaned trees the splits stranded: a duplicate ResourceUsage
surface, cmd-j-match-relevance, and an agent-session claim-key module whose
logic the record store already owns. Makes two non-recursive test walkers
recursive, one of which silently skipped every nested CLI handler group.
2026-09-01 12:51:17 -07:00
Neil 79990464d2 fix(store): merge the duplicated agent-status-launch-config import
The rename collapsed agent-status-map-helpers into agent-status-launch-config,
leaving two import statements for the same module.
2026-09-01 12:27:01 -07:00
Neil 8cc7634051 refactor: name modules for their domain instead of 'helpers'
Renames seven -helpers modules for the concept their functions operate on, and
splits three that were genuine grab-bags -- each had a clean cleavage along its
importers, which is the signal AGENTS.md describes for a file holding more than
one responsibility.

Leaves keybindings/definitions-core-1..4 alone: definitions.ts spreads them in
order, so their concatenation order is the command palette order and regrouping
them thematically would be a user-visible change. Records that reasoning in a
comment so it is not re-litigated.
2026-09-01 12:27:01 -07:00
Neil de8aaac344 refactor(mobile): name terminal WebView modules for their contents
fragment-01..10 were arbitrary line-count slices of one template literal. Two
seams fell mid-expression -- inside buildMouseClickInput and inside the touchmove
listener -- so those pieces had no identity to name. Re-splits at real statement
boundaries and names each for what it holds.

The composed output is byte-identical: sha256 42cc000f..., 729776 bytes, verified
before, after the regroup, and after formatting. Also fixes two ratchet tests that
read fragment paths directly, one of which duplicated the composer's file list.
2026-09-01 12:27:01 -07:00
Neil fc68d2c3a2 refactor(preload): name bridge modules for what they expose
The split named these -part-N, which says nothing. Renames each for the group of
bridge methods it actually exposes and folds the single-method window-reveal
module into the window-controls module it belongs with.

Verified by walking the composed contextBridge surface before and after: 1060
keys, identical nesting and value types, zero delta. The bridge modules carry no
satisfies annotation, so a dropped key here is a runtime error in the renderer
rather than a typecheck failure.
2026-09-01 12:27:01 -07:00
Neil bbcb7a199d fix(task-page): restore the STA-5949 GitHub list scroll restore
The split left this logic inline in use-task-page-github-detail.ts at its
pre-STA-5949 shape: a 5s deadline whose expiry overwrote the remembered offset
with the committed 0 -- the permanent-loss bug the extracted module's header
says was removed -- observing only the children rather than the container, and
with no MutationObserver, so late-mounting rows never retriggered a retry. The
list scroll handler also blanket-returned while a restore was pending instead
of classifying echoes, dropping a user's scroll during an unreachable restore.

Promotes the module to a live path and wires the hook and scroll handler to it,
which also gives the ten behavioral cases in the scroll-restore suite something
live to assert against.
2026-09-01 12:25:53 -07:00
Neil 97d1a9001c fix(task-page): restore behavior lost in the UI split
Five regressions shipped in the oversized-UI-surfaces split because the ratchet
tests guarding them still pointed at an earlier, orphaned split of the same file:

- GitLab rows lost the target/currentTarget guard, so Enter on the nested
  open-in-browser button also opened the task detail.
- GitLab row keys dropped the repo prefix; work-item ids collide across hosts.
- GitHub Enterprise avatars lost their onError fallback in three slots, so an
  unauthenticated avatarUrl rendered a broken image (#8784, #13976).
- Linear new-issue popovers lost the viewport-aware scroll container and
  reverted to a fixed inner scroller the outer cap clips.
- Jira issue creation lost its catch, so a transport failure told the user
  nothing.

Retargets the eight ratchet files at the live modules so these stay guarded.
2026-09-01 12:25:53 -07:00
Neil 02ffd40edc fix(ssh): stop treating an unanswered native-deps probe as broken deps (#17979)
probeRequiredNativeDeps mapped any thrown error to available:false, which
both triggered the repair and fed resetDeps — so one dropped exec channel
rm -rf'd node_modules/node-pty on a healthy relay and forced a node-gyp
source build. Verdicts are now ok / blocked / unverifiable; only an
answered probe may repair, and only an answered probe may name reset deps.
2026-09-01 12:23:12 -07:00
Jinjing 512ec1a0da Revert "Move mobile search button to bottom left floating (#17808)" (#17990)
This reverts commit 823934034f.
2026-09-01 11:33:18 -07:00
Brennan BensonandMerge Sim 5150c52045 fix(dev): skip blocking keychain diagnostic (#17877)
* fix(dev): skip blocking keychain diagnostic

* fix(dev): preserve forced secret protection report

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-01 11:02:54 -07:00
Jinwoo Hong 69fff5eaa3 fix(runtime): defer websocket heartbeat startup probe (#17810)
* fix(runtime): defer websocket heartbeat startup probe

* fix(runtime): defer heartbeat probes until websocket auth
2026-09-01 14:00:11 -04:00
Jinjing 51df2189b5 fix(ci): age adhoc releases by publishedAt, not createdAt (#17984)
GitHub reports a release's createdAt as the date of the commit its tag
points at. Every adhoc tag is cut against orca-adhoc's single seed commit
(ff9ca5b6, 2026-08-02T09:46:58Z), so all of them share that one createdAt.

The 30-day cutoff crossed it today: the 06:53 run logged "Nothing to
prune", and the 10:34 run marked the entire channel expired and deleted
40+ releases -- including the one it had published two minutes earlier.
The picker had nothing newer than Aug 13 left to offer.

Age on publishedAt instead, keep any release missing one rather than
guessing, exclude the tag the run just shipped, and prune only after a
live publish. Hourly and daily already moved to publishedAt for the
adjacent sort bug; adhoc was the last one still on createdAt.
2026-09-01 10:15:30 -07:00
github-actions[bot] 5aa02ead59 Update README downloads badge 2026-09-01 12:36:59 +00:00
Neil 26dfa46aa9 fix(wsl): resolve sparse-checkout and diff-stamp gitdirs through the caller's distro (#17932)
Two main-process `resolveGitDir` call sites dropped the WSL distro their caller
already held, so they could only resolve a gitdir pointer whose spelling carries
its own translation: a `//wsl.localhost/<Distro>/...` base, or a `/mnt/<letter>`
drvfs pointer that maps to a drive letter on its own.

The layout that needs the third case is a repo inside the distro's filesystem
with its worktrees on the Windows drive. `git worktree list` reports the worktree
as `/mnt/c/wt/x`, which the listing translates to `C:\wt\x` — a base that no
longer names a distro — while the `.git` gitfile beside it points at
`/home/me/repo/.git/worktrees/x`, which has no drive to derive. Win32 then treats
that pointer as absolute and reads a path that names nothing:

- `detectSparseCheckout` stats `info/sparse-checkout` under the fabricated path,
  always misses, and reports the worktree as non-sparse — no sparse badge, and
  the file list claims files that are not on disk.
- `readWorktreeDiffStamp` reads HEAD under the same path, gets nothing, and
  returns null. Null is the safe answer ("cannot prove unchanged"), but it
  retires the settled-diff cache for every file in that worktree, so each diff
  respawns Git.

Both callers already have the distro: the listing threads its
`GitWorktreeExecOptions` to `annotateSparseCheckoutStatus`, on through
`detectSparseCheckoutCached` (the annotation cache added by #17859) and its
background revalidation probe, and finally to `detectSparseCheckout`; and
`file-diff` already forwards its `GitRuntimeOptions` to `readWorktreeDiffStamp`,
which now forwards it to `resolveGitDir` as well.

`resolveGitMetadataPath` still prefers a UNC base's distro and still tries drvfs
before the caller-named distro, so nothing that resolved before resolves
differently.

The cache hop matters twice over. It is the only remaining caller of
`detectSparseCheckout`, so without threading it the fix would not reach the
probe at all. And the cache is where the bug turns sticky. #17859 keyed entries
on `repoPath` + `worktreePath` alone, on the reasoning that the distro is a
property of the repo and so every read for a given `repoPath` carries the same
one. That invariant does not hold. `listRepoWorktrees(repo)` is called with no
options at all from the filesystem-auth root rebuild
(`registered-worktree-roots-cache.ts`, reached from `ensureAuthorizedRootsCache`
on any auth check with a dirty cache) and from the local worktree-ownership
check in `filesystem-worktree-helpers.ts`. Both land on the *same* key as the
distro-carrying listing, because `translateWslOutputPaths` derives the distro
from the cwd spelling before falling back to `options.wslDistro`, so a
UNC-spelled repo path yields the identical `C:\...` worktree row either way.
Measured on Windows in one process, branch build: a distro-less read followed by
a distro-carrying read reported the sparse worktree as non-sparse both times.

So `wslDistro` now joins the cache key -- trimmed and lowercased, matching how
the rest of the codebase compares distro names, and appended last so the
repo-scoped prefix delete still matches every variant. The per-path invalidate
becomes a prefix delete for the same reason, dropping every distro variant of a
removed or moved worktree.

Keying on it closes both halves of the defect. A correct caller can no longer be
served an answer derived without the distro it supplied. And because the entry a
reader reaches is now selected by the same distro it would re-probe with,
`revalidateInBackground` can no longer re-derive a warm entry under weaker
options -- which mattered on its own: a distro-less reader crossing the
five-minute window would otherwise flip a correct `true` to `false`, and the
resulting change notification runs the registered invalidator, clearing the
whole repo's cache and re-probing every worktree cold, on a five-minute loop.

Cost of the extra key dimension is bounded by the number of distinct distros a
given repo is actually read under: one where a distro is threaded everywhere,
two while the distro-less callers above still exist. Entries are still
repo-scoped, and both clears already sweep by prefix.

Per-platform delta:
- macOS/Linux: no change. Guest-pointer translation is gated to win32 and a
  caller-named distro is ignored off Windows; no caller supplies one there, so
  the cache keys and probes exactly as before.
- native Windows, no WSL: no change. `wslDistro` is undefined, so the resolver
  takes exactly the branches it took before and every read keys on the same
  empty distro component, so the cache behaves exactly as it did.
- Windows + WSL, UNC-spelled worktree: no change. The base already names the
  distro and outranks the caller's.
- Windows + WSL, drvfs-spelled worktree with a drvfs pointer: no change. The
  drive-letter derivation still runs first.
- Windows + WSL, drvfs-spelled worktree with a non-drvfs pointer: the sparse
  badge appears and the settled-diff cache starts hitting. Both previously
  failed toward "not sparse" / "do not cache", so neither can now serve a stale
  answer, and the distro-less listings no longer share the badge's cache entry.
- SSH/relay: none. Those paths return through the provider branch before
  reaching either function.
- folder workspaces, GitLab: none. Neither is on these code paths.

Not in this change:
- `readRepoCommonDirFromDisk` (worktree-listing). Passing the distro there is
  inert: a repo root's `.git` is a directory, so the gitfile-pointer branch never
  runs, and when `repoPath` itself is guest-spelled the preceding `stat` already
  fails — which no `resolveGitDir` option can fix.
- The two `findExistingWorktreeSymlinkPaths` calls on the removal paths. Both
  receive `registeredWorktree.path` from `listWorktreesStrict`, which already
  translates every row out of the guest namespace, so the distro would be a
  no-op. The `removeWorktreeLinkedPaths` unlink beside them is untranslated too,
  so a half-threaded fix would only move the refusal from Orca's preflight to
  `git worktree remove`.
- An absolute `commondir` payload, which `resolveGitCommonDir` still resolves
  untranslated. Git writes that file relative in the layouts above, and the
  failure direction is unchanged.
- Giving the two distro-less `listRepoWorktrees(repo)` callers a distro. Neither
  reads `isSparse` -- both use only `worktree.path` -- so the distro would buy
  them nothing they consume, while resolving a project runtime inside the
  filesystem-auth rebuild would put a call that throws on `repair-required`
  behind a catch that skips the whole repo's authorized roots. The cache key
  makes their reads harmless; skipping the annotation for callers that never
  read it is a separate, larger change. The third no-options call in
  `hosted-review.ts` is inside the `repo.connectionId` branch and returns
  through the SSH provider, so it never reaches this cache.
2026-09-01 04:25:19 -07:00
djegor315-sketch f27a30d1b6 fix(ai-vault): avoid large Codex scan timeouts (#17889)
Agent Session History exceeded its 130-second deadline on large local Codex
histories. Three costs combined: excluded worker transcripts were recognized on
their first line but still drained to EOF (1,011 files / ~18.3 GiB on the
reported corpus), large ignored records were fully decoded and JSON.parsed, and
the persisted parse cache was discarded on every app update.

- Stop resumable reads the moment `session_meta` marks a worker transcript.
- Skip decode + `JSON.parse` for records the parser only feeds to the timeline.
  The skip set is the complement of what `consumeCodexRecordLine` reads, and
  applies only above the bounded prefix limit, so a long opening prompt (which
  is the session title) still takes the exact parser.
- Prove cross-volume rollout aliases from a bounded `session_meta` read routed
  through the WSL transcript FS gate, carrying the scan's AbortSignal, fanned
  out across contested candidates with bounded concurrency.
- Make parse-cache schema 2 the semantic compatibility boundary so an update no
  longer forces a multi-gigabyte cold scan, fenced by a build-time ratchet on
  the persisted session shape.
- Report early-stopped transcripts as their own `aiVault.scan` attribute.

Verified on macOS, Ubuntu over SSH, and Windows: read volume drops 336 -> 49.5
MiB identically on all three; the Windows failing-test set is byte-identical to
main. Reported corpus: 130s timeout -> 58.6s, 244 sessions, 0 issues.

Fixes #17888.
2026-09-01 04:21:43 -07:00
Neil f176e49478 fix(git): narrow fork-remote fetch refspecs to tracked branches (#17887)
* fix(git): narrow fork-remote fetch refspecs to tracked branches

git remote add with no -t writes the wide +refs/heads/*:refs/remotes/<name>/*
refspec, so any later plain `git fetch` (user, agent, or Orca's own Fetch
action) re-imports a fork's entire branch set and its tags -- one real
machine had ~50 leaked/wide fork remotes producing 59,716 remote-tracking
refs. Mint and reuse now pin -t <branch> --no-tags; a rate-limited sweep
narrows and cleans up remotes minted before this fix; gitFetch self-heals
when a narrowed remote's tracked branch is later deleted upstream.

Refs #17828

* fix(git): soften narrow fork-remote refspec against deleted upstream branches

A bare `git fetch` in a worktree checked out on a fork-PR branch resolves to
the pr-* remote via branch.<name>.remote -- not origin -- making it the
dominant fetch shape in Orca's terminal-centric, agent-driven usage. The
previous literal-refspec design hard-failed that fetch ("couldn't find
remote ref") the moment the tracked branch was deleted/renamed upstream,
which is not the narrow edge case it was first described as.

Switch to a trailing-`*`-suffixed refspec source/destination
(refs/heads/<branch>*:refs/remotes/<name>/<branch>*). Verified against real
git: this restores wildcard zero-match tolerance (silent no-op instead of a
hard failure) and lets plain `git fetch --prune` reclaim the stale ref once
the branch disappears, at the cost of also matching sibling branches that
share the literal name as a prefix -- a materially smaller widening than the
original unbounded-import bug.

Also close a race with #17842's orphaned-pr-remote reconciliation sweep:
both sweeps read the same worktree-metadata store to pick candidate remotes,
so reconciliation can `remote remove` a remote this migration is
concurrently narrowing. `ensureRemoteTracksBranchNarrowly`'s plain `config
--add` would silently resurrect a url-less config section in that case;
re-check `remote.<name>.url` (via the new `remoteHasUrl`, plumbing rather
than porcelain `remote get-url`, which falls back to echoing the remote name
as a bogus URL) after the narrowing writes and remove the section if it's
gone.

* fix(git): update stale fork-remote mint assertions for -t/--no-tags and wildcard-suffix refspec

Four test files still asserted the pre-#17828 remote-add shape or the
literal (non-wildcard-suffixed) fetch refspec from before the
deleted-upstream-branch softening commit, so CI went red on that HEAD:

- worktree-push-target-refspec-real-git.test.ts: the migration fixture
  asserted a hardcoded tracked-ref count before narrowing. Under git
  >= 2.44, `followRemoteHEAD` auto-creates a `refs/remotes/<name>/HEAD`
  symref on the first fetch matching the full wildcard refspec, adding
  one untracked ref. Made the count/assertions robust to that ref's
  presence instead of hand-tuning the constant per git version.
- worktrees-wsl-runtime-routing.test.ts: assertions predated both the
  `-t <branch> --no-tags` mint change and the wildcard-suffix refspec
  change; updated to the full, correct call sequence and confirmed the
  WSL routing options (cwd, wslDistro) are threaded to every call.
- worktrees-create-metadata-persistence.test.ts and
  orca-runtime-tests/worktree-removal-and-reconciliation.spec.ts: same
  class of staleness, found via CI job log cross-referencing rather
  than being explicitly flagged.

Verified out of scope: the SSH fork-remote mint path
(prepareWorktreePushTargetSsh) is untouched by this PR -- it never
persists a `remote.<name>.fetch` refspec at all, using
provider.fetchRemoteTrackingRef for a targeted per-branch fetch
instead -- so worktrees-ssh-fork-push-target-remote.test.ts needed no
change.

* fix(git): migrate pr-* remotes with zero worktree-metadata trace too

The migration sweep's candidate discovery was purely metadata-driven
(store.getAllWorktreeMeta()), so a pr-* remote whose every referencing
worktree was removed outside preserve-on-delete (metadata purged, not
just the worktree) was permanently invisible to it and stayed on the
wide default forever.

Field data from a manual migration run against a real user's repo (31
pr-* remotes, 34,637 tracking refs, only 18 actually needed) found
exactly this: 15 of 31 remotes had no branch pinning them at all.

Widen discovery to every pr-* remote git reports on disk, in addition
to metadata-derived candidates. For a remote with no branch provenance
from either metadata or surviving branch.*.remote/.pushRemote config,
there's nothing to narrow *to* -- clear its fetch refspec entirely
instead (stays pushable, imports nothing on a plain fetch), gated on
it still carrying the untouched stock wide default so a user's own
custom pr-*-named remote isn't touched. Removing the remote outright
stays #17842's job.

Adds clearForkRemoteFetchRefspec (fork-remote-refspec.ts), 3 new
mocked-exec tests, and a real-git integration test proving a
subsequent plain `git fetch` on the cleared remote imports nothing.
2026-09-01 03:45:09 -07:00
Neil 84584b61d0 perf(git): cache sparse-checkout annotation on worktree listing (#17859)
* perf(git): cache sparse-checkout annotation on worktree listing

`git worktree list` never reports sparse-checkout state, so every listing paid a
per-worktree fs.stat + config read to detect it -- measured at ~9x the cost of
the `git worktree list` call it decorates on a 1000-worktree repo. Cache the
result per worktree path, invalidated by the existing worktree-change
invalidator registry plus explicit remove/move hooks, with a 5-minute
reconcile window bounding the one unwitnessed edge case (external
`git sparse-checkout` toggle with extensions.worktreeConfig off), matching the
precedent already accepted in readRepoWorktreeAdminFingerprint.

* perf(git): normalize/scope sparse-checkout cache keys, add SWR

Address independent-review follow-ups on the sparse-checkout annotation
cache (#17859):

- Extract canonicalWorktreePath() from areWorktreePathsEqual and key/invalidate
  the cache through it on both read and write, closing the disclosed
  path-spelling P2 outright instead of leaving it as a residual risk.
- Scope cache entries and clears by repo path (derived from the invalidator
  registry's repoId via a store lookup, falling back to a full clear when the
  repo can't be resolved), so churn in one repo no longer evicts a sibling
  repo's warm cache.
- Replace the hard 5-minute cutoff with stale-while-revalidate: past the
  window, callers get the cached value immediately while a deduplicated
  background probe corrects it and, on a flip, drives the existing
  worktrees-changed notification -- collapsing visible staleness from the
  full window to one refresh cycle at zero added listing latency.

Also corrects a stale claim in the original PR description: newer Git does
emit a `sparse` porcelain line (which annotateSparseCheckoutStatus already
skips), but Orca's Git 2.25 compatibility baseline predates it, so the
fallback detection this caches remains necessary.

* fix(git): stop background sparse-checkout revalidation resurrecting invalidated entries

Readiness-loop finding: a stale-while-revalidate probe in flight when a
worktree is removed/moved (or a repo's cache is cleared) would still write
its result back afterward, resurrecting an entry that was deliberately
dropped. Guard the write with a presence check so an invalidated key stays
absent until the next real read.

* fix(git): identity-check the sparse-checkout SWR write-back guard

The has()/presence guard from the previous commit only proved some
entry existed at the key, not that it was the one this revalidation
started from. A worktree removed and re-created at the same path while
a background re-detect was in flight would repopulate the key with a
fresh cold read, and the stale in-flight result would then overwrite
it -- exactly the race greptile (P1) and pullfrog both flagged as
still open. Compare the map's current entry by reference to the entry
captured when the revalidation began; a mismatch means something else
(invalidate, clear, or a fresh cold read) replaced it, and the stale
result must not be written back.

Added a regression test that fails against the old has() guard and
passes with the identity check: invalidate and repopulate the key with
a different value mid-flight, then let the stale revalidation settle
and assert the fresh value survives.
2026-09-01 03:44:50 -07:00
Neil 9542b45d99 fix(wsl): resolve conflict and working-tree probes in the host path namespace (#17895)
Git running inside a WSL distro writes `.git` gitdir pointers, and answers
`status --porcelain`, in the guest namespace. Node reads both back in the
Windows main process, where `/mnt/c/repo/.git` resolves to `C:\mnt\c\repo\.git`
and `/home/me/wt` names nothing at all. Four fs probes were built on those
fabricated paths and always came back "absent":

- `detectConflictOperation`'s four marker probes, so merge/rebase/cherry-pick
  badges silently went missing.
- `parseUnmergedEntry`'s compat existence check, so every `deleted_by_us` /
  `added_by_them` conflict rendered as 'deleted' regardless of the working tree.
- `findExistingWorktreeSymlinkPaths`' `lstat` from status, so Orca's own shared
  symlinks (node_modules and friends) showed as user changes.
- the same `lstat` from the hosted-review dirty preflight, which fails closed:
  an unreadable shared symlink read as uncommitted work and blocked PR/MR
  creation outright.

`resolveGitDir` computes the host spelling of the worktree once and uses it for
both the gitfile read and the pointer resolve, so a guest-spelled worktree path
is reached at all, and a relative pointer (`worktree.useRelativePaths`, git
2.48+) resolves against a spelling Win32 understands. The pointer itself now
goes through the already-landed `resolveGitMetadataPath`, and the function gains
an optional `{ wslDistro }` for a caller whose base path does not encode a
distro. `detectConflictOperation` forwards it, and the three callers that reach
it -- status-read, the runtime RPC, the `git:conflictOperation` IPC -- pass the
git options they already hold. The return type stays `Promise<string>`.

`resolveWorktreeHostPath` is the same rule applied to a worktree path, used by
status-read for the two working-tree probes and by the review preflight. Both it
and `resolveGitMetadataPath` now treat only a single-leading-slash path as guest
namespace: `//wsl.localhost/...` is already a host UNC spelling, and translating
it prepended a second share prefix.

`readWorktreeDiffStamp` needed the same one-namespace guarantee, since moving
translation inside `resolveGitDir` would otherwise make its HEAD and index real
while the working-tree stat stayed fabricated, letting a settled diff survive
every edit. #17896 landed that change first, so it is no longer in this diff;
its version is a superset and all four components already resolve from one
`hostWorktreePath`. What remains here is the `resolveGitDir` gitfile-pointer
fix that #17896 explicitly deferred, which `worktree-diff-stamp-host-paths.test.ts`
pins.

`getConflictCompatibilityStatus` moves from `existsSync` to async `access`, for
the same reason `detectConflictOperation` did: once these paths are real they
are `\\wsl.localhost\...` shares, and a sync probe per asymmetric conflict
blocks the Electron main thread for a 9p round trip on every status poll.

Per-platform delta:
- native Windows, no WSL: no behavioral change. Nothing here starts with a
  single `/`, so no path is translated. An absolute pointer is now returned
  verbatim rather than separator-normalized; every consumer re-joins or
  normalizes it before use.
- macOS/Linux: no change. Guest-pointer translation is gated to win32, and a
  caller-named distro is ignored off Windows.
- Windows + WSL: drvfs pointers and drvfs-spelled worktrees now resolve to their
  drive spelling instead of `C:\mnt\...`; a non-drvfs guest path resolves
  through the named distro's UNC share, or stays verbatim (ENOENT -> existing
  fail-safe) when none is named.
- SSH/relay: none. Those paths return before any of this via the provider
  branch; `src/relay/git-handler-status-ops.ts` keeps its own resolveGitDir.
- folder workspaces, GitLab: none. Neither is on these code paths.
2026-09-01 03:17:34 -07:00
Jinjing 899304d515 Increase artifact content size limit from 5 MiB to 10 MiB (#17910)
Doubles the maximum UTF-8 bytes accepted for manually shared artifacts,
enabling users to share larger content while maintaining recovery and
transport constraints.
2026-09-01 03:16:34 -07:00
Jinjing 2b6c14d4b5 Add startup delivery diagnostics and success announcements (#17814)
Terminal sessions now report startup command delivery details (whether written, presence, length, and delivery method) without logging the command text—preventing credential leakage and distinguishing missing commands from lost ones in diagnostics.

Setup scripts now announce completion on both POSIX and Windows before executing the startup command, so healthy setups don't appear stuck in the UI with "Waiting for setup..." as the last visible line.

Diagnostics failures are caught and ignored so they never break session creation.
2026-09-01 03:08:25 -07:00
Jinjing 823934034f Move mobile search button to bottom left floating (#17808)
* Move workspace search toggle to floating button

Extract the search bar into a separate component and move the search toggle button from the toolbar to a bottom-left floating action button, positioned above the new workspace FAB. This consolidates phone-only floating actions in one location.

* Remove SearchWorkspacesFab component

Consolidates search functionality into bottom-left floating action button as part of mobile search button repositioning.
2026-09-01 02:56:37 -07:00
Neil 1603810dde perf(worktree): make head-identity refresh incremental (#17843)
* perf(worktree): make head-identity refresh incremental

Head-identity refresh re-read `gitdir` + `HEAD` + a loose ref for every
linked worktree on every watcher burst. On a 973-worktree checkout that is
~2,800 metadata reads (~1.0s of main-process fs I/O) per event, and the
debounced pipeline fires on every commit in any worktree — so fleet-wide
agent activity degenerated into a continuous scan loop.

Watcher events already name the admin dir that changed. Classify each event
into a head-identity scope, memoize per-entry identities, and re-read only
the scoped entries. Refs resolved during a pass are replayed onto cached
entries that share the same branch, so `git worktree add --force` siblings
stay current without extra reads.

Invalidation stays conservative: an absent scope (watcher failure, event
overflow, cold start) means a full re-read, `packed-refs` writes invalidate
every entry, misses are never memoized, and one refresh per minute is
promoted back to a full re-read to bound the window where a ref moves with
no event under any admin dir.

Measured on the reported 989-entry checkout (macOS/APFS): one-worktree
commit 2,816 -> 2 file reads, 61ms -> 0.5ms p50 with an identical page
cache; a 20-worktree debounce burst costs 57 reads / 9.7ms; an external
`git worktree add`/`remove` costs one readdir / 1.0ms.

Refs #17828

* fix(worktree): harden incremental head-identity invalidation

Two holes found in self-review:

- An admin entry name removed and immediately reused inside one debounce
  window coalesced into a listing-only scope, so the reused entry kept
  serving the removed worktree's cached head. Name the entry alongside the
  listing on every `worktrees/<name>` create/delete.
- A non-ENOENT `readdir` failure on `worktrees/` collapsed the memo to the
  primary row, which then re-emitted every identity on recovery. Mirror
  worktree-git-common-polling: only a genuinely absent dir means empty; any
  other error keeps the previous listing.

* fix(worktree): let empty-scope bursts still take the head re-baseline

Adversarial review found the 60s full-rebaseline promotion was unreachable
whenever the triggering burst had an empty head-identity scope: the skip
guarded on the raw caller scope and returned before `resolveScope` ran, so
`lastFullReadAtMs` was never re-evaluated. A repo whose only churn is
`git worktree lock`/`unlock` or a sparse toggle — Orca's own prepared-checkout
flow locks and unlocks on every create — could starve the promotion forever
and hold a stale head indefinitely. Resolve the scope first and skip on the
effective scope.

Also stop deferring an add/remove that arrived while the `worktrees/` listing
was transiently unreadable: forget the memoized listing so the next refresh
re-enumerates whatever its scope, instead of waiting for another listing event.

Both fixes carry a test verified to fail without them.

* fix(worktree): return head-read completeness instead of sniffing the memo

Adversarial review round two. Six fixes, each with a test verified to fail
without it.

- `readGitCommonHeadIdentities` now returns `{ identities, listingComplete }`.
  The refresh layer was inferring "enumeration failed" from `cache.entryNames
  === null`, a reader-owned field whose null also means "cold start" — fragile
  in production and impossible to express in a mock.
- A read discarded by teardown, or one that could not enumerate `worktrees/`,
  no longer arms the 60s freshness clock.
- A queued refresh whose re-run met a destroyed window (macOS recreates the
  window while the watch lives on) was cleared and dropped. It now stays armed
  and is folded into the next request.
- An incomplete listing carries forward the baseline rows it could not observe,
  so recovery does not report every linked worktree as changed.
- The baseline advances after notifying, so a send into destroyed chrome leaves
  the move to be retried instead of diffing it away.
- A scope naming an entry the memoized listing does not know now forces a
  re-enumeration instead of resolving to zero work — this removes an unstated
  dependency on `diffGitCommon` emitting a dir-level create for new entries.
- Overflow states FULL at its construction site rather than relying on a
  downstream `?? FULL` for an absent field.

Also documents the load-bearing invariant behind the empty-scope skip (an empty
scope only reaches the refresh from a structural burst, which forces
`emit: false` and is always paired with a catalog notification for every repo
on the watch), and strengthens two tests that could not distinguish the
behaviour they claimed.

* fix(worktree): bound head-identity staleness with a one-shot catch-up

The previous re-baseline was opportunistic: it rode the next refresh, so a ref
that moves with no watched write (`git update-ref refs/heads/x` from a sibling
worktree) stayed stale until an event happened to arrive after the interval.
Pre-PR the very next event anywhere in the repo corrected it, so this was a
real narrowing of correctness, not just a pre-existing gap.

Arm a one-shot, unref'd timer when a SCOPED pass completes, firing one full
re-baseline an interval after the last full read, then disarming. A full pass
disarms instead of arming, so it never becomes a background poll, and the timer
only exists after an event — an idle repo still schedules nothing and reads
nothing. Cost is O(1) timer per active repo and at most one full read per
interval: the same operation the old code ran per event, 60x rarer.

This also converts "stale until some later event" into "stale at most one
interval, period", which is what bounds the blast radius of any invalidation
bug in the scoping itself.

Cleared on watch disposal. Three tests, each verified to fail without its fix:
the catch-up runs with no further events; a quiet repo issues no background
reads and the timer disarms after firing; disposal stops it.

* fix(worktree): treat an unreadable head as unknown, not absent

Reported independently by two PR reviewers. `readTrimmedFile` collapsed every
errno to `null`, so an EIO/EACCES/ENFILE on a `gitdir`, `HEAD`, loose ref, or
`packed-refs` read was indistinguishable from the file being absent — and the
caller deletes the cached identity on `null`. Same conflation AGENTS.md forbids
for the SSH verdict vocabulary: loss of contact is not evidence of absence.

Reads now report three outcomes, and an unknown:

- keeps the entry's last verified identity instead of evicting it,
- is never replayed onto siblings sharing the branch as "this ref is gone",
- marks the entry unverified so the very next pass re-reads it whatever its
  scope, and
- reports the pass incomplete, so it cannot arm the freshness clock.

The reviewers' stated consequence — that an evicted entry stays evicted until
the next full pass — did not hold, because `!cache.entries.has(name)` already
forced a re-read. The real cost was that one EMFILE evicted every entry it
touched and the next pass re-read all of them, which is exactly the full scan
this PR exists to remove, plus a spurious re-publish of every row.

Renames `listingComplete` to `complete`: it now covers entry reads too.
2026-09-01 02:53:05 -07:00
NeilandNeil dff2ff0ec3 fix(git): read the diff working tree and stamp through the host path spelling (#17896)
Git can execute inside a WSL distro against a raw Linux worktree path while Node,
on the Windows side, reads the same files back through Win32. `path.join(
'/home/me/repo/feature', 'src/file.ts')` on win32 produces the drive-relative
`\home\me\repo\feature\src\file.ts`, which resolves against whatever the current
drive happens to be and almost always ENOENTs. The same mis-spelling hits the
drvfs form, where `/mnt/c/repo` should read as `C:\repo`.

Two consequences, both on the Node side only (git already works, because it gets
the Linux path as its cwd and resolves it inside the distro):

- getDiff's unstaged working-tree read missed, `readWorkingTreeFile` mapped ENOENT
  to `exists: false`, and an existing file rendered as DELETED in the diff view.
- `readWorktreeDiffStamp` could not find `.git`, so the stamp was null, the settled
  diff cache neither hit nor stored, and every diff respawned `git show` - two
  `wsl.exe` spawns the cache exists specifically to avoid.

Both now spell the worktree directory for the reading host first, via a new
`resolveWorktreeHostPath` wrapper around the resolver that landed in #17804.
The wrapper exists because `resolveGitMetadataPath` trims: a gitfile payload
carries a trailing newline, but a directory name may legally begin or end with
whitespace on POSIX, so the wrapper keeps the caller's spelling whenever the
resolver only trimmed it. The stamp's opaque `value` still embeds the caller's
original `worktreePath`, so settled-cache identity is byte-identical and no cache
key moves.

`readWorktreeDiffStamp` was already `Promise<WorktreeDiffStamp | null>` with one
caller that treats null as a cache miss, so no new nullability enters the type
system and the resolver's never-null-for-a-non-empty-pointer contract is
untouched. The only unspellable input is an empty worktree path, handled locally
as "not provably unchanged" in the stamp and as a read *failure* (not a proven
deletion) in file-diff.

What changes for users

| Platform | Delta |
|---|---|
| macOS | No change. An absolute POSIX path is returned verbatim, including one whose directory name carries leading or trailing whitespace. |
| Linux | No change. Same reason. |
| Native Windows (no WSL) | No change. A `C:\...` or `\\server\share\...` path is already absolute for win32 and passes through verbatim. |
| Windows + WSL, UNC worktree path (`\\wsl.localhost\Ubuntu\...`) | No change. Already absolute for win32; passes through verbatim. This is today's common case. |
| Windows + WSL, drvfs worktree path (`/mnt/c/repo`) | Fixed. Reads as `C:\repo` instead of the drive-relative `\mnt\c\repo`. Needs no distro name. |
| Windows + WSL, Linux worktree path with a named distro (`/home/me/repo`) | Fixed. Reads as `\\wsl.localhost\Ubuntu\home\me\repo`. The deleted-file misrender goes away and the diff cache starts hitting. |
| Windows, POSIX path, no distro and not a drvfs mount | No change. Passes through verbatim, same ENOENT, same existing fallback. |
| SSH | No change. `runtime-git-diff-commands.ts` and the `git:diff` IPC both route to `provider.getDiff` for a connection, so this local code is never reached. |
| Relay / remote | No change. No RPC param, wire field, stream opcode, or published content is touched; the relay host runs the same local code and gets the same fix. |
| Folder workspace (non-git) | No change. `.git` is absent either way, `resolveGitDir` returns the same fallback, and the stamp stays null exactly as today. |
| GitLab / other providers | Not applicable. No provider-specific or review code is touched. |

What this does NOT do

- It does not fix `resolveGitDir` itself. For a drvfs repo whose worktree Orca
  already spells `C:\repo\feature`, the gitfile payload `gitdir: /mnt/c/repo/.git/
  worktrees/feature` is still mis-resolved by `path.resolve` to
  `C:\mnt\c\repo\.git\...`, so the stamp still returns null in that shape. Separate
  change, separate PR; this one neither fixes nor regresses it.
- It does not touch submodule path resolution. `resolveSubmoduleWorktreePath` is
  the path-escape guard and has a near-identical twin in the relay; changing it
  without escape tests on both is out of scope.
- It does not change `readHeadComponent`'s `commondir` resolution. The relative
  `../..` git actually writes takes the identical `path.resolve` branch, and an
  absolute POSIX `commondir` under a WSL UNC `gitDir` already resolves correctly
  because the UNC root is `\\wsl.localhost\<distro>\`.
- It does not reorder drvfs-before-UNC inside the shared resolver. That changes the
  identity of returned strings and needs a real Windows+WSL box.
- It does not add any Git command, option, or version dependency.

Costs and residual risk

- One extra pure function call per diff read. No I/O added or removed on the
  unaffected paths.
- Translation still trims. `resolveWorktreeHostPath` preserves whitespace only when
  no translation happened; a guest directory named `/home/me/repo ` loses its
  trailing space on a Windows reader. Reachable only on win32, where such a name is
  not addressable anyway, and the previous behavior for that shape was a
  drive-relative miss.
- A relative worktree path (no caller passes one) is now resolved against the
  process cwd instead of joined relative to it. Same file in every case except a
  relative name that itself ends in whitespace.
- `UNSPELLABLE_WORKING_TREE_READ`'s `exists`/`failed` fields are correct but not
  observable today: the stamp is null for the same input, so nothing can be cached
  and `reusable` cannot be read back. They are there so the branch stays right if
  `loadDiff` ever gains a second caller. The test pins the observable part - that no
  read lands on a cwd-relative path.
- Every test here mocks `node:fs/promises` and spoofs `process.platform`. They prove
  which path string reaches `stat`/`readFile`, which is the right assertion, but
  none of this has executed against a real 9p mount on a Windows+WSL box and this
  repo's CI has no such runner.
- Honest framing of the trigger: I could not demonstrate a mainline path that hands
  `getDiff` an untranslated POSIX worktree path on Windows today -
  `translateWslOutputPaths` UNC-translates worktree paths whenever a distro is
  known, `getWslHome` returns the UNC spelling, and `resolveWslRepoWorktreeBasePath`
  normalizes a configured Linux base. The drvfs case is the most plausible live one.
  Treat this as defense-in-depth that is a strict no-op on every configuration above
  except the two marked Fixed.

Verification

- `npx vitest run src/main/git src/shared/git-metadata-path.test.ts` -> 196 files /
  2241 tests passed, 2 files and 5 tests skipped. One failure,
  `git-admission-storm-measurement.test.ts > reports bounded-concurrency before and
  after measurements` (ENOENT scandir on its own temp state dir), is pre-existing
  and environmental: it fails identically in isolation and spawns real git children
  without touching any changed module.
- `npx vitest run src/main/git/status-diff-settled-cache.test.ts` -> 21/21 (16
  pre-existing, 5 new). `npx vitest run src/shared/git-metadata-path.test.ts` ->
  25/25 (19 pre-existing, 6 new cases across 3 new tests).
- `npx oxfmt --write` then `npx oxlint` on all five changed files -> clean.

Mutation checks - all eight production substitutions were reverted one at a time
and the suite re-run. Each fails at least one test, and no new test survives its
own mutation:

| Reverted | Failing test |
|---|---|
| file-diff working-tree read -> `worktreePath` | reads the working tree through the host spelling instead of reporting a deletion; invalidates when the working tree file is edited under the host spelling |
| stamp working-tree component -> `worktreePath` | invalidates when the working tree file is edited under the host spelling |
| stamp `.gitmodules` stat -> `worktreePath` | invalidates when .gitmodules appears under the host spelling |
| stamp `resolveGitDir` -> `worktreePath` | stamps through the host spelling so the second read does not respawn git |
| `options` threading at the `readWorktreeDiffStamp` call | stamps through the host spelling...; invalidates when .gitmodules appears... |
| wrapper's untrimmed preservation -> return the resolver's value | keeps whitespace that belongs to the directory name (both cases) |
| `UNSPELLABLE_WORKING_TREE_READ` -> a cwd-relative `readWorkingTreeFile` | reads nothing relative to the cwd when the worktree path has no host spelling |
| stamp's null early return -> `hostWorktreePath ?? worktreePath` | reads nothing relative to the cwd when the worktree path has no host spelling |

The settled-cache tests seed the fake filesystem through the platform-bound `path`
module rather than `path.win32`, so they assert real behavior on a POSIX CI host as
well as on Windows and are not gated on the host platform.

Co-authored-by: Neil <79079362+brennanb2025@users.noreply.github.com>
2026-09-01 02:39:48 -07:00
NeilandNeil e4a708da17 fix(wsl): read untracked line counts through the host's worktree spelling (#17897)
Untracked line counts, and the untracked share of the branch line total, come
from direct lstat/open calls rather than from git. When git executes inside a
WSL distro the worktree path can be a guest path, which Win32 reads as
drive-relative (`/home/me/repo`) or as a literal `C:\mnt\c\repo`. Every lstat
then fails, countFileAdditions swallows the error into `{}`, the file renders
with no +N, and the branch total silently undercounts it.

Route only those two filesystem reads through resolveWorktreeFilesystemPath,
which translates a guest-rooted path via the shared resolveGitMetadataPath.
Both call sites produce the same string, so the stat-keyed untracked cache
still hits across them. resolveGitMetadataPath itself is not modified, so its
other callers are untouched.

The wrapper translates only when the host is win32 and the path is a guest
spelling: exactly one leading slash, and unchanged by trim(). Each condition
is load-bearing.

- `//wsl.localhost/...` and `//wsl$/...` are UNC spellings that also start with
  `/`, and translating one prepends the distro root a second time, so a
  worktree that reads fine today would ENOENT on every untracked file. The same
  single-leading-slash guard is used, for the same reason, by
  resolveWslRepoWorktreeBasePath in src/shared/wsl-paths.ts.
- resolveGitMetadataPath returns `rawPath.trim()`, so a worktree directory name
  with leading or trailing whitespace (legal on ext4) would be re-spelt onto a
  different directory. Leaving those verbatim keeps them exactly as they read
  today.
- Off win32 the resolver is already an identity for these inputs; the platform
  check makes the macOS/Linux no-op structural rather than derived.

Per platform:
- Windows + WSL, worktree spelled as a guest path: untracked +N appears and the
  branch total includes it.
- Windows + WSL, worktree already spelled `\\wsl.localhost\...`,
  `//wsl.localhost/...` or `//wsl$/...`: unchanged, returned verbatim.
- Native Windows: `C:\...` is unchanged. One shape does change: a
  `/mnt/<drive>/...` worktree now resolves to `<Drive>:\...` instead of being
  passed through to a guaranteed lstat failure. Native Windows does not produce
  that spelling, and if it were reached the new result is the correct file.
- macOS / Linux: unchanged; not win32, returned verbatim, whitespace included.
- SSH / relay: unchanged. Remote status runs in the relay, which builds its own
  branch-total input and does not pass filesystemWorktreePath.
- Folder workspaces / GitLab: unaffected, no workspace-kind or provider
  behavior is touched.

No fail-closed degradation: attachLineStats still returns
`stagedStats !== null && unstagedStats !== null`, and createBranchLineTotalInput
still has no early return. The wrapper returns `string`, never null, so an
unmappable worktree keeps its old spelling and its old (missing) untracked
counts rather than dropping the staged/unstaged counts as well.

The `filesystemWorktreePath` field on computeGitBranchLineTotal is optional and
does not touch the lease key, so the coalescing/cooldown identity is unchanged.

Co-authored-by: Neil <neil@orca.local>
2026-09-01 02:39:31 -07:00
Neil 0443d42638 fix(git): give WSL-hosted repos one FETCH_HEAD lock lane (#17898)
The fetch lock key is the worktree's resolved common Git directory. On a Windows
host two derivations split one repo into several lanes, so sibling fetches on the
same repo race the FETCH_HEAD write the lock exists to serialize.

- Windows aliases \\wsl$ to \\wsl.localhost and folds the distro name and any
  drvfs tail case-insensitively, so two spellings of one repo produced two keys.
  The finished key now goes through foldWslUncPathCaseInsensitiveParts. This is a
  pure function of the finished key, so equal keys stay equal: it can only merge.
- Under a drive-spelled base, git-in-WSL's `/mnt/c/repo/.git` gitfile and
  commondir pointers are read by path.resolve as the non-existent
  C:\mnt\c\repo\.git. The commondir read then fails and every linked worktree got
  its own dead-end key while the main checkout keyed on C:\repo\.git. Such a
  pointer now goes through toWindowsWslDrivePath.
- realpath and stat take no AbortSignal, so cancelling a fetch still blocked
  behind a hung 9P/UNC lookup. Both are wrapped in waitForPromiseWithSignal. The
  rejection keeps today's synthetic AbortError shape, including when the caller
  aborts with its own reason, because callers classify on error.name.
- The hand-rolled gitfile regex is replaced by the shared
  parseGitdirMarkerPayload, matching git's own read_gitfile_gently.

A WSL UNC base is deliberately excluded from the pointer translation. win32
path.resolve already carries such a base's distro onto a guest-rooted pointer
(\\wsl.localhost\Ubuntu\home\me\wt + /mnt/c/repo/.git ->
\\wsl.localhost\Ubuntu\mnt\c\repo\.git), and a main worktree's `.git` is a
directory with no pointer to translate, so its key stays on that UNC spelling.
Rewriting only the linked worktrees to C:\... would have split one repo across
two lanes - the opposite of the intent. A test now pins that layout.

Direction of every key change, re-derived over a ten-layout matrix that runs both
this code and an emulation of the pre-change derivation under Win32 path rules:
nine layouts either merge or are byte-identical. The fold is merge-only by
construction; the pointer translation's sole delta is C:\mnt\c\X -> C:\X, and
C:\mnt\c\X is derived from bytes that live at C:\X, so it can only join a
worktree to its own common dir. The tenth layout is the one narrowing: the shared
parser accepts `gitdir:` only at offset 0 where the old regex accepted it on any
line, so a `.git` file with a leading blank line falls through to the parent walk
(C:\repo\.git\FETCH_HEAD -> C:\.git\FETCH_HEAD). Nothing can race there - git
2.44 refuses that same file with `fatal: invalid gitfile format`, so no fetch
runs in such a worktree at all. Native Windows repos with no WSL, SSH, relay and
folder workspaces are byte-identical.

hostPath() returns the same node:path submodule Node itself selects, so it is a
no-op on every real host; it exists so the Win32 derivation is testable off
Windows. resolveGitFetchHeadCommand's argument parsing is untouched, and
--git-dir gitfile dereferencing is deliberately not added: it would make N
worktrees of one repo serialize fetches that run in parallel today.
2026-09-01 02:39:17 -07:00
Neil f2db24bca0 fix(worktree): reclaim a prepared checkout whose discard failed in-process (#17899)
Speculative create-preparation evicts entries past the 3-entry limit and the
5-minute TTL, and both paths swallowed a failed `discardPreparedWorktree`.
The only other reclaim path, `cleanupStalePreparations`, skips any preparation
whose lock-reason pid is still alive, so a discard that failed inside the
running app stranded its scratch checkout and its locked worktree registration
until restart.

Record the failed discard keyed by host (repo path + WSL distro) and prepared
path, and retry it the next time a fresh preparation starts for that host.
The retry is kicked off before `listWorktreeGraph` but never awaited, so it
runs alongside the stale scan and the `worktree add` instead of sitting in
front of the user's create. It is capped at 3 attempts and warns when it gives
up. Enrolment is unconditional: `prepareWorktreeCreateCheckout` self-discards
on failure, but only best-effort, so a checkout that failed on a busy handle
can strand the same registration.
2026-09-01 02:39:03 -07:00
Neil 95cd6969a0 docs(wsl): note that the distro VHDX is a high-water mark, not a leak (#17900)
WSL2 keeps the guest filesystem in a dynamically-expanding ext4.vhdx.
Deleting files inside the distro frees the blocks for ext4 to reuse but
never shrinks the host-visible file, so engineers watching speculative
worktree preparation and mirrored worktrees write into a distro see the
vhdx grow and reasonably ask whether we leak disk.

Records the measurement taken on WSL 2.7.11.0 / Ubuntu-24.04 (a second
fresh incompressible 1 GiB after deleting the first cost zero growth,
measured as size on disk via GetCompressedFileSize), how to locate the
vhdx across all three install layouts, and a complete elevated diskpart
recipe for compacting existing slack. Scopes the sparse-flag observation
to the measured machine and states that peak-tracking is the best case
for block reuse, not a guarantee against drift.

The reclaim steps state their preconditions rather than reading as
directly runnable: --set-sparse needs the distro stopped and WSL 2.5 or
newer. .wslconfig is given as %UserProfile%\.wslconfig -- it lives in
the Windows user profile, not inside the distro at ~/.wslconfig.

Per-platform delta: documentation only, no production code. No behavior
change on macOS, Linux, native Windows, WSL, SSH, relay, folder
workspaces, or any git provider.
2026-09-01 02:38:47 -07:00
Jinjing 997d3e1b55 Import browser cookie search to tab (#17627)
* Add browser history search to the new-tab omnibox

- Display matching pages from browser history in the tab entry panel
- Extract address-bar history scoring into reusable `browser-history-match` module
- Rank by match tier (host prefix > substring > title > tail) then frecency

* Replace History icon with ExternalLink for browser search results

* Replace ExternalLink with Globe icon for browser search results

* Fix browser history matching for workspace docs and recency rankings

Promote path-prefix matches to top tier for entries without a host (workspace
docs), and clamp the recency bonus so future timestamps cannot outrank fresh
visits. Includes tests for both path-prefix promotion and recency bonus
clamping behavior.

* Cache browser history by identity and snapshot omnibox entries

- Use WeakMap to cache prepared browser history entries by identity, so
  re-parsing is skipped for the same snapshot
- Change omnibox to read a history snapshot at menu open (via getState)
  instead of subscribing to live updates, preventing background navigations
  from reshuffling results mid-keystroke
- Support fully-qualified URL prefix matching (e.g., `https://github.com`)
  to preserve address-bar behavior
- Fix percentile calculation in performance tests (nearest-rank method)

* Break browser history ties with URL for stable snapshot ordering

When browser history entries tie on tier, score, and recency, the sort
order can become non-deterministic, especially when combining browser and
document history that may be reordered in snapshots. Add normalizedUrl
as the final tie-breaker to guarantee consistent ordering.
2026-09-01 02:31:29 -07:00
Neil a573c5706b docs(child-process): note split-relocated allowlist entries 2026-09-01 02:07:33 -07:00
Neil c31e7b0d9a docs(main): restore rationale comments lost in the startup and cookie splits 2026-09-01 02:07:33 -07:00
Neil 15c4f1c447 fix(runtime): defer process incarnation lookup for plain sends
(cherry picked from commit d522a3e15a56c0d6d1b92b67414d51f6bf78e987)
2026-09-01 02:07:33 -07:00
Neil 1d9b5306ac chore(child-process): prune stale import allowlist
(cherry picked from commit 92d39fc984)
2026-09-01 02:07:33 -07:00
Neil 83513b82d1 test(terminal): follow Hangul lifecycle extraction
(cherry picked from commit b62361fad271809819aa01711e437197065e3f75)
2026-09-01 02:07:33 -07:00
Neil d5db0bedfc fix(main): route browser cookie key commands through runner
(cherry picked from commit e321eccb41)
2026-09-01 02:07:33 -07:00
Neil de84a9f999 fix(main): resolve runtime lazily in observers
(cherry picked from commit 369496b27c)
2026-09-01 02:07:33 -07:00
Neil d462766cb0 refactor(main): split filesystem git remote handlers
(cherry picked from commit 2146cff06a)
2026-09-01 02:07:33 -07:00
Neil fbaa125a35 fix(main): merge split startup imports
(cherry picked from commit c0f38db5de)
2026-09-01 02:07:33 -07:00
Neil 5b4e7edb50 refactor(main): split backend services and startup
(cherry picked from commit a33573328b)
2026-09-01 02:07:33 -07:00