Skip to content

fix(worktrees): let repos reveal coding-agent scratch worktrees - #10560

Closed
jeongph wants to merge 1 commit into
stablyai:mainfrom
jeongph:fix/agent-scratch-worktree-visibility
Closed

jeongph wants to merge 1 commit into
stablyai:mainfrom
jeongph:fix/agent-scratch-worktree-visibility

Conversation

@jeongph

@jeongph jeongph commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Since #9535 (fixing #9388), worktrees under <repo>/.claude/worktrees/** and <repo>/.gsd-workspaces/** are classified agent-scratch and suppressed unconditionally. That stops sub-agent fan-out from flooding the sidebar, which is the right default — but it left no way out and no trace.

A worktree created by Claude Code's claude -w lands in <repo>/.claude/worktrees/<name>. When a user starts parallel work there and wants to continue it in Orca, the worktree is simply gone:

  • no sidebar row
  • setting the repo to show non-Orca worktrees has no effect — shouldShowWorktree returns on the agent-scratch branch before it reaches that check
  • the Non-Orca worktrees dialog reports 0 worktrees currently shown while the worktree sits on disk, because every surface funnels through isUserFacingExternalWorktree, which excludes scratch
  • importedExternalWorktreePaths is the only field that overrides the gate, and its only writer is the new-worktree inbox — which also excludes scratch

So there is no in-app path to see that the worktree exists, let alone recover it. Orca even mints worktreeMeta for these paths (with workspaceStatus: "in-progress") while the UI reports 0.

Reported after upgrading to 1.4.155 from a pre-1.4.148 build: four worktrees across three repos disappeared, all of them visible on the prior build.

This is the gap #7912 asks to close, and the diagnosis matches the confirming comment on that issue — externalWorktreeVisibility: 'show' stopped working as a workaround once the agent-scratch classification began running ahead of the visibility check.

Fix

A per-repo agentWorktreeVisibility opt-in, default hide, so #9388's protection stays the default and no migration is needed — an absent field reproduces today's behavior exactly.

  • shouldShowWorktree's single agent-scratch branch now reads the field instead of returning false. Selected-checkout, orca-managed, and explicit-import branches keep their precedence.
  • The Non-Orca worktrees dialog gains a second row that counts scratch worktrees and toggles the field. It names the containing directory — or a location count when they span more than one — because that path is the diagnostic payload. Once a repo has opted in the row stays visible even at zero, so the opt-in is always reversible.
  • isUserFacingExternalWorktree is deliberately untouched, so the discovery card and the new-worktree inbox keep excluding scratch. Two sibling accessors serve the dialog instead.
  • The project-menu entry point is unchanged.

Accounting invariant

src/shared/worktree-accounting-invariant.test.ts asserts that every worktree git worktree list reports is either unconditionally visible (selected checkout, explicit import, or orca-managed) or counted in exactly one accessor bucket. It catches a worktree being dropped from every bucket — the failure mode above — and is what should stop a future AGENT_SCRATCH_PATH_PREFIXES change from reintroducing it. (It does not catch a misclassification that retags a worktree into a different bucket; that is stated in the design doc.)

Relation to #7912

#7912 asks for agent-created worktrees to be adopted rather than hidden. Against its checklist:

  • Recognize agent-created worktrees as a distinct category — already true via the agent-scratch class from fix: never surface coding-agent scratch worktrees in the sidebar #9535.
  • Adopt them without flipping externalWorktreeVisibility for every unrelated worktree — this PR, via a separate agentWorktreeVisibility toggle.
  • Make the default discoverable — this PR; the dialog row reports the count and the containing directory.
  • Detect them promptly, without a manual refresh — not addressed here. The worktree scan caches git worktree list on a short TTL and requireAuthoritative does not bypass it, so a worktree created outside Orca takes a few seconds to appear. Separate change.

Non-goals

  • No default change. Agent scratch stays hidden unless the user opts in.
  • No AGENT_SCRATCH_PATH_PREFIXES change. Adding a location hides worktrees that are visible today — the very regression this makes recoverable — so it should not ride along.
  • No agent-session re-attribution. Running claude -w inside a split pane leaves its agent row under the original worktree, because agent rows derive from tabsByWorktree and bind at pane creation (useWorktreeAgentRows.ts:47). Different subsystem, separate discussion.

Verification

Automated:

  • src/shared, sidebar, persistence, i18n suites: 3079 passing, output pristine
  • pnpm run typecheck (all three projects): clean
  • pnpm run lint (including verify-localization-catalog, audit-localization-coverage, check-max-lines-ratchet): clean
  • three pre-existing sidebar spec failures (mobile-sidebar-onboarding-badge, SidebarToolbar, LinearAgentSkillSetupPrompt.reminder-toast) reproduce identically on the merge base and are untouched by this branch

Against the running app, driven through the repo's Playwright Electron harness with a real git worktree add <repo>/.claude/worktrees/…:

  1. detected, classified agent-scratch, visible: false, no sidebar row — the reported bug
  2. the dialog names the containing directory and offers Show
  3. Show reveals the sidebar row, the dialog stays open and flips to the shown state, and agentWorktreeVisibility persists as 'show'

That run is also what caught the path overflowing the dialog. DialogContent is a single-column grid, so a child at full content width inflates the shared column and stretches its siblings. Measured before and after:

before   dialog 540→972 (433px)   row0 564→1405   row1 564→1405
after    dialog 541→971 (431px)   row0 565→947    row1 565→947

row0 is the pre-existing non-Orca row — it was being stretched by ours and returns to normal. Fixed with min-w-0 on two containers so the path truncates instead.

Reproducing it yourself

cd <a repo registered in Orca>
git worktree add -b some-branch .claude/worktrees/scratch-example

Wait a few seconds — the worktree scan caches git worktree list on a short TTL, so a worktree created outside Orca takes a moment to appear. Then: project menu → Hide non-Orca worktrees / Show hidden worktrees → the second row reports the count and its container → Show.

Localization

The new row is filled in for all five catalogs, using each one's existing dominant terms, so it reads in the same language as the non-Orca row directly above it rather than falling back to English mid-dialog.

Design doc

docs/agent-scratch-worktree-visibility.md, force-added past the docs/** gitignore entry, following the precedent in 70ff47e and 3efc930.

Refs #7912, #9388, #9535

ELI5

Agent scratch worktrees (like Claude's .claude/worktrees) were always hidden with no way to open them in Orca. Repos can reveal those coding-agent worktrees when you need to continue work there.

@coderabbitai

coderabbitai Bot commented Jul 25, 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 Plus

Run ID: 64b8bb2b-fdbd-4c15-b519-3e1de7a77a50

📥 Commits

Reviewing files that changed from the base of the PR and between f713739 and ffcc55c.

📒 Files selected for processing (21)
  • docs/agent-scratch-worktree-visibility.md
  • src/main/ipc/repos.ts
  • src/main/persistence.test.ts
  • src/main/persistence.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/rpc/methods/repo-update-schema.ts
  • src/preload/api-types.ts
  • src/renderer/src/components/sidebar/WorktreeVisibilityDialog.agent-worktrees.test.tsx
  • src/renderer/src/components/sidebar/WorktreeVisibilityDialog.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/store/slices/repos.ts
  • src/shared/external-worktree-inbox.test.ts
  • src/shared/external-worktree-inbox.ts
  • src/shared/types.ts
  • src/shared/worktree-accounting-invariant.test.ts
  • src/shared/worktree-ownership.test.ts
  • src/shared/worktree-ownership.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • src/main/persistence.test.ts
  • src/main/persistence.ts
  • src/shared/worktree-ownership.ts
  • src/shared/external-worktree-inbox.test.ts
  • src/shared/types.ts
  • src/renderer/src/i18n/locales/en.json
  • src/main/runtime/orca-runtime.ts
  • src/shared/external-worktree-inbox.ts
  • src/renderer/src/i18n/locales/es.json
  • src/main/ipc/repos.ts
  • docs/agent-scratch-worktree-visibility.md
  • src/renderer/src/store/slices/repos.ts
  • src/renderer/src/i18n/locales/ja.json
  • src/shared/worktree-ownership.test.ts
  • src/preload/api-types.ts
  • src/shared/worktree-accounting-invariant.test.ts
  • src/renderer/src/i18n/locales/ko.json
  • src/main/runtime/rpc/methods/repo-update-schema.ts
  • src/renderer/src/components/sidebar/WorktreeVisibilityDialog.agent-worktrees.test.tsx

📝 Walkthrough

Walkthrough

Adds an optional Repo.agentWorktreeVisibility setting with validation, IPC transport, persistence, and runtime support. Agent scratch worktree detection now respects the setting, with dedicated hidden and visible accessors. The sidebar dialog adds an agent worktree row with counts, locations, show/hide controls, refresh and rollback handling, errors, and localized strings in five languages. Tests cover classification, accounting, persistence, dialog behavior, error handling, and layout constraints.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the problem, solution, scope, testing, and references, but it omits the required AI Review Report and Security Audit sections. Add the required AI Review Report and Security Audit sections, and include screenshots or explicitly document the required visual evidence.
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 (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: allowing repositories to reveal coding-agent scratch worktrees.
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.

@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: 2

🧹 Nitpick comments (1)
src/renderer/src/components/sidebar/WorktreeVisibilityDialog.agent-worktrees.test.tsx (1)

192-204: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Condense the layout-test rationale.

The 12-line comment walks through implementation details already conveyed by the test. Keep a one-line note that happy-dom cannot validate geometry and E2E covers it.

Suggested change
-    // Why: happy-dom computes no real layout — getBoundingClientRect and
-    // scrollWidth/clientWidth all report 0 (verified empirically) — so this
-    // cannot assert actual truncation or that the row stays inside the
-    // dialog. It can only guard the specific utility classes a real browser
-    // needs for that to happen, which is exactly what shipped broken
-    // silently before: `flex-1` on the label defeats truncate (flex-basis:0
-    // forces it to claim all row slack instead of shrinking), and the two
-    // ancestor `div`s between the label and DialogContent's grid need
-    // min-w-0 or their intrinsic content width — the full untruncated path —
-    // inflates the shared grid column and drags the sibling non-Orca row's
-    // width along with it. The e2e harness is the only real guard for the
-    // geometry itself; see tests/e2e/zz-agent-scratch-visibility-*.spec.ts.
+    // happy-dom cannot validate layout geometry; E2E covers actual truncation.

As per coding guidelines, “Comments must be concise, limited to non-obvious information, and preferably one line; do not explain obvious code or walk through implementation details.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 93c3e4c1-02bf-4679-8707-1ed9bfee572e

📥 Commits

Reviewing files that changed from the base of the PR and between 505967e and 2fbebce722d8cb5938d2fd93bccf2c81f166f69b.

📒 Files selected for processing (21)
  • docs/agent-scratch-worktree-visibility.md
  • src/main/ipc/repos.ts
  • src/main/persistence.test.ts
  • src/main/persistence.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/rpc/methods/repo-update-schema.ts
  • src/preload/api-types.ts
  • src/renderer/src/components/sidebar/WorktreeVisibilityDialog.agent-worktrees.test.tsx
  • src/renderer/src/components/sidebar/WorktreeVisibilityDialog.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/store/slices/repos.ts
  • src/shared/external-worktree-inbox.test.ts
  • src/shared/external-worktree-inbox.ts
  • src/shared/types.ts
  • src/shared/worktree-accounting-invariant.test.ts
  • src/shared/worktree-ownership.test.ts
  • src/shared/worktree-ownership.ts

@AmethystLiang

Copy link
Copy Markdown
Contributor

thanks for the PR. will give it some thought. potentially also just use fold it under "externalWorktreeVisibility"

@nwparker nwparker added the bug Something isn't working label Jul 27, 2026
Gldywn added a commit to Gldywn/orca that referenced this pull request Aug 6, 2026
Keeping a discovered worktree hidden was a one-way door for agent scratch.
The inbox baseline suppressed the notification, the repo-wide toggle can
never reveal scratch by design (stablyai#9388), and the dialog reported nothing was
there, so the worktree became unreachable from the UI.

The visibility dialog now owns both kinds of non-Orca worktrees, each with
the same anatomy: a repo switch that also covers worktrees created later,
and a collapsible per-worktree list to show or hide one at a time. Agent
worktrees answer to their own opt-in setting, so the non-Orca toggle still
never reveals them; a shared test pins that guarantee. Hiding a whole kind
drops that kind's explicit exceptions, otherwise an exception would outrank
the switch that was just set, and both switches roll back if the refresh
that follows them fails.

Both switches also need an authoritative listing, because hiding derives the
purge and the decision ledger from it. On a host Orca cannot reach, the
dialog says so and offers a retry instead of purging against an empty list.
Worktrees a legacy repo shows whatever the setting are left out of a kind's
counts, since no switch on this screen can move them.

A row counts as an exception when the visibility rule without imports would
hide it, not when its path sits in the recorded list, because that list can
lag behind reality. Row-level actions reuse the inbox import path so
behavior and rollback stay identical, and the recovery list ignores the
inbox baseline on purpose: the baseline suppresses a notification, it must
not block recovery.

The sidebar notices now speak the same vocabulary as the dialog and link
straight into it instead of naming a menu. docs/non-orca-worktree-visibility.md
writes down the model, the stablyai#9388 guarantee, and the exception rules.

Supersedes stablyai#11730 and consolidates the overlapping work in stablyai#10560 (the
agent visibility setting, whose field name and rollback shape are kept) and
Agent scratch worktrees (.claude/worktrees, .gsd-workspaces) have been
hidden unconditionally since stablyai#9535. The suppression reaches every surface
that could report it — sidebar, discovery card, inbox, and the Non-Orca
worktrees dialog counts — so a worktree created by `claude -w` vanishes
with no way to see that it exists or to get it back. Setting the repo to
show non-Orca worktrees has no effect, because shouldShowWorktree returns
on the agent-scratch branch before it reaches that check, and the only
field that can override the gate (importedExternalWorktreePaths) is
writable solely through an inbox that also excludes scratch.

Add a per-repo agentWorktreeVisibility opt-in, default hide, so stablyai#9388's
fan-out protection stays the default. Read it from the single scratch
branch in shouldShowWorktree, and give the visibility dialog a second row
that counts scratch worktrees and toggles the field. The row names the
containing directory, or a location count when the worktrees span more
than one, since that path is the diagnostic payload. Once a repo has
opted in the row stays visible even at zero, so the opt-in is always
reversible.

isUserFacingExternalWorktree is deliberately untouched, so the discovery
card and the new-worktree inbox keep excluding scratch; two sibling
accessors serve the dialog instead.

Adds an accounting invariant test: every worktree git reports is either
unconditionally visible (selected checkout, explicit import, or
orca-managed) or counted in exactly one accessor bucket. It catches a
worktree being dropped from every bucket — the failure mode this fixes —
and is what should stop a future path-list change from reintroducing it.

The new row is localized in all five catalogs, so it reads in the same
language as the non-Orca row directly above it.

Verified against the running app with a real `.claude/worktrees/` worktree:
hidden by default with no sidebar row, named in the dialog, revealed on
Show without closing it, and persisted. That run is also what caught the
path overflowing the dialog — DialogContent is a single-column grid, so
the row needed min-w-0 for the path to truncate instead of inflating the
shared column.

Refs stablyai#9388, stablyai#9535
@jeongph
jeongph force-pushed the fix/agent-scratch-worktree-visibility branch from 2fbebce to ffcc55c Compare August 6, 2026 12:04
@jeongph
jeongph requested a review from brennanb2025 as a code owner August 6, 2026 12:04
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@brennanb2025

Copy link
Copy Markdown
Contributor

Thanks for this work — the agent-worktree visibility policy and recovery path landed in #13652, so this PR is now superseded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants