Skip to content

fix(worktrees): show CLI-created local worktrees in the sidebar while a remote runtime is active - #6628

Merged
brennanb2025 merged 11 commits into
stablyai:mainfrom
smk508:smk508/fix-local-worktree-visibility-runtime-active
Jul 22, 2026
Merged

brennanb2025 merged 11 commits into
stablyai:mainfrom
smk508:smk508/fix-local-worktree-visibility-runtime-active

Conversation

@smk508

@smk508 smk508 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Worktrees created outside Orca for a local, unbound repo do not appear in the sidebar while a remote runtime is active — they only show up after an app restart, even though their sessions are already running. This is the common case for CLI/automation flows that call orca worktree create against a local project while a remote runtime (SSH/runtime host) is focused.

Root cause. The renderer's local worktrees:changed handler (useIpcEvents.ts) early-returns whenever any remote runtime is active:

if (isRuntimeEnvironmentActive()) {
  // local worktree events carry local repo ids. Fetching the
  // active runtime with those ids can purge or overwrite server state.
  return
}

The guard is there for a real reason: an unbound repo's list fetch routes to the active runtime (settingsForKnownRepoOwner's unbound fall-through), so refreshing with local worktree ids could query — and purge against — the remote host. But the cost is that all local worktree events are dropped, and there is no watcher/poller to recover them, so the only refresh is the authoritative git worktree list scan on app restart (which also suspends running agents).

Fix. Instead of dropping the event, pin the refresh to the local host (forceLocalOwner):

  • fetchWorktrees(repoId, { forceLocalOwner: true }) lists the worktrees against the local owner (strips activeRuntimeEnvironmentId for the list fetch only), gated on a local hostId so a repo genuinely bound to a runtime/SSH host is never force-listed locally.
  • The existing host-scoped merge then only touches local-host worktrees, and the repo-wide deletion-purge is skipped on this path, so the refresh is additive: it can only add local worktrees, never overwrite the active runtime's worktree state.

This touches only the renderer event path and the list-fetch owner resolution. It does not change settingsForKnownRepoOwner (push-target / git-mutation routing is unchanged) or the IPC contract.

Related prior art: #5104 / #6241 address externally-created worktrees in the no-runtime-active case; this PR is the distinct remote-runtime-active variant of the same visibility gap, which those changes leave intact (#6241 explicitly makes no renderer event-contract changes).

Screenshots

No new UI elements — the fix restores the sidebar rows that already exist for local worktrees. The change is behavioral (when the rows appear), so it is covered by unit regression tests rather than a static screenshot.

Manual repro (before this PR):

  1. Register a local project and a remote runtime; make the remote runtime the active one.
  2. From a terminal: orca worktree create <local-repo> --agent ... --prompt ....
  3. Before: the new worktree never appears in the sidebar until you restart Orca. After: it appears live, with no restart and no agent suspension.

Testing

  • pnpm lint (oxlint on the touched files; pre-commit lint-staged ran oxlint + react-doctor clean)
  • pnpm typecheck (typecheck:web, clean)
  • pnpm test (touched suites: worktrees.test.ts, worktree-change-refresh-queue.test.ts, useIpcEvents.test.ts — 237 passing; full suite not run locally)
  • pnpm build (not run locally — change is confined to type-checked renderer code with no new build inputs)
  • Added regression tests:
    • worktrees.test.ts → "pins the list fetch to the local host when forceLocalOwner is set" (proves the routing fix: an unbound repo with a remote runtime active lists local instead of the runtime).
    • worktree-change-refresh-queue.test.ts → threads forceLocalOwner through the queue and does not coalesce a local-pinned refresh into a runtime-routed one.

AI Review Report

Reviewed the diff for correctness, regression risk, and cross-platform behavior:

  • Owner-routing safety: confirmed forceLocalOwner only strips activeRuntimeEnvironmentId when hostId === LOCAL_EXECUTION_HOST_ID, so a runtime/SSH-bound repo is never force-listed locally. settingsForKnownRepoOwner (push-target routing) is untouched — verified the existing "hydrates a missing linked GitHub PR push target through the active remote runtime" test still passes.
  • No state clobber: the merge is host-scoped (mergeWorktreesForHost keyed on hostId) and the repo-wide deletion-purge (getAuthoritativeDetectedWorktreeIds, not host-scoped) is skipped on the forceLocalOwner path, so a local-only scan cannot mis-read remote-host worktrees as deletions. Worst case is a deleted local worktree lingering until the next unguarded full refresh — strictly better than today's drop-everything.
  • Coalescing: the refresh queue no longer folds a local-pinned refresh into an adjacent runtime-routed one (covered by a new test).
  • Cross-platform: no platform-specific code paths, shortcuts, labels, shell, or path handling touched. The change is pure TS in the renderer store/hook layer; behavior is identical on macOS/Linux/Windows. The pre-existing Windows/OneDrive burst-coalescing comment still holds.

