Skip to content

ssh-tmux: mirror into current window by default (--new-window keeps dedicated window) - #7264

Merged
austinywang merged 34 commits into
manaflow-ai:mainfrom
0xJord4n:feat/sub-workspace
Jul 14, 2026
Merged

austinywang merged 34 commits into
manaflow-ai:mainfrom
0xJord4n:feat/sub-workspace

Conversation

@0xJord4n

@0xJord4n 0xJord4n commented Jul 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

cmux ssh-tmux <host> now mirrors a remote host's tmux sessions as plain workspaces in the current window by default — no group, no dedicated new window. A --new-window flag preserves the old dedicated-window behavior.

Previously the command always opened a dedicated new window that quarantined the remote sessions away from your local workspaces. Now remote sessions sit alongside local ones in the same sidebar.

What changed

  • RemoteTmuxController.mirrorHostInCurrentWindow — hardened from the old mirrorHost: auth-required handling, no-window fallback (creates a plain window), idempotent reuse, beginAttach guard, cancellation, window-raise on activate, and a fail-loud guard that throws (without discarding the window) when zero sessions mirror.
  • New socket command remote.tmux.attach_here (registered at dispatch + worker allowlist + execution policy); retired remote.tmux.mirror.
  • CLI: cmux ssh-tmux routes to the current-window mirror by default; --new-window routes to the old remote.tmux.window path verbatim.
  • Docs + localization: remote-tmux docs page updated; cli.help.ssh-tmux and docs strings localized in English + Japanese.

Behavior decisions

  • Session end (network loss) → reconnect, same as SSH workspaces (existing path, unchanged).
  • Session end (tmux session killed) → the workspace closes (nothing to reconnect to); the window and other workspaces survive.
  • New tmux session creation from cmux → not offered in v1; new sessions appear via the live feed.

Testing

  • Unit: 61 remote-tmux tests pass (cmux-unit scheme). Includes a new guard asserting remote.tmux.attach_here stays dispatched and validates params before touching the network, and an updated capabilities assertion for the method rename.
  • Live E2E against a real tmux host: default mirrors sessions into the current window (no new window, local + remote coexist); --new-window opens a dedicated window; killing a remote session closes only its workspace while the window survives.

Notes

Behind the existing remoteTmux beta flag. No new keyboard shortcut. No schema/config changes.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

cmux ssh-tmux <host> now mirrors remote tmux sessions into your current window by default; use --new-window for a dedicated window. Added remote.tmux.window; socket calls stay focus‑neutral unless activate: true, and closing a mirrored workspace detaches (never kills).

  • New Features

    • CLI/docs: default to current-window; --new-window opens a dedicated window. Help updated and localized.
    • Socket/controller: added remote.tmux.window (dedicated window) alongside remote.tmux.mirror (current window). Focus is opt‑in; creates the window after SSH preflight, consolidates any existing mirrors, removes the bootstrap tab, and throws if no sessions mirror.
  • Bug Fixes

    • Closing a mirrored tab/window or via v2 socket now detaches without killing the remote session; killing only happens via an explicit disconnect.
    • Failure handling: clean up partial mirrors, discard a just-created dedicated window, never fabricate a window_id, and return a localized “could not create a new window” error; both mirror entry points validate params and fail early with structured errors.
    • Tests/CI: added regressions for detach‑on‑close, dedicated‑window consolidation, focus neutrality, and param validation; focused gate runs before flaky suites.

Written for commit f6b650e. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • cmux ssh-tmux now mirrors remote tmux sessions into the current window by default.
    • Added --new-window to mirror into a dedicated new window (with updated focus/activation behavior).
    • Added socket command remote.tmux.attach_here for current-window mirroring (and updated docs to reflect the default entry point).
  • Bug Fixes

    • Improved attach preflight with structured auth-required results and safer, idempotent mirroring.
    • Mirroring now isolates per-session failures and validates that workspaces were actually created.
    • Closing a mirrored remote tmux workspace now detaches instead of killing the remote session.
  • Documentation / Tests

    • Updated help text, Remote tmux beta docs, localized strings, and socket-command examples.
    • Added/updated capabilities tests and new regression coverage for attach/close semantics.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

@0xJord4n is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

This PR makes cmux ssh-tmux mirror remote tmux sessions into the current window by default through remote.tmux.attach_here, while keeping dedicated-window behavior behind --new-window. It updates controller flow, socket dispatch, CLI routing, tests, docs, localized copy, and Xcode project wiring.

Changes

attach_here mirroring feature

Layer / File(s) Summary
Controller mirroring and preflight
Sources/RemoteTmuxController.swift, Sources/RemoteTmuxController+CurrentWindowMirror.swift, Sources/RemoteTmuxController+MirrorAttachPreflight.swift, Sources/RemoteTmuxMirrorAttachPreflight.swift, Sources/RemoteTmuxSSHTransport.swift, Sources/AppDelegate.swift
Current-window mirroring is added with attach preflight, in-flight guarding, auth-required outcomes, reuse handling, session mirroring into the current window, and updated socket-path comments/doc text.
Socket handler and dispatch wiring
Sources/TerminalController+RemoteTmux.swift, Sources/TerminalController.swift, Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift
Adds remote.tmux.attach_here, keeps remote.tmux.mirror as a deprecated alias, and registers the new method in socket dispatch, capabilities, and worker routing.
CLI routing and help text
CLI/cmux.swift, Resources/Localizable.xcstrings
Adds --new-window, switches CLI routing between current-window and dedicated-window methods, and updates usage/help/examples plus localized help copy.
Mirror close and detach behavior
Sources/TabManager.swift, cmuxTests/RemoteTmuxMirrorCloseDetachTests.swift
Mirror close flows now detach locally instead of killing the remote session, and the no-op kill-on-close seam is covered by a regression test.
Docs, specs, and project wiring
cmuxTests/RemoteTmuxCapabilitiesTests.swift, docs/superpowers/plans/*, docs/superpowers/specs/*, web/app/[locale]/docs/remote-tmux/page.tsx, web/messages/en.json, web/messages/ja.json, cmux.xcodeproj/project.pbxproj
Adds planning/design docs, updates remote-tmux docs and EN/JA message copy, adds capability tests, and registers the new Swift sources in the Xcode project.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#7020: Both PRs touch the remote-tmux attach/auth path and the conditions that return .authRequired(sshArgv:) during mirroring preflight.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (3 errors)

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error mirrorHostInNewWindow moves each existing workspace one-by-one, and each move does a full tab/window scan via tabManagerFor(tabId:), making the batch O(k*n). Precompute a workspaceId→tabManager/windowId map once (or move from sessionMirrors values directly) before the loop so the rebind stays linear.
Cmux Full Internationalization ❌ Error cli.help.ssh-tmux and docs.remoteTmux.* were added/changed only in en/ja, but the catalog/messages support 20 locales. Add translated entries for every supported locale in Resources/Localizable.xcstrings and web/messages/*.json for all changed user-facing strings.
Cmux No Test Or Debug Seam In Production Source ❌ Error FAIL: Sources/RemoteTmuxController.swift adds #if DEBUG test hooks bindDedicatedWindowForTesting/unbindDedicatedWindowForTesting in production source. Move those helpers into the test target and reach needed state via @testable import after widening internals, or isolate any truly debug-only API in a dedicated debug file.
✅ Passed checks (22 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Cmux Swift Actor Isolation ✅ Passed PASS: The new remote-tmux controller code stays @MainActor, socket entrypoints hop via MainActor.run, and new Sendable payloads only contain Sendable models.
Cmux Swift Blocking Runtime ✅ Passed PASS: The diff adds async attach guards and @MainActor state, but no new semaphores, waits, sleeps, main.sync, polling, or manual locks in production code.
Cmux Browser Automation Off-Main ✅ Passed Only TabManager.swift changed, and the diff is remote-tmux detach-on-close logic/comments; no browser.* commands, WebKit/AppKit automation routing, or policy tests were touched.
Cmux Expensive Synchronous Load ✅ Passed PR only changes remote-tmux mirroring/close semantics; the synchronous RestorableAgentSessionIndex.load() fallback is pre-existing in TabManager.closeWorkspace, not newly moved onto an interact...
Cmux Cache Substitution Correctness ✅ Passed No persistence/history/undo/snapshot path in the diff swaps a fresh authoritative read for a cache; the changed tmux logic is transient attach/close UI flow with fresh window resolution fallbacks.
Cmux No Hacky Sleeps ✅ Passed Changed TS/JSON docs files add remote-tmux copy only; no fixed sleeps, timers, polling, or delay-based coordination appear in the diff.
Cmux Swift Concurrency ✅ Passed The PR adds structured async/await flows and no new legacy async patterns; diff searches found no added DispatchQueue/Combine/completion-handler/fire-and-forget Task usage.
Cmux Swift @Concurrent ✅ Passed PASS: The new async work is actor-isolated (@MainActor controller / RemoteTmuxSSHTransport actor), and v2VmCall already runs RPC closures in a detached Task, so no missing @concurrent.
Cmux Swift File And Package Boundaries ✅ Passed No new oversized production file; touched >800-line files got small focused edits, and the new remote-tmux files are small app-glue around AppDelegate/TabManager, not package-sized core.
Cmux Swiftpm Lockfiles ✅ Passed pbxproj changes are source membership only; no Package.resolved, Package.swift, or .gitignore lockfile-related diff appears.
Cmux Swift Logging ✅ Passed No new production logging was added or changed: touched helper files contain no print/debugPrint/dump/NSLog, and the only runtime diff hunk was a TabManager comment change.
Cmux User-Facing Error Privacy ✅ Passed Only the current diff is a comment change; reviewed user-facing strings in touched code are generic and don’t expose forbidden provider or secret details.
Cmux Swiftui State Layout ✅ Passed PASS: HEAD only changes a TabManager comment; no new ObservableObject/@published or SwiftUI layout/state patterns were introduced.
Cmux Architecture Rethink ✅ Passed Centralized controller/registry ownership and shared helpers; no new timing hacks, polling, or split lifecycle owner beyond required bridges.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only changes main-workspace tmux mirroring and CLI/docs; no new NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes, so the auxiliary-window rule isn’t triggered.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/docs/tests/localization or tracked submodule/checksum updates; no temp/build/log/screenshot artifact paths appear.
Cmux No Ambient Global State ✅ Passed The new remote-tmux helpers are instance methods inside type extensions; no new file-scope funcs, mutable globals, or singleton state were added.
Title check ✅ Passed The title clearly summarizes the main behavior change: default current-window mirroring with --new-window preserving the old path.
Description check ✅ Passed The PR description covers summary, changes, behavior decisions, and testing, but omits the Demo Video, Review Trigger, and checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps

greptile-apps Bot commented Jul 3, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

cmux ssh-tmux now mirrors remote tmux sessions into the current window by default (alongside local workspaces), with --new-window preserving the old dedicated-window behavior. Closing a mirrored workspace now detaches from the remote session rather than killing it.

  • CLI: --new-window flag routes to the new remote.tmux.window socket command; the existing remote.tmux.mirror handles the current-window default. Both are registered in the dispatch switch, socket-worker allowlist, and ControlCommandExecutionPolicy.
  • Attach path: dedicatedNewWindow window target creates its window only after SSH preflight succeeds, moves existing mirrors in, discards on failure, and removes the bootstrap tab — all cleanup branches covered.
  • Close semantics: markRemoteTmuxKillOnWindowCloseIfNeeded is converted to a no-op (was a kill-marker setter); all close paths — tab, socket, window, and app-quit — now detach instead of kill. New RemoteTmuxMirrorCloseDetachTests provides regression coverage.
  • Localization: xcstrings additions cover every app locale; web/messages/ additions cover only English and Japanese, leaving 18 other locale files without the new attachNewWindow doc paragraph.

Confidence Score: 4/5

Safe to merge for all Swift and CLI changes; one web localization gap in the docs page.

The Swift, CLI, and socket-routing changes are well-structured and fully tested — attach paths handle all failure modes, close semantics are corrected across every path, and ControlCommandExecutionPolicy was already updated for remote.tmux.window. The only defect is in web/messages/: the new attachNewWindow paragraph key was added to English and Japanese but is absent from the other 18 locale files, so users of the docs site in those locales will see English text rather than a localized translation.

The 18 web locale files (ar.json, bs.json, da.json, de.json, es.json, fr.json, it.json, km.json, ko.json, no.json, pl.json, pt-BR.json, ru.json, th.json, tr.json, uk.json, zh-CN.json, zh-TW.json) each need an attachNewWindow entry.

Important Files Changed

Filename Overview
web/messages/en.json Adds attachNewWindow doc paragraph; key is present here (and in ja.json) but missing from all other 18 web locale files.
Sources/TerminalController+RemoteTmux.swift Adds v2RemoteTmuxWindow handler (nonisolated, routes to v2VmCall + await MainActor.run, no blocking sync), extracts remoteTmuxActivate helper; patterns are consistent with existing mirror handlers.
Sources/RemoteTmuxController+Attach.swift Extends prepareMirrorAttach with dedicatedNewWindow branch: creates window post-preflight, moves existing mirrors, discards on failure, removes bootstrap tab — all cleanup paths handled.
Sources/TabManager.swift Makes markRemoteTmuxKillOnWindowCloseIfNeeded a visible no-op (previously private kill-marker setter); all close paths now detach instead of kill. Accessed from tests via @testable import.
CLI/cmux.swift Adds --new-window flag routing to remote.tmux.window; current-window default uses remote.tmux.mirror. Help text and top-level usage are updated.
cmuxTests/RemoteTmuxMirrorCloseDetachTests.swift New regression suite covering detach-not-kill for tab/socket/window close and dedicated-window consolidation; test-only Task.sleep polling and RunLoop.main.run are test scaffolding, not production paths.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI as cmux CLI
    participant Socket as Socket Worker
    participant Ctrl as RemoteTmuxController
    participant App as AppDelegate / TabManager

    alt Default (current window)
        CLI->>Socket: "remote.tmux.mirror {host, activate}"
        Socket->>App: MainActor.run → remoteTmuxAttachWindowTarget
        App-->>Socket: windowTarget (.contextualWindow / .explicitWindow)
        Socket->>Ctrl: attachHost(windowTarget, activate)
        Ctrl->>Ctrl: SSH preflight / discovery
        Ctrl->>App: mirrorDiscoveredSessions → existing window
        App-->>Ctrl: workspaceIds
        Ctrl-->>Socket: .mirrored(windowId, workspaceIds)
        Socket-->>CLI: "{mirrored:true, window_id, workspace_ids}"
    else --new-window
        CLI->>Socket: "remote.tmux.window {host, activate}"
        Socket->>Ctrl: attachHost(.dedicatedNewWindow, activate)
        Ctrl->>Ctrl: SSH preflight / discovery
        Ctrl->>App: createMainWindow()
        App-->>Ctrl: newWindowId + TabManager
        Ctrl->>App: moveExistingMirrors → new window
        Ctrl->>App: mirrorDiscoveredSessions → new window
        Ctrl->>App: removeBootstrapTab
        App-->>Ctrl: workspaceIds
        Ctrl-->>Socket: .mirrored(newWindowId, workspaceIds)
        Socket-->>CLI: "{mirrored:true, window_id, workspace_ids}"
    end

    Note over App: Tab/window/socket close → detachMirrorWorkspaceKeptOpenLocally (never kill-session)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CLI as cmux CLI
    participant Socket as Socket Worker
    participant Ctrl as RemoteTmuxController
    participant App as AppDelegate / TabManager

    alt Default (current window)
        CLI->>Socket: "remote.tmux.mirror {host, activate}"
        Socket->>App: MainActor.run → remoteTmuxAttachWindowTarget
        App-->>Socket: windowTarget (.contextualWindow / .explicitWindow)
        Socket->>Ctrl: attachHost(windowTarget, activate)
        Ctrl->>Ctrl: SSH preflight / discovery
        Ctrl->>App: mirrorDiscoveredSessions → existing window
        App-->>Ctrl: workspaceIds
        Ctrl-->>Socket: .mirrored(windowId, workspaceIds)
        Socket-->>CLI: "{mirrored:true, window_id, workspace_ids}"
    else --new-window
        CLI->>Socket: "remote.tmux.window {host, activate}"
        Socket->>Ctrl: attachHost(.dedicatedNewWindow, activate)
        Ctrl->>Ctrl: SSH preflight / discovery
        Ctrl->>App: createMainWindow()
        App-->>Ctrl: newWindowId + TabManager
        Ctrl->>App: moveExistingMirrors → new window
        Ctrl->>App: mirrorDiscoveredSessions → new window
        Ctrl->>App: removeBootstrapTab
        App-->>Ctrl: workspaceIds
        Ctrl-->>Socket: .mirrored(newWindowId, workspaceIds)
        Socket-->>CLI: "{mirrored:true, window_id, workspace_ids}"
    end

    Note over App: Tab/window/socket close → detachMirrorWorkspaceKeptOpenLocally (never kill-session)
Loading

Reviews (18): Last reviewed commit: "fix(remote-tmux): cover tab detach and r..." | Re-trigger Greptile

Comment thread docs/superpowers/specs/2026-07-03-ssh-tmux-current-window-design.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/specs/2026-07-03-ssh-tmux-current-window-design.md`:
- Around line 76-80: Use the underscore naming consistently for the new tmux
socket method: change the proposed `remote.tmux.attach-here` reference in the
`ssh-tmux` design to `remote.tmux.attach_here` so it matches the naming used
elsewhere and avoids a mismatched copy/paste target. Update any mention in the
spec around the `ssh-tmux` parser/default behavior and the new socket method
naming to use `attach_here` alongside the existing `remote.tmux.mirror`
reference.

In `@Sources/RemoteTmuxController.swift`:
- Line 456: The fallback in RemoteTmuxController’s reuse and success paths is
fabricating a fake window_id with UUID() when appDelegate.windowId(for:) returns
nil. Replace that fallback with explicit error handling in the affected flow so
the socket/API response never returns an ID that does not correspond to a real
window. Use the existing manager lookup logic around tabManagerFor(tabId:),
tabManagerFor(windowId:), and appDelegate.tabManager to surface the inconsistent
registration state instead of masking it, and apply the same change to both
occurrences.

In `@web/messages/en.json`:
- Around line 728-731: The `attachCli` help text reads like an exhaustive list
of supported flags but omits `--no-focus`, which is still valid for the
current-window attach flow. Update the `attachCli` message in the messages JSON
to either include `--no-focus` alongside `--port`, `--identity`, and
`--new-window`, or rephrase the sentence so it clearly presents those flags as
examples rather than an exhaustive list.

In `@web/messages/ja.json`:
- Around line 684-687: Update the attach option descriptions in the japanese
messages entry for attachCli/attachNewWindow so they either explicitly mention
--no-focus alongside --port, --identity, and --new-window, or clearly indicate
the list is only an example of supported flags. Keep the wording aligned with
the existing attach flow wording in attachIntro and attachSockets so users know
--no-focus still applies when attaching into the current window.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fe9c5d1b-5ed6-4b44-a8cb-fea6a6eed3c4

📥 Commits

Reviewing files that changed from the base of the PR and between b7edfde and da694fd.

📒 Files selected for processing (14)
  • CLI/cmux.swift
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/RemoteTmuxController.swift
  • Sources/RemoteTmuxSSHTransport.swift
  • Sources/TerminalController+RemoteTmux.swift
  • Sources/TerminalController.swift
  • cmuxTests/RemoteTmuxCapabilitiesTests.swift
  • docs/superpowers/plans/2026-07-03-ssh-tmux-current-window.md
  • docs/superpowers/specs/2026-07-03-ssh-tmux-current-window-design.md
  • web/app/[locale]/docs/remote-tmux/page.tsx
  • web/messages/en.json
  • web/messages/ja.json

Comment thread docs/superpowers/specs/2026-07-03-ssh-tmux-current-window-design.md Outdated
Comment thread Sources/RemoteTmuxController.swift Outdated
Comment thread web/messages/en.json Outdated
Comment thread web/messages/ja.json Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

5 issues found across 14 files

You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/RemoteTmuxController.swift Outdated
Comment thread Sources/RemoteTmuxController.swift
Comment thread web/messages/ja.json Outdated
Comment thread web/messages/en.json Outdated
Comment thread docs/superpowers/specs/2026-07-03-ssh-tmux-current-window-design.md Outdated
…doc fixes

- RemoteTmuxController: replace '?? UUID()' window_id fallback with a thrown
  error at both the reuse and success paths (CodeRabbit) — never return a
  fabricated id the caller would treat as a live window; resolve windowId once.
- web/messages en+ja: attachCli lists --no-focus alongside the other flags.
- spec: attach-here → attach_here, Status Draft → Implemented (Greptile).
@0xJord4n

0xJord4n commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the bot review findings in df562509be:

  • ?? UUID() fabricated window_id (CodeRabbit) — fixed. Both the reuse and success paths now resolve windowId(for:) once and throw RemoteTmuxError.unreachable(...) when it's nil, instead of returning a fabricated UUID the caller would treat as a live window. This matches the method's existing fail-loud posture and also removed a redundant double lookup.
  • attachCli omits --no-focus (CodeRabbit, en + ja) — fixed. Both message catalogs now list --no-focus alongside --port, --identity, and --new-window. (The cli.help.ssh-tmux help text already had --no-focus in its Flags: list, so no change needed there.)
  • Spec drift: attach-here hyphen + Status: Draft (Greptile) — fixed. The design spec now uses remote.tmux.attach_here (underscore, matching production) and Status: Implemented (PR #7264).

Build green, 61 unit tests pass (incl. the attach_here socket guard), and the live E2E behavior described in the PR body is unchanged.

@vercel

vercel Bot commented Jul 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 13, 2026 10:55pm

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/RemoteTmuxController`+CurrentWindowMirror.swift:
- Around line 52-65: The current `firstMirroredWorkspaceId` selection can
accidentally use a workspace mirrored in a different `TabManager` because
`mirrorSession` returns a Bool that is ignored here. Update `mirrorSessions`/the
surrounding loop to only assign `firstMirroredWorkspaceId` when
`mirrorSession(host:sessionName:into:)` actually mirrors into the current
`manager`, and never read `sessionMirrors[key]?.mirroredWorkspaceId` unless that
mirror belongs to this manager. Keep `hasMirrorForHost` behavior unchanged, but
ensure the workspace chosen for `manager.selectWorkspace` comes from a session
mirrored into the same manager.
- Around line 18-27: The closure inside prepareMirrorAttach in
RemoteTmuxController+CurrentWindowMirror is capturing sessions before that local
is declared, causing a compile error. Update the call to
completeCurrentWindowMirrorOutcome to use the closure parameter that provides
the discovered sessions (for example, the argument passed into the
prepareMirrorAttach completion) instead of referencing the later sessions
binding, and keep the later let sessions assignment only for the value returned
after preflight.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b87101bc-dffe-442c-9d66-8fd5a1917aba

📥 Commits

Reviewing files that changed from the base of the PR and between df56250 and 16c082f.

📒 Files selected for processing (10)
  • CLI/cmux.swift
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift
  • Sources/RemoteTmuxController+CurrentWindowMirror.swift
  • Sources/RemoteTmuxController+MirrorAttachPreflight.swift
  • Sources/RemoteTmuxController.swift
  • Sources/RemoteTmuxMirrorAttachPreflight.swift
  • Sources/TerminalController+RemoteTmux.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/RemoteTmuxCapabilitiesTests.swift

Comment thread Sources/RemoteTmuxController+CurrentWindowMirror.swift Outdated
Comment thread Sources/RemoteTmuxController+CurrentWindowMirror.swift Outdated
Comment thread Sources/RemoteTmuxController+CurrentWindowMirror.swift Outdated
@robertnisipeanu

robertnisipeanu commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

When I introduced the ssh-tmux feature, the idea to keep them in the same window also sparkled in my head.

In this case, you would loose a very important feature of working remotely: being able to create new remote workspaces (sessions). It's also not clear for me if you can cleanly disconnect from the remote session (without closing the workspaces, since that would trigger the remote tmux sessions to also be killed)?
Yes, I know the old default would still be usable via a flag, but I think the solution should not degrade the "default" experience.

I actually liked the idea from this issue, under Sidebar UX example, but I did not want to bring an UX change together with the new ssh-tmux as it would have decreased the likelihood of it making it into master (so I kept it out of MVP1): #2673
With the example from the issue, there could be even dedicated + button for creating a new workspace and X button for disconnecting from that remote session.

Ofcourse, this is not my call, by the maintainers call; it's just my opinion as someone that does uses this feature daily.

But just to be clear: I love the idea, and I definitely would find it useful to have both local and remote sessions in the same window; but it would need to retain the 2 basic features: being able to detach from the remote; and being able to create more workspaces in the remote easily.

0xJord4n added 2 commits July 5, 2026 02:28
…for kill

Reproduces PR manaflow-ai#7264 review finding: closing a mirrored remote-tmux workspace
marks its window to kill-session on the remote, so the ssh-tmux author's live
session dies when they close the tab. Asserts markRemoteTmuxKillOnWindowCloseIfNeeded
never marks a mirror for kill; RED against current kill-on-close behavior.

Widens the mark seam from private to internal so the test can drive it directly
(the marker is set-then-consumed synchronously inside the real close gesture).
Closing a mirrored remote-tmux workspace now DETACHES from the remote tmux
session (leaving it alive on the server for resume) instead of running
kill-session. Killing a live session is only ever an explicit disconnect
action, never a side effect of closing a tab/window/quit. Addresses the
ssh-tmux author's PR manaflow-ai#7264 review: with mirrors as plain workspaces in the
current window, the natural close gesture was silently killing their session.

- closeWorkspace routes mirrors to detachMirrorWorkspaceKeptOpenLocally.
- markRemoteTmuxKillOnWindowCloseIfNeeded is a retained no-op, so the last-tab,
  window-close, and app-quit paths all fall through to the existing detach
  handlers. handleWorkspaceClosed + the kill-marker machinery stay dormant for
  a future explicit disconnect-host action.

Turns the RED test from the prior commit GREEN. Verified live against a real
tmux host (my-vps): mirrored 9 sessions into the current window, closed all
mirror workspaces, every remote session survived (host session set unchanged).
@0xJord4n

0xJord4n commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@robertnisipeanu thanks for this — you're right on both counts, and the detach point is the one that actually mattered.

Detach, not kill (fixed in 54417d76): you nailed a real data-loss trap. With mirrors living as plain workspaces in the current window, closing a workspace ran kill-session on the remote — so the natural "get this off my screen" gesture silently killed your live tmux session. That's now fixed: closing a mirror workspace (or its window, or quitting) detaches and leaves the session alive on the server for resume. Killing is no longer ever a close side-effect.

  • closeWorkspace routes mirrors to detachMirrorWorkspaceKeptOpenLocally instead of handleWorkspaceClosed.
  • markRemoteTmuxKillOnWindowCloseIfNeeded is now a no-op, so the last-tab / window-close / app-quit paths all fall through to the existing detach handlers.
  • The kill machinery (handleWorkspaceClosed + the window kill-marker) is retained but dormant — it's exactly what a future explicit "disconnect / kill session" action should call.

Verified live against a real tmux host: mirrored 9 sessions into the current window, closed every mirror workspace, and all 9 sessions survived (host session set unchanged). Added a RED/GREEN regression test (RemoteTmuxMirrorCloseDetachTests) so the kill-on-close behavior can't come back.

Creating new remote sessions: agreed this is a genuine gap, not just a nice-to-have — and the #2673 "Sidebar UX example" direction (grouped REMOTE: host section with a dedicated + for new-session and X for clean disconnect) is the right home for both "create session" and an explicit "kill this session" affordance. I kept it out of this PR deliberately to land the detach fix cleanly, same reasoning you gave for keeping it out of MVP1 — but the dormant kill path above is left in place precisely so that X has something to call. Happy to follow up with that as a separate PR.

So this PR now retains detach; new-session creation is the tracked follow-up. Ultimately the maintainers' call on sequencing, but I think this keeps the default experience whole in the way you're describing.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/TabManager.swift`:
- Around line 2222-2231: Update the stale caller comment in
closeWorkspacesWithConfirmation so it matches the current detach-on-close
behavior: it should no longer say closing every tab will kill the remote-tmux
session(s) on commit. Refer to the nearby
markRemoteTmuxKillOnWindowCloseIfNeeded no-op and the other updated comments in
this flow, and rewrite the note to describe that closing workspaces detaches
from the remote session rather than killing it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8f458788-fb63-432f-add0-26acddcaa314

📥 Commits

Reviewing files that changed from the base of the PR and between 16c082f and 54417d7.

📒 Files selected for processing (8)
  • CLI/cmux.swift
  • Sources/AppDelegate.swift
  • Sources/RemoteTmuxController+CurrentWindowMirror.swift
  • Sources/RemoteTmuxController.swift
  • Sources/TabManager.swift
  • Sources/TerminalController+RemoteTmux.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/RemoteTmuxMirrorCloseDetachTests.swift

Comment thread Sources/TabManager.swift
The close-every-tab path in closeWorkspacesWithConfirmation still said it kills
the remote session on commit; it detaches like every other close path now.
Aligns the last remaining stale comment (coderabbit review on manaflow-ai#7264).
@robertnisipeanu

Copy link
Copy Markdown
Contributor

@0xJord4n not sure this is a step in the right-direction either, because now detach is a possibility, but a synced close is not possible anymore. So while now detaching is possible again, closing & cleaning up sessions/tabs on the remote is degraded (not possible anymore).

@0xJord4n

0xJord4n commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@robertnisipeanu agreed — swinging from "always kill" to "always detach" trades one gap for the other. The close gesture is simply ambiguous: it can't express both "get this off my screen" and "I'm done with this session."

I think the two verbs need to be separate, like tmux itself (detach vs kill-session):

  • Default close = detach. The asymmetry is risk: an accidental detach costs a re-attach; an accidental kill is unrecoverable work loss. The destructive verb shouldn't be reachable by the same gesture as the safe one.
  • Explicit kill = its own affordance. The kill path (handleWorkspaceClosed + the window kill-marker) was deliberately kept dormant for exactly this. Concretely: a context-menu item on mirror workspaces — "Kill Remote Session and Close" — next to the existing "Reconnect" item, so a synced close is one right-click away. Host-level cleanup (per-host X, batch kill) belongs with the Remote workspace mode: SSH ControlMaster + first-class remote tmux/shell support #2673 sidebar design as the follow-up.

That would restore synced close without making it the default blast radius — and it's a small diff since the machinery is already in place. Does that shape work for you? If so I'll add it to this PR.

@austinywang
austinywang merged commit b64a66d into manaflow-ai:main Jul 14, 2026
23 of 25 checks passed
naveengovind added a commit to naveengovind/cmux that referenced this pull request Jul 18, 2026
Three fixes for the local tmux mirror, all live-verified against a real
tmux 3.5a server plus unit/e2e coverage:

1. New tab / session working directory (was: filesystem root). cmux's own
   process cwd is `/` when launched from Finder, so a `new-window` or
   `new-session` it sent without `-c` inherited `/` as the start dir and
   stranded new tabs in the root. `newWindowCommand` now falls back to
   `-c '#{pane_current_path}'` (the target window's own active-pane dir,
   expanded server-side) when no concrete cwd is known, and a session cmux
   creates on the LOCAL server passes `-c $HOME`. SSH sessions are
   unchanged (a remote `new-session` already starts in the login home).

2. Focus-gated size authority. While the cmux app is not frontmost, each
   mirror connection sends `refresh-client -f ignore-size` so a
   co-attached real terminal drives the window size (no filler border in
   that terminal); when cmux returns to the foreground it clears the flag
   (`!ignore-size`) and re-imposes its own grid. `ignore-size` excludes the
   control client from window-size calculation, so a cmux-sole-client
   window simply holds its size (no balloon, measured on 3.5a). State is
   kept across reconnects (reseed re-applies) and applied to connections
   that attach while backgrounded. Driven by NSApplication become/resign
   active in RemoteTmuxController.

3. Closing a mirror workspace kills its tmux session (two-way close sync).
   For a LOCAL mirror, an explicit workspace close now routes to
   handleWorkspaceClosed (kill-session) instead of detach — cmux is the
   tmux UI, so closing the workspace closes the session, symmetric with
   auto-mirroring a new session on open. handleWorkspaceClosed only kills a
   still-live session, so the tmux->cmux session-ended cleanup that also
   routes through closeWorkspace detaches without a redundant kill (manaflow-ai#7364).
   SSH mirrors keep detach-for-resume (PR manaflow-ai#7264); closing the whole cmux
   window still detaches, so quitting never kills every session at once.

Tests: newWindowCommand cwd/placement assertions updated for the
`#{pane_current_path}` fallback; new live e2e
`sizeAuthorityReleaseTogglesIgnoreSizeFlag` asserts the flag toggles via
`list-clients`. 20 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
patrick-toulme added a commit to patrick-toulme/cmux that referenced this pull request Aug 17, 2026
Pressing X on a mirrored session workspace looked like it closed the
session, but only detached: the session kept running on the machine, and
with no per-session kill anywhere in the UI, every new-session + close
cycle leaked a live session (observed: auto-named sessions 27-33 piling
up on cloudtop, each still running an agent).

Explicit per-workspace closes (sidebar X, tab X, Cmd+W, context-menu
Close, batch multi-close) now kill the session, the workspace-level
analogue of the window-tab X that already kills its tmux window. The
confirmation reads the REMOTE session's activity and names the running
command; idle sessions close instantly. The kill rides the live control
connection and vetoes the local close, so tmux's own %exit tears the
workspace down; without a live connection the close degrades to the old
detach. This deliberately narrows the blanket detach-on-close rule from
the upstream PR manaflow-ai#7264 review to the non-explicit paths: window close,
app quit, batch close-all via the window path, and non-interactive
socket closes still detach, and the workspace context menu gains an
explicit Detach (Keep Session Running) for the resume workflow.

Localization audited: four new catalog strings (kill dialog title, both
message forms, detach menu item) added in en, ja, ko, and uk; no other
user-facing text changed.
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.

3 participants