Skip to content

fix(ssh): open a terminal in an emptied worktree when the host holds a tab this client can't place - #23213

Merged
nwparker merged 1 commit into
stablyai:mainfrom
AmoabaKelvin:clicking-an-empty-workspace-should-awaken
Oct 5, 2026
Merged

nwparker merged 1 commit into
stablyai:mainfrom
AmoabaKelvin:clicking-an-empty-workspace-should-awaken

Conversation

@AmoabaKelvin

@AmoabaKelvin AmoabaKelvin commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator
Files Added Deleted Net
Test 6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​274 $\color{#cf222e}{\Huge{\mathbf{−}}}$​31 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​243
Prod 6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​33 $\color{#cf222e}{\Huge{\mathbf{−}}}$​9 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​24

ELI5

You close the last tab of a worktree on an SSH host, click the worktree again, and nothing opens. This happens when the host remembers a tab for a folder your machine doesn't have, for example a worktree you opened from another computer. Now the click opens a terminal.

What Changed

Before: when you connect, Orca downloads the host's saved list of tabs. If one tab belonged to a folder this machine doesn't know, Orca marked the whole host "Workspace conflict" and from then on refused to create a terminal in any worktree on it. Clicking an emptied worktree left a blank screen, permanently.

After: clicking the emptied worktree opens one terminal, the same as on a host with no conflict. A sleeping agent in such a worktree can resume as well.

How it works: when Orca applies the host's tab list and can't place some folders, it now writes down which folders those were, on the conflict status it already sets. Before creating a terminal or resuming an agent, Orca asks "has the host told me this worktree's tabs?". That used to be answered once for the whole host. It is now answered per worktree: a worktree whose folder is not on the list had its tabs applied, so it counts as known.

What still waits, on purpose:

  • A worktree whose own folder is on the list (for example two projects pointing at the same folder). The host named tabs there that Orca couldn't show.
  • A conflict where the host's list was never applied, or where an upload was rejected because another device changed the host.
  • A worktree you emptied on purpose still stays empty after a restart. Only an explicit click brings a terminal back.

Why

The answer "do I know this worktree's tabs?" comes from the host's tab list, and that list is applied per worktree. Storing the answer per host threw that away, so one unrelated folder blocked everything.

This PR previously took a different route: it asked the host which terminal processes were running and treated "none" as permission to create a terminal. That answers a different question. "No running processes" is not "no tabs here", and each review finding (processes Orca couldn't show, a blank tab beside a sleeping agent, tabs whose processes had died) came from that gap. This version removes that override and both guards that were added around it, so those findings no longer have code to apply to. The commit that documented the override is dropped with it; the startup case it pinned has a test here.

The new list can't go stale: it lives on the conflict status, and every later sync event replaces that status.

Not fixed here, different cause: for about ten seconds after app start, while the host's tab list is still downloading, a click still does nothing and a second click works. There the answer "not known yet" is correct; the click just isn't remembered. That is a separate change.

Supersedes #22016.

Linked Issue

Fixes #22015

Visual Proof

N/A: nothing looks different. A terminal now opens where the screen used to stay blank; the new end-to-end test drives the real app through it.

Testing

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

  • tests/e2e/ssh-emptied-worktree-reactivation.spec.ts (Docker SSH host, real app): gives the host one tab on a folder the client lacks, waits for "Workspace conflict", closes the last tab with its close button, clicks the worktree. Fails on main (no terminal after 25s), passes here. Registered in the SSH Docker runner and workflow.

  • On main with the host in sync, the same close-and-click already opens a terminal, so the conflict is the only state this changes.

  • workspace-terminal-host-authority.test.ts: a placed worktree gets a terminal; a worktree on the list, or one spelled differently (/srv/proj//feature/), keeps waiting; a later sync event that replaces the conflict stops the trust; a worktree emptied on purpose stays empty at startup.

  • remote-workspace-snapshot-unplaced-tab-adoption.test.ts: applying a host list with one unplaceable folder answers for the placed worktree and not for the other.

  • resume-sleeping-agent-session-direct-ssh-hydration-gap.test.ts: a sleeping agent resumes in a placed worktree and keeps waiting in a listed one.

  • Breaking each line that makes this work (never answering "known", comparing raw paths, not writing the list) turns a test red.

  • Web typecheck, oxlint, oxfmt, the changed-code quality check, and the dependent test files pass.

  • Platforms: macOS client against a Linux SSH host in Docker.

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Security: no new IPC, wire field, or process spawn. The terminal is created through the existing seeding path on the host the worktree already belongs to.
  • Cross-platform: renderer-only state. The folder comparison uses the existing path normaliser, which folds slashes and Windows case; a mismatch it can't fold keeps waiting.
  • Remote SSH: only direct-SSH git worktrees change. Folder workspaces and paired-runtime workspaces take the same path as before.
  • Uploads: a host in conflict still doesn't accept uploads from this client, so a terminal opened there isn't saved to the host until the conflict clears. That already applies to tabs opened by hand.
  • Backwards compatibility: the new field is in-memory renderer state. Nothing is persisted or sent to the host.
  • Performance: one list lookup inside a check that already runs behind a memoised selector.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@pullfrog pullfrog 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.

ℹ️ No critical issues — one semantic mismatch worth a look.

Reviewed changes

  • Direct-SSH authority can be overridden by the activation census. resolveWorkspaceTerminalHostAuthority gained an optional hostAnsweredActivationCensus; in the SSH non-folder branch a true value returns 'none' instead of deferring to resolveDirectSshAuthority, so a sync still pulling/conflict/not-yet-attempted no longer blocks seeding.
  • The empty gate now supplies that flag. reseedGatedEmptyWorkspace sets hostAnsweredActivationCensus: true (it only runs after a gate outcome of empty), and ensureWorktreeHasInitialTerminal forwards opts to the resolver.
  • Contract and tests. New optional InitialTerminalOptions field; tests cover conflict lifting to none, paired runtime staying unverifiable, reseeding across the three sync phases, and staying empty/blocked when listSessions throws.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Activation reseeding now distinguishes an empty workspace from a workspace with live PTYs that were not surfaced. Both outcomes can trigger reseeding. Reseeding records when the host census found no live PTY, and SSH terminal-host authority uses that flag when resolving authority. Tests cover SSH sync phases, unsurfaced PTYs, census failures, and direct and paired-runtime authority.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to 07df1

In a limited SSH recovery case, activation may open an extra terminal before the existing PTY tab synchronizes. Add the SSH-specific guard while preserving the local fallback.

Architecture Summary

Architecture risk: 🔵 Low · up to 07df1

The change affects 1 system.

Changed systems: src

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 12 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in src/renderer/src/components/use-terminal-watcher-effects.ts: The activation-gate import now also includes activationOutcomeNeedsSeed.
  • observed — Modified behavior in src/renderer/src/components/use-terminal-watcher-effects.ts: The startup-seeding guard now uses activationOutcomeNeedsSeed(outcome) instead of continuing only when outcome is 'empty'.
  • observed — Modified behavior in src/renderer/src/lib/worktree-activation-gated-empty-reseed.ts: Imports activationOutcomeNeedsSeed for the updated reseed decision.
  • observed — Modified behavior in src/renderer/src/lib/worktree-activation-gated-empty-reseed.ts: Replaces the 'empty'-only check with activationOutcomeNeedsSeed(outcome). When seeding is needed, calls reseedGatedEmptyWorkspace with an additional flag that is true only for the 'empty' outcome.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 12 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #22015 requires explicit reopening of an emptied workspace to create a usable blank terminal. The activation gate now requests seeding for both empty and unsurfaced outcomes. `reseedGatedEmp…
Out of Scope Changes check ✅ Passed The changed activation outcomes, host census override, seeding flow, authority resolution, and tests support the explicit-reopen behavior and the SSH, startup, and runtime boundaries in issue #22015. …
Title check ✅ Passed The title clearly identifies the SSH worktree terminal-opening fix and matches the primary change in the pull request.
Description check ✅ Passed The description is complete and aligned with the repository template. It explains the user impact, implementation, rationale, linked issue, visual proof, testing, scope, compatibility considerations, …
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch clicking-an-empty-workspace-should-awaken

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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: a364d334-b695-4c74-9164-da273282fb0e

📥 Commits

Reviewing files that changed from the base of the PR and between da6d483 and f6d946693ee6a92f05eedd583b154d8cc3e52e48.

📒 Files selected for processing (5)
  • src/renderer/src/lib/workspace-terminal-host-authority.test.ts
  • src/renderer/src/lib/workspace-terminal-host-authority.ts
  • src/renderer/src/lib/worktree-activation-emptied-workspace-reseed.test.ts
  • src/renderer/src/lib/worktree-activation-store-contract.ts
  • src/renderer/src/lib/worktree-initial-terminal-seeding.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

@pullfrog pullfrog 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.

Important

The hostAnsweredActivationCensus narrowing is right, but the companion source-pin test was not updated and now fails.

Reviewed changes

  • The gate now separates empty from unsurfaced. runWorktreeAgentActivationGate returns 'unsurfaced' when it listed live workspace PTYs that no surface could adopt; 'empty' now strictly means the host listed no live PTY here. A shared activationOutcomeNeedsSeed treats both as seedable.
  • The census flag is narrowed to empty. gateAndReseedEmptyWorkspace passes outcome === 'empty' through, so hostAnsweredActivationCensus is no longer set when live PTYs exist — an SSH worktree mid-sync with unsurfaced PTYs keeps waiting, while local still seeds.
  • Startup effect and tests follow suit. use-terminal-watcher-effects gates on !activationOutcomeNeedsSeed; gate tests expect 'unsurfaced'; the reseed suite adds stubActivationCensus and covers both the SSH-waits and local-seeds cases.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Keep an unsurfaced SSH census unverifiable. · workspace-terminal-host-authority.ts:108-111

src/renderer/src/lib/workspace-terminal-host-authority.ts:108-111
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep an unsurfaced SSH census unverifiable.

'unsurfaced' means that the host listed a live PTY, but no surface was adopted. The reseed caller passes hostAnsweredActivationCensus: false for this outcome. The SSH branch then falls back to resolveDirectSshAuthority, which can return 'none' for hydrated targets and for offline or error sync phases. The seeding path treats 'none' as permission to create a terminal, so it can create a second terminal beside the live PTY.

Only 'empty' can establish that the host listed no live PTY. Preserve the target-wide fallback only when no activation census result exists.

Suggested fix
-    return opts?.hostAnsweredActivationCensus === true
-      ? 'none'
-      : resolveDirectSshAuthority(state, host.targetId)
+    if (opts?.hostAnsweredActivationCensus === true) {
+      return 'none'
+    }
+    if (opts?.hostAnsweredActivationCensus === false) {
+      return 'unverifiable'
+    }
+    return resolveDirectSshAuthority(state, host.targetId)

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8a729141-2479-4a2f-a01c-7de183b18f51

📥 Commits

Reviewing files that changed from the base of the PR and between f6d946693ee6a92f05eedd583b154d8cc3e52e48 and 004334ba5ea99c26845f2b0b7536537e023f4145.

📒 Files selected for processing (7)
  • src/renderer/src/components/use-terminal-watcher-effects.ts
  • src/renderer/src/lib/worktree-activation-emptied-workspace-reseed.test.ts
  • src/renderer/src/lib/worktree-activation-gated-empty-reseed.ts
  • src/renderer/src/lib/worktree-activation-store-contract.ts
  • src/renderer/src/lib/worktree-agent-activation-gate.test.ts
  • src/renderer/src/lib/worktree-agent-activation-gate.ts
  • src/renderer/src/lib/worktree-initial-terminal-seeding.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/renderer/src/lib/worktree-activation-store-contract.ts
  • src/renderer/src/lib/worktree-initial-terminal-seeding.ts
  • src/renderer/src/lib/worktree-activation-emptied-workspace-reseed.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Startup-routing source-pin follows the new predicate. app-startup-routing.test.ts:434 now asserts the gate block contains !activationOutcomeNeedsSeed(outcome), fixing the failing orders packaged restoration before adoption, projection, and default creation test raised in the prior review.
  • Structured-launch hook-test mock keeps the real module. initial-terminal-structured-launch.test.tsx spreads importOriginal so the newly imported activationOutcomeNeedsSeed resolves instead of being undefined.
  • Gate test comment corrected to say unsurfaced rather than empty.

The production narrowing reviewed previously is unchanged; all three affected suites pass locally (71 tests).

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@AmoabaKelvin
AmoabaKelvin force-pushed the clicking-an-empty-workspace-should-awaken branch from db958fc to e9b1239 Compare September 27, 2026 06:00

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • The authority-override flag is renamed to match what it encodes. hostAnsweredActivationCensus becomes hostListedNoLivePty across the InitialTerminalOptions field, the direct-SSH parameter in resolveWorkspaceTerminalHostAuthority, and the authority tests.
  • Seeding now waits when a sleeping agent could not resume. reseedGatedEmptyWorkspace withholds hostListedNoLivePty from the resolver when workspaceHasSleepingAgentSessions finds a record, so an empty SSH census with a sleeping agent falls back to the direct-SSH wait instead of seeding a blank shell beside the tab the agent resumes into once the host answers.
  • Regression coverage and verification. A new case drives the real activation entry point with a sleeping codex record and asserts the workspace stays at 0 tabs (it fails without the guard); the rename is covered by the authority tests. Typecheck (pnpm tc:web), oxlint, and all five affected suites (114 tests) pass.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Handles terminal seeding when remote snapshots cannot place all tabs.

The PR should not merge until conflict authority accounts for differently spelled worktree IDs that refer to a host path whose tab was placed elsewhere.

Findings

  1. P1 Alias can create duplicate terminal ▶
  2. P1 Old-host record blocks terminal ▶

Summary

The PR replaces the SSH PTY-census override with per-worktree authority derived from host snapshot placement, allowing a terminal to reopen in a placed worktree while another host tab remains unplaceable. It also adds unit and Docker SSH end-to-end coverage. The conflict check still needs to account for a host path placed under a differently spelled worktree ID.

Reviews (3) · Last reviewed commit: "fix(ssh): let a placed worktree seed whi..."

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Make activationOutcomeNeedsSeed aware of SSH worktree… · worktree-agent-activation-gate.ts:49-53

src/renderer/src/lib/worktree-agent-activation-gate.ts:49-53
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make activationOutcomeNeedsSeed aware of SSH worktree context.

When an SSH git worktree returns unsurfaced, the census already lists a live PTY. Both activation callers still treat that outcome as seed-eligible. If SSH authority resolves to none, either caller can create a fallback terminal before synchronization publishes the existing PTY tab. The workspace can then contain an extra terminal.

Keep the local unsurfaced fallback. Pass the resolved SSH-worktree context from both callers into this predicate. Do not use authority none as the context because local worktrees also resolve to none.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: a6740c53-4c07-4c82-a75c-ce84940e3df3

📥 Commits

Reviewing files that changed from the base of the PR and between e9b1239076ac32c7e15920c1f5d89e917925b1e2 and 07df15a4e76c4717dae55e6744a3a3b59899581f.

📒 Files selected for processing (3)
  • src/renderer/src/hooks/remote-workspace-snapshot-apply.ts
  • src/renderer/src/lib/workspace-terminal-host-authority.test.ts
  • src/renderer/src/lib/workspace-terminal-host-authority.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/lib/workspace-terminal-host-authority.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

@AmoabaKelvin
AmoabaKelvin force-pushed the clicking-an-empty-workspace-should-awaken branch from 07df15a to f624eef Compare October 1, 2026 13:10
@AmoabaKelvin
AmoabaKelvin force-pushed the clicking-an-empty-workspace-should-awaken branch from f624eef to 7ffcd59 Compare October 1, 2026 14:38
@AmoabaKelvin AmoabaKelvin changed the title fix(ssh): seed a terminal when reopening an emptied SSH worktree fix(ssh): open a terminal in an emptied worktree when the host holds a tab this client can't place Oct 1, 2026
@AmoabaKelvin

Copy link
Copy Markdown
Collaborator Author

@nwparker I reworked this after reproducing it against a Docker SSH host, and force-pushed (7ffcd590bb).

What the repro showed: with the host connected and in sync, closing the last tab and clicking the worktree already opens a terminal on main. It only fails once the host's snapshot holds a tab on a path this client can't place. The target then sits in conflict with hydration cleared, and every worktree on it reads unverifiable for good.

So the fix now records the unplaced paths on that conflict status, and the authority check answers none for worktrees the snapshot did place. The PTY-census override is removed, along with the unsurfaced outcome and the sleeping-agent guard that were added around it. All seven bot findings were against that override.

That means your docs commit is dropped, since it documented the override. The startup case it pinned (a worktree emptied on purpose stays empty on restore) has its own test in workspace-terminal-host-authority.test.ts. The previous version is still available if you'd rather keep that approach.

Also answering CodeRabbit's outside-diff note on activationOutcomeNeedsSeed: that predicate no longer exists.

One thing left out on purpose: a click while the host's snapshot is still downloading is still dropped, and a second click works. That has a different cause, so I'll send it separately.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Replaced the activation-census override with per-worktree conflict trust. The previous hostAnsweredActivationCensus / hostListedNoLivePty override, the activation gate's unsurfaced outcome, and the reseedGatedEmptyWorkspace sleeping-agent guard are all gone; the fix now lives entirely in the authority resolver and the sync status it reads.
  • The conflict status records which host paths it could not place. RemoteWorkspaceSyncStatus gains unplacedTabWorktreePaths, written only by an applied pull that ends in conflict (some rows placed, some dropped). Because setRemoteWorkspaceSyncStatus replaces the whole per-target object, the list is cleared by every later sync write and cannot go stale.
  • Authority is answered per worktree. resolveDirectSshAuthority now takes the worktree id: on a conflict that carries the unplaced list, a worktree whose own path is not on it resolves none (seed / resume), while one whose path is on it stays unverifiable. Conflicts with no list (never-applied snapshot, rejected upload, pull in flight) keep waiting for every worktree.
  • Tests and e2e registration. New authority, snapshot-apply, and sleeping-agent-resume cases cover the placed/unplaced split and the deliberate empty-on-startup case; tests/e2e/ssh-emptied-worktree-reactivation.spec.ts drives the real app against a Docker SSH host, with the relay snapshot helpers extracted to docker-ssh-relay-workspace-snapshot.ts.

I traced the placed ⇒ adopted invariant across importRemoteWorkspaceSession, the merge, and every writer of the conflict status: onUnplacedTerminalTabs fires exactly when a host path fails to resolve, and markRemoteWorkspaceHydrated cannot run over a conflict carrying the list, so the hydration branch in the resolver cannot mask the fix. The path-normalization asymmetry (raw equality for placement, normalized equality for authority) resolves in the conservative direction only.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

One host tab on a folder this client lacks marked every worktree on the host unverifiable, so reopening an emptied one never got a terminal.

Fixes stablyai#22015

Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
@nwparker
nwparker force-pushed the clicking-an-empty-workspace-should-awaken branch from 7ffcd59 to 6923656 Compare October 5, 2026 05:52

@nwparker nwparker 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.

Partial SSH hydration conflict authority stays scoped to unplaced host paths, preventing destructive unknown-tab replacement while allowing other empty workspaces to seed. Preserved latest CI E2E routing. 27 authority tests and 8 routing tests, web typecheck and quality gate pass.

Required PR Checks verify passed on this exact commit: 6923656.

@nwparker

nwparker commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Pre-merge bot audit on 6923656: the remaining old-host/census thread was superseded by removal of that override; replied with exact-head evidence and resolved it. Other inline findings were already resolved. The stale conflict warning no longer applies: GitHub reports this head mergeable, with approval and all current checks passing. The docstring-coverage suggestion is declined under AGENTS.md's concise, non-obvious-comment policy. Unknown/unplaced remote tabs retain host authority and protection; verified provider/routing tests and the retried Docker SSH shard all passed.

@nwparker
nwparker merged commit 1168e0f into stablyai:main Oct 5, 2026
71 of 72 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Clicking an emptied workspace does not awaken it with a blank terminal

2 participants