Security Audit

No new input handling, command execution, path handling, auth, secrets, or IPC surface. forceLocalOwner is computed renderer-side from existing app state (isRuntimeEnvironmentActive()); the worktrees:changed IPC payload is unchanged. No dependency changes. No follow-up security work identified.

Notes

  • The deeper architectural direction (host-qualified worktree identities, e.g. Use host-qualified worktree identities #6096) would decouple sidebar presentation from mutation routing more completely; this PR is a targeted, low-risk fix for the user-visible regression that does not block that work.
  • A full Electron before/after screen recording can be added on request — happy to provide if a maintainer wants it on the PR.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b7a642ae-e7ab-4cc3-8638-83fd020da0a6

📥 Commits

Reviewing files that changed from the base of the PR and between 0a4c00d and 04d7434.

📒 Files selected for processing (5)
  • src/renderer/src/hooks/useIpcEvents.ts
  • src/renderer/src/store/slices/worktree-helpers.ts
  • src/renderer/src/store/slices/worktrees.test.ts
  • src/renderer/src/store/slices/worktrees.ts
  • tests/e2e/local-worktree-visibility-runtime-active.spec.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/renderer/src/hooks/useIpcEvents.ts
  • tests/e2e/local-worktree-visibility-runtime-active.spec.ts
  • src/renderer/src/store/slices/worktree-helpers.ts

📝 Walkthrough

Walkthrough

A forceLocalOwner flag now flows through the worktree refresh path. The event and queue contracts carry it, fetchWorktrees and fetchWorktreeLineage accept it and can clear activeRuntimeEnvironmentId for local hosts, and handleWorktreesChanged skips deletion-diff purge logic when it is set. The IPC listener now always enqueues refreshes during runtime activity with forceLocalOwner derived from runtime state. Tests were updated for the new call signatures and for local worktree visibility while a remote runtime is active.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main user-visible fix for CLI-created local worktrees during an active remote runtime.
Description check ✅ Passed The description follows the template well with complete Summary, Screenshots, Testing, AI Review Report, Security Audit, and Notes sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

❤️ Share

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

@smk508

smk508 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

Verification

Concrete proof for the testing checklist above.

Regression test catches the bug (before → after)

The new test worktrees.test.ts → "pins the list fetch to the local host when forceLocalOwner is set" reproduces the exact bug: an unbound repo with a remote runtime active. Run against the unfixed fetchWorktrees (origin/main), it fails — the worktree never lands because the list fetch routed to the runtime instead of the local host:

FAIL  src/renderer/src/store/slices/worktrees.test.ts > pins the list fetch to the local host when forceLocalOwner is set
- Expected: [ { id: 'repo1::/local/wt1', path: '/local/wt1', repoId: 'repo1', ... } ]
+ Received: undefined
  expect(store.getState().worktreesByRepo.repo1).toEqual([local])
                                                 ^
 Test Files  1 failed (1)

With the fix applied, it passes (routes to local, listDetected called once, runtimeEnvironmentCall not called).

Full touched-suite run (with fix)

$ npx vitest run worktrees.test.ts worktree-change-refresh-queue.test.ts useIpcEvents.test.ts
 Test Files  3 passed (3)
      Tests  237 passed (237)

Typecheck + lint

$ pnpm typecheck:web      # tsgo --noEmit
exit 0 (no errors emitted)

$ pnpm exec oxlint <4 touched source files>
exit 0 — 0 findings

The pre-commit hook (lint-staged: oxlint + oxlint react-doctor + oxfmt) also ran clean on the commit.

Not yet run

  • Full pnpm test / pnpm build — the change is confined to type-checked renderer store/hook code with no new build inputs; happy to run them or attach an Electron before/after screen recording if a maintainer wants it on the PR.

@smk508

smk508 commented Jun 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Visual before/after (autonomous headless E2E)

Captured by a Playwright + _electron.launch() spec running fully headless — no human in the loop. The repro is faithful: it drives the real orca worktree create path (the CLI's RuntimeClient connects to the running app's socket and calls worktree.create), which registers a managed worktree and fires the worktrees:changed IPC. The "remote runtime active" condition is staged hermetically by injecting activeRuntimeEnvironmentId into the renderer store — no real remote host needed.

Each run: create a control worktree (no runtime active), then activate a runtime and create a second worktree via the same CLI path. The only difference between the two images is this PR.

Before (origin/main — the bug): the control worktree appears, but the worktree created while a runtime was active is missing from the sidebar.

before — bug

After (this PR — fixed): the runtime-active worktree now appears live, no restart.

after — fixed

The spec is committed as a regression test (tests/e2e/local-worktree-visibility-runtime-active.spec.ts): it fails on origin/main (the runtime-active worktree never appears, 15s timeout) and passes on this branch. .webm recordings of both runs were captured alongside these stills.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c50f1a6c-a5be-4242-9657-96570f437505

📥 Commits

Reviewing files that changed from the base of the PR and between 39b63e93392fb26fde4f971900ef611c77ebc82b and a6ef1f7d0303565f19565340db0548a3740a0c85.

📒 Files selected for processing (1)
  • tests/e2e/local-worktree-visibility-runtime-active.spec.ts

smk508 added 2 commits June 28, 2026 18:43
… runtime is active

When a remote runtime is active, a local `worktrees:changed` event for an
unbound repo was dropped by the renderer guard in useIpcEvents. Worktrees
created outside Orca for that repo (e.g. `orca worktree create` from a CLI or
automation flow) therefore stayed invisible in the sidebar until an app
restart, even though their sessions were already running.

The guard existed because an unbound repo's list fetch routes to the active
runtime (settingsForKnownRepoOwner's unbound fall-through), so refreshing with
local worktree ids could query — and purge against — the remote host.

Instead of dropping the event, pin the refresh to the local host
(forceLocalOwner): fetch the worktree list against the local owner and merge
additively. The merge is host-scoped and the deletion-purge is skipped on this
path, so it only ever adds local-host worktrees and never overwrites the active
runtime's worktree state. A genuinely-removed local worktree is reclaimed by
the next unguarded full refresh.
…untime is active

Drives the real `orca worktree create` path: the CLI RuntimeClient calls
`worktree.create` over the app's socket, registering a managed worktree and
firing the `worktrees:changed` IPC the renderer listens for. Stages a remote
runtime as active by injecting `activeRuntimeEnvironmentId` into the renderer
store, so no real remote host is needed. Fails on the prior behavior (the
worktree never appears while a runtime is active) and passes with this fix.
@smk508
smk508 force-pushed the smk508/fix-local-worktree-visibility-runtime-active branch from a6ef1f7 to 0a4c00d Compare June 28, 2026 22:43
@brennanb2025

Copy link
Copy Markdown
Contributor

Taking a look 👀

brennanb2025 and others added 2 commits June 29, 2026 18:06
…-active

Resolve worktrees.ts fetchWorktrees conflict: keep upstream's getProjectHostSetupForRepoHost(setup) line alongside the forceLocalOwner list-fetch override.
@ghost

ghost commented Jun 30, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 4 individual chapters for you:

Title
1 Add forceLocalOwner option to worktree store
2 Thread forceLocalOwner through the refresh queue
3 Update IPC event handlers to use local refresh
4 Verify fix with E2E regression test
Open in Stage

Chapters generated by Stage for commit 7671a3f on Jun 30, 2026 8:32pm UTC.

@smk508

smk508 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@brennanb2025 lmk if you have any questions

@AmethystLiang AmethystLiang added the orca-remote-server Remote Orca Server / orca serve / paired remote runtime label Jul 22, 2026
…rktree-visibility-runtime-active

# Conflicts:
#	src/renderer/src/hooks/useIpcEvents.ts
#	src/renderer/src/store/slices/worktree-helpers.ts
#	src/renderer/src/store/slices/worktrees.ts
@smk508

smk508 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Synced this branch with current main (was 24 days / ~1,295 commits behind). Merged rather than rebased since a rebase would force-push over the review history here — merge just adds a commit on top.

Three conflicts, all in the exact area this PR touches:

  • useIpcEvents.ts — the worktrees.onChanged handler still carries the original isRuntimeEnvironmentActive() early-return on current main (unchanged by Refresh project worktrees created outside Orca #6241), so this fix is still needed as-is. Kept this PR's behavior, adopted main's shortened comment style elsewhere in the same handler.
  • worktree-helpers.ts / worktrees.ts — main added an unrelated fetchAllWorktrees({ hydrationPurge }) option and a reuseRecentCompatibilityFailure flag on refreshWorktreeLineageForSettings since this PR was opened. Combined both with this PR's forceLocalOwner threading — orthogonal changes to the same functions, no actual overlap in behavior.

Re-verified after the merge:

  • typecheck:web — clean
  • targeted tests (worktrees.test.ts, useIpcEvents.test.ts, worktree-helpers.test.ts) — 288 passed (up from 237 at the original open, main added more in the interim)
  • oxlint on the touched files — clean
  • the load-bearing worktrees.test.ts case this PR was originally careful not to break ("hydrates a missing linked GitHub PR push target through the active remote runtime") still passes

@brennanb2025 — following up since this has been open a few weeks; happy to answer anything if it helps get eyes on it.

@brennanb2025

brennanb2025 commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Really sorry for the delay! Got wrapped up in other stuff, thanks for the reminder here. 😓

@brennanb2025

Copy link
Copy Markdown
Contributor

Reviewed in depth and pushed a few commits on top — the core approach here is right: pinning the refresh to the local owner instead of dropping the event keeps the remote-runtime protection intact while fixing the visibility gap.

What we added during review:

  • Duplicate repo ids route locally (5c90c0e): when the same repo id is registered both locally and on the focused runtime, the local event now resolves to the local owner instead of silently no-op'ing; runtime/SSH-only repos are still never redirected (test-covered both ways).
  • Origin-tagged events (24b885d): local worktrees:changed events now always carry forceLocalOwner — the flag marks the event's origin rather than sampling runtime state at enqueue, so queue delays and runtime focus changes can't misroute a refresh. The deletion-diff skip is gated on actual runtime overlap, so no-runtime behavior is byte-identical to before.
  • Small hardening: rename-grace expiry now sweeps before every early return (no session-long map growth), queue coalescing compares the flag as a boolean, and the e2e locator uses JSON-escaped attribute values (the previous quoting would have broken on Windows paths).
  • Tests: a wiring test pins the origin-based flag with a no-runtime assertion that fails under the old sampling code, plus a duplicate-id test proving a forced-local scan preserves runtime-hosted rows.

Validation: typecheck/lint green, 298 unit tests across the touched suites, and the e2e spec passes on this branch while failing at exactly the target assertion with the old drop-guard restored — so the regression is genuinely guarded. Perf-audited: steady-state cost while a runtime is active now equals what the app always paid with no runtime active, with burst coalescing intact.

Thanks @smk508 — clean fix for a real CLI/automation pain point.

@brennanb2025
brennanb2025 merged commit a356b9d into stablyai:main Jul 22, 2026
2 checks passed
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
… a remote runtime is active (stablyai#6628)

* fix(worktrees): refresh local worktrees in the sidebar while a remote runtime is active

When a remote runtime is active, a local `worktrees:changed` event for an
unbound repo was dropped by the renderer guard in useIpcEvents. Worktrees
created outside Orca for that repo (e.g. `orca worktree create` from a CLI or
automation flow) therefore stayed invisible in the sidebar until an app
restart, even though their sessions were already running.

The guard existed because an unbound repo's list fetch routes to the active
runtime (settingsForKnownRepoOwner's unbound fall-through), so refreshing with
local worktree ids could query — and purge against — the remote host.

Instead of dropping the event, pin the refresh to the local host
(forceLocalOwner): fetch the worktree list against the local owner and merge
additively. The merge is host-scoped and the deletion-purge is skipped on this
path, so it only ever adds local-host worktrees and never overwrites the active
runtime's worktree state. A genuinely-removed local worktree is reclaimed by
the next unguarded full refresh.

* test(e2e): regression — CLI-created worktree visible while a remote runtime is active

Drives the real `orca worktree create` path: the CLI RuntimeClient calls
`worktree.create` over the app's socket, registering a managed worktree and
firing the `worktrees:changed` IPC the renderer listens for. Stages a remote
runtime as active by injecting `activeRuntimeEnvironmentId` into the renderer
store, so no real remote host is needed. Fails on the prior behavior (the
worktree never appears while a runtime is active) and passes with this fix.

* fix(worktrees): pin local lineage refresh during runtime activity

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

* review: trim comments to house style, normalize queue coalescing to booleans

* review: sweep rename-grace expiry before early returns in worktrees:changed handler

* review: document accepted workspace-space gap, drop imprecise 'additive' wording

* fix(worktrees): route duplicate local repo events locally

* fix(worktrees): tag local worktree events at origin, gate purge skip on runtime overlap

* test: pin origin-based forceLocalOwner with a no-runtime local event assertion

---------

Co-authored-by: brennanb2025 <brennankbenson@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

orca-remote-server Remote Orca Server / orca serve / paired remote runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants