Skip to content

Track remote cwd from ssh/mosh prompt OSC signals - #3812

Closed
austinywang wants to merge 45 commits into
mainfrom
issue-3791-mosh-remote-cwd-detection
Closed

austinywang wants to merge 45 commits into
mainfrom
issue-3791-mosh-remote-cwd-detection

Conversation

@austinywang

@austinywang austinywang commented May 10, 2026 •

Copy link
Copy Markdown
Contributor

cmux tracks local pane cwd/branch primarily through its shell prompt integration: zsh/bash hooks call the cmux socket (report_pwd, report_git_branch, clear_git_branch) and Ghostty also has an OSC 7 cwd action path for terminal-emitted cwd. A plain ssh/mosh launched inside a normal pane crosses into a shell that has neither cmux's local socket integration nor the dedicated cmux ssh relay bootstrap, and Ghostty was also rejecting non-local OSC 7 hosts before the app could classify them. The smallest robust fix is to treat terminal cwd as first-class per-surface location state, let the embedded app receive non-local OSC 7 as a URI, and ship cmux remote-shell-init so remote bash/zsh prompts can emit OSC 7 with host/path plus cmux git-branch query hints over the terminal stream.

Closes #3791.

Fix

  • Added TerminalLocation state per surface with local/remote host+path parsing from OSC 7 (file://host/path) and branch query hints.
  • Routed TabManager.updateSurfaceDirectory through remote-aware location updates and skipped local git/PR probes for remote locations.
  • Updated sidebar directory resolution to render remote pane cwd as host:/path while preserving raw paths for local filesystem behavior.
  • Added cmux remote-shell-init, printing a bash/zsh snippet that installs prompt hooks and emits OSC 7 with cwd, branch, and dirty state.
  • Adjusted the Ghostty fork so non-local OSC 7 still updates Ghostty's internal path but reaches cmux as the original URI for host-aware classification.
  • Documented the command in README and the CLI contract, and localized new CLI help strings.

Tests

  • Added regression coverage for OSC 7 remote host/path/branch parsing and workspace sidebar state updates.
  • Added bash and zsh smoke tests for RemoteShellIntegrationSnippet.script() that assert the emitted OSC 7 bytes include host, branch, dirty state, and ST terminator.
  • Added the failing tests first in commit 77e1dfbef, then the implementation in commit 98a2124fb.

Local Validation

  • git diff --check
  • Resources/Localizable.xcstrings parsed as JSON with Ruby.
  • Built GhosttyKit for submodule SHA 6b8d558cea313b09c10ac107254d6ee41a5044d9 with CMUX_GHOSTTYKIT_NO_PREBUILT=1 ./scripts/ensure-ghosttykit.sh to generate the real archive checksum.
  • Did not run local unit/UI tests per repo/task policy.

Manual Repro Plan After CI

  1. Launch the tagged dev build with CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag issue-3791-mosh-remote-cwd-detection --launch.
  2. Open a regular terminal pane, cd into a local git repo, and confirm the sidebar shows the local cwd/branch.
  3. From that pane, run ssh localhost or mosh <host>.
  4. On the remote shell, run eval "$(cmux remote-shell-init)" or append the printed snippet to ~/.zshrc/~/.bashrc, then cd into a git worktree.
  5. Confirm the sidebar/title metadata follows the remote host:/path and branch.

Notes

  • The Ghostty submodule commit is pushed to manaflow-ai/ghostty branch issue-3791-mosh-remote-cwd-detection. Direct push to Ghostty main was rejected by branch protection, so the commit is reachable but not yet on Ghostty main.
  • No screenshots/asciinema yet; final visual verification is blocked until CI is green and the required tagged dev build is launched.

Note

Medium Risk
Touches sidebar directory/branch/PR tracking, session snapshot/restore, and terminal socket reporting paths; mistakes could cause incorrect git/PR state or directory display, but changes are mostly additive with extensive new tests.

Overview
Adds first-class per-surface TerminalLocation state that can parse OSC 7/file:///kitty-shell-cwd:// cwd reports (including remote host) and optional git-branch hints, and updates sidebar directory resolution to display remote locations as host:/path.

Makes git/PR probing and listening-port tracking remote-aware: remote locations skip local git metadata probes/PR refreshes and clear stale local PR/branch state; session snapshot/restore and detached-surface transfer now persist terminal location when appropriate while avoiding restoring remote display paths as local directories.

Introduces cmux remote-shell-init to print a bash/zsh prompt-hook snippet that emits OSC 7 cwd + branch/dirty hints for plain ssh/mosh panes (with localized help/errors), updates docs/README accordingly, and bumps the pinned GhosttyKit fork/checksums to forward non-local OSC 7 URIs to the app. Also includes a small browser history restore fix and broad new unit/smoke test coverage for parsing, persistence, and shell snippet output.

Reviewed by Cursor Bugbot for commit a98992e. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Track remote cwd and git branch from OSC 7/kitty-shell-cwd:// (and file://) in plain ssh/mosh panes and show them per‑pane as host:/path. Adds cmux remote-shell-init, makes sidebar/Finder/git/PR/port scans and session restore remote‑aware, and updates the ghostty fork to forward non‑local OSC 7. Closes #3791.

  • New Features

    • Per‑pane TerminalLocation from OSC 7/kitty-shell-cwd:///file:// with optional git hints; displays host:/path, treats loopback as local, preserves trailing spaces and remote file:// URIs, escapes ;/: in hosts, and clears stale branch on the first local update after a remote.
    • Remote‑aware behavior: sidebar renders host:/path; Finder opens only local paths; skip local git/PR and listening‑port scans for remote panes; avoid re‑publishing remote git‑branch hints; detached transfer and session snapshots carry TerminalLocation and ignore remote display strings for local restore; normalize terminal URI paths; never treat host:/path or host:port as local cwd.
    • CLI: cmux remote-shell-init prints a zsh/bash snippet that emits OSC 7 with escaped host/path and optional git hints; supports CMUX_REMOTE_HOST and CMUX_REMOTE_DISABLE_GIT=1; zsh‑safe, preserves PATH, with localized help/error strings.
    • ghostty fork: forwards non‑local OSC 7 URIs to the app; submodule pin and checksums updated.
  • Migration

    • On the remote host inside a plain ssh/mosh pane: run cmux remote-shell-init >> ~/.zshrc or >> ~/.bashrc, then source it. Optionally set CMUX_REMOTE_HOST=name to override the displayed host, or CMUX_REMOTE_DISABLE_GIT=1 to report cwd only.

Written for commit a98992e. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a CLI command to print a shell snippet for remote shells to report cwd and git branch.
  • Behavior

    • Sidebar and terminals show remote locations as host:/path, skip local Git/PR probing for remote panels, and persist/restore per-panel terminal locations.
  • Documentation

    • Expanded SSH guide and CLI docs with usage, init steps, examples, and git-disable tip for slow filesystems.
  • Localization

    • Added localized help/error strings for the new command.
  • Tests

    • Added unit and integration tests for OSC7 parsing, snippet emission, and session restore.
  • Chores

    • Updated embedded tool pin and checksum.

Review Change Stack

Plain SSH or mosh prompt escapes need to carry a remote host, path, and branch through terminal state before sidebar rendering can be correct. These regression tests encode the expected OSC 7 signal and the reusable remote shell snippet behavior before adding the implementation.

Constraint: Keep the bug-fix history test-first for issue 3791

Rejected: Sidebar-only string assertions | they would not prove terminal escape handling or shell prompt emission

Confidence: high

Scope-risk: narrow

Tested: git diff --check

Not-tested: Local test execution is prohibited by workspace policy
@vercel

vercel Bot commented May 10, 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 May 20, 2026 2:06am
cmux-staging Building Building Preview, Comment May 20, 2026 2:06am

@coderabbitai

coderabbitai Bot commented May 10, 2026 •

Copy link
Copy Markdown

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

Adds OSC 7-based remote cwd and Git-branch reporting: TerminalLocation model and parsers, RemoteShellIntegrationSnippet + cmux remote-shell-init CLI, per-panel terminalLocation tracking and session persistence, TabManager/TerminalController probe gating, tests, localization/docs, Xcode and ghostty packaging updates.

Changes

Remote Terminal Location Detection

Layer / File(s) Summary
TerminalLocation Data Model
Sources/TerminalLocation.swift
New TerminalLocation type parsing OSC7/file:///kitty-shell-cwd inputs, host normalization, remote detection, git-branch signal decoding, and session snapshot bridging.
Remote Shell Integration Snippet
Sources/RemoteRelayZshBootstrap.swift
RemoteShellIntegrationSnippet.script() returns a bash/zsh snippet that escapes/sanitizes host/path, strips control characters, computes optional git query, emits OSC 7 file:// payload with cmux_git_branch/cmux_git_dirty, and installs prompt hooks for Zsh/Bash avoiding duplicates.
CLI Command
CLI/cmux.swift
Adds remote-shell-init dispatch and runRemoteShellInit(commandArgs:) handler that rejects unexpected arguments and prints the generated snippet; updates help/usage mapping and supported-commands listing with localized usage text.
Workspace Terminal Location Tracking
Sources/Workspace.swift, Sources/Workspace+DetachedSurfaceTransfer.swift
Adds @Published private(set) var panelTerminalLocations: [UUID: TerminalLocation], updatePanelLocation(...), terminalLocation(for:); parses reported directories into TerminalLocation; persists/restores terminalLocation in session snapshots; forwards terminalLocation on detach/attach and prunes on teardown.
Workspace Sidebar & Display Logic
Sources/Workspace.swift
Sidebar resolution and display-order logic prefer TerminalLocation.displayDirectory when present; focused-panel fallbacks and branch-directory grouping use terminal location when available.
Terminal Data Flow
Sources/TabManager.swift, Sources/TerminalController.swift
TerminalController.normalizeReportedDirectory uses TerminalLocation.parseReportedDirectory. TabManager.updateSurfaceDirectory() parses into TerminalLocation and calls updateSurfaceLocation(..., location:); local Git/PR probing is gated on location.isRemote and tab.isRemoteWorkspace, with PR/probe clearing for remote locations and appropriate refresh scheduling for directory/branch changes.
Session Snapshot Persistence
Sources/SessionPersistence.swift
Adds SessionTerminalLocationSnapshot and terminalLocation: SessionTerminalLocationSnapshot? on SessionPanelSnapshot to persist terminal locations across sessions.
Tests & Integration
cmuxTests/*
Adds OSC7/kitty-shell-cwd parsing unit tests, session restore tests, TabManager unit tests for branch-triggered probe scheduling and races, and smoke tests that run the generated snippet under Bash/Zsh asserting OSC7 emission, encoded branch, dirty flag, escaping, Bash PROMPT_COMMAND behavior, and control-char stripping; adds regression tests for tilde canonicalization and stale PR clearing.
Localization & Documentation
Resources/Localizable.xcstrings, README.md, docs/cli-contract.md, docs/ghostty-fork.md
Adds cli.remote-shell-init.error.unknownArguments and cli.remote-shell-init.usage (en/ja); expands README SSH guidance to document cmux remote-shell-init and CMUX_REMOTE_DISABLE_GIT; adds CLI contract entry and help probe; documents Ghostty fork note.
Xcode Project
GhosttyTabs.xcodeproj/project.pbxproj
Registers TerminalLocation.swift in the project: PBXFileReference, Sources group, PBXBuildFile, and adds it to the PBXSourcesBuildPhase.
Ghostty Submodule & Packaging
docs/ghostty-fork.md, ghostty, scripts/ghosttykit-checksums.txt
Updates ghostty submodule pin to a fork commit required for non-local OSC7 URIs, inserts new GhosttyKit checksum mapping, and documents the fork/commit and release/checksum regeneration note.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as cmux CLI
  participant Shell as Remote shell (bash/zsh)
  participant TM as TabManager
  participant WS as Workspace
  CLI->>Shell: cmux remote-shell-init (print snippet)
  Shell->>Shell: source snippet -> install prompt hook
  Shell->>TM: emit OSC7 file://... with cmux_git_branch/cmux_git_dirty
  TM->>WS: updatePanelLocation(panelId, TerminalLocation)
  TM->>TM: gate git/PR probes (skip if remote)
  WS->>WS: persist terminalLocation in session snapshot
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#1292: Overlaps SSH remote-shell integration and TerminalLocation/snippet work.
  • manaflow-ai/cmux#3394: Updates ghostty submodule pin and related packaging artifacts — directly related to the dependency updates here.
  • manaflow-ai/cmux#2123: Modifies Workspace detach/attach transfer state — related to DetachedSurfaceTransfer/terminalLocation changes.

Poem

🐰 I nibble at OSC 7’s glow,

I carry host and path in tow.
A tiny script, printed neat,
Source it once, then cmux will meet
your remote cwd and branch on show.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error TerminalLocation struct lacks Sendable conformance but is stored in @Published and parsed from nonisolated contexts, violating Swift 6 actor isolation rules. Mark struct TerminalLocation: Equatable, Sendable in Sources/TerminalLocation.swift. All members (String, enums) are implicitly Sendable-compatible.
Cmux Swift File And Package Boundaries ❌ Error TerminalLocation.swift (196 lines) added to Sources/ root. Per boundary rules, lifecycle-independent feature logic belongs in scoped areas, not app-target root. Review comment flagged this. Move Sources/TerminalLocation.swift to scoped directory (e.g., Sources/RemoteDirectory/) per codebase pattern of Auth/, Feed/, Panels/, Sidebar/
Docstring Coverage ⚠️ Warning Docstring coverage is 4.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (12 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: tracking remote current working directory from OSC signals emitted by remote ssh/mosh prompt hooks.
Linked Issues check ✅ Passed The PR fully addresses all objectives from issue #3791: detects remote context via OSC 7 parsing, implements a protocol via cmux remote-shell-init for remote shells to report cwd/git branch, displays remote location as host:/path in sidebar, skips local git/PR probes for remote locations, and provides integration documentation.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing remote cwd/git branch tracking; no unrelated features or refactorings are present beyond what is necessary to achieve the stated objectives.
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization patterns (semaphores, waits, sleeps, main-queue sync) found in production code. Blocking patterns exist only in test files for test synchronization, which is acceptable.
Cmux No Hacky Sleeps ✅ Passed This PR does not modify any non-Swift runtime files (shell, JS, TS, Python). The check is not applicable as it only covers TypeScript, JavaScript, shell, and non-Swift runtime scripts.
Cmux Swift Concurrency ✅ Passed No legacy async patterns. New code synchronous; no Dispatch.global/queues, Tasks, or completion handlers. panelTerminalLocations @Published follows established patterns.
Cmux Swift @Concurrent ✅ Passed No async functions, @concurrent annotations, or violations detected. All new code is synchronous with lightweight string/URL parsing and state updates only.
Cmux Swift Logging ✅ Passed No Swift logging violations. The print() in CLI/cmux.swift is CLI output (whitelisted). RemoteRelayZshBootstrap contains shell code. Production code is compliant.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state layout violations found. New @Published state on existing ObservableObject class is allowed. TerminalLocation is value struct. Proper observation boundaries.
Cmux Architecture Rethink ✅ Passed No violations found. No timing repair paths, no split lifecycle ownership, no duplicate wiring. Clean state consolidation via panelTerminalLocations.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No new standalone windows (NSWindow, NSPanel, NSWindowController, SwiftUI Window/WindowGroup) introduced. All changes involve internal state/model management for remote terminal tracking.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all major template sections including summary, testing details, manual validation, and a detailed review plan.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-3791-mosh-remote-cwd-detection

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 and usage tips.

Comment thread Sources/Workspace.swift Outdated
@greptile-apps

greptile-apps Bot commented May 10, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds first-class per-surface TerminalLocation state to track remote cwd/branch for plain ssh/mosh panes via OSC 7 (file://host/path?cmux_git_branch=...). It routes all directory updates through updateSurfaceLocation, makes git/PR probing, Finder resolution, listening-port display, and session restore remote-aware, introduces cmux remote-shell-init, and updates the Ghostty fork to forward non-local OSC 7 URIs.

  • TerminalLocation struct (Sources/Terminal/TerminalLocation.swift, 228 lines): parses OSC 7 / kitty-shell-cwd:// / plain paths into a (host, path, source, gitBranchSignal) tuple; caches local hostname candidates statically; drives displayDirectory as host:/path for remote hosts and plain path for local/loopback.
  • TabManager.updateSurfaceLocation replaces the old updateSurfaceDirectory body with a remote-aware flow: skips local git/PR probes for remote or remote-workspace panels, applies gitBranchSignal from OSC 7 query params, clears stale remote branch when transitioning back to local, and schedules git metadata refreshes only for local panels.
  • Shell snippet (RemoteShellIntegrationSnippet) and new cmux remote-shell-init CLI command emit a bash/zsh precmd hook that printf-formats OSC 7 with escaped host/path and optional git hints; hostname and URI-escaping are computed once at sourcing time.

Confidence Score: 5/5

The change is safe to merge; OSC7 remote panes are excluded from git probing, Finder resolution, listening-port snapshots, and session restore at every entry point.

TerminalLocation is a clean nonisolated value type with thorough test coverage. TabManager remote-aware guards are consistent across all probe paths. Shell snippet escaping and hostname caching are correct. Session persistence is forward-compatible. The only open item is a narrow fallback edge in sidebarBranchDirectoryEntriesInDisplayOrder that does not affect the primary code paths.

Sources/Workspace.swift — sidebarBranchDirectoryEntriesInDisplayOrder defaultDirectory fallback warrants a second look; all other files are clean.

Important Files Changed

Filename Overview
Sources/Terminal/TerminalLocation.swift New nonisolated Sendable struct for OSC 7 / kitty-shell-cwd / plain-path parsing; static localHostnameCandidates caches DNS work; gitBranchSignal carries branch hints from query params; well-tested and cleanly separated from Workspace.
Sources/TabManager.swift updateSurfaceLocation replaces updateSurfaceDirectory body with remote-aware PR/branch/git-probe logic; correctly guards gitProbeDirectory, updateSurfaceGitBranch, and PR sweep results for remote panels; branch-only local update now schedules a refresh via shouldScheduleLocalBranchRefresh.
Sources/Workspace.swift panelTerminalLocations added alongside panelDirectories; updatePanelLocation strips branch signal before storing; sidebarFinderDirectory now filters OSC7-remote panels; localRestorableDirectory guards session restore against host:/path strings; sidebarBranchDirectoryEntriesInDisplayOrder defaultDirectory now reflects focused panel location.
Sources/RemoteRelayZshBootstrap.swift RemoteShellIntegrationSnippet emits a bash/zsh precmd snippet with correct printf ESC escaping, hostname cached once at sourcing time, path and host percent-encoded including brackets, git query appended when not disabled.
Sources/SessionPersistence.swift SessionTerminalLocationSnapshot added with forward-compatible manual Codable that silently treats unknown source strings as nil; integrates cleanly into SessionPanelSnapshot via optional field with default nil.
Sources/TerminalController.swift normalizeReportedDirectory now returns full URI for remote locations so downstream locationForDirectoryUpdate can classify host; socket status output (cwd/focused_cwd) prefers terminalLocation.displayDirectory for remote panes.
cmuxTests/SidebarOrderingTests.swift 398-line addition covering OSC 7 parsing (remote host/path/branch, loopback-as-local, bracketed paths), sidebar directory ordering, and sidebarFinderDirectory filtering for OSC7-remote panels.
cmuxTests/WorkspaceRemoteConnectionTests.swift 322-line addition covering remote branch state transitions, stale-branch clearing on local return, PR-probe suppression for remote panels, and detached-surface location transfer.
cmuxTests/SessionPersistenceTests.swift New tests cover session snapshot round-trips for OSC7-remote listening-port skipping, unknown-source forward-compat decode, and panel location snapshot save/load.
CLI/cmux.swift remote-shell-init subcommand wired with --help / unknown-arg handling via localized strings; usage string added to the help registry; command listed in CLI contract doc.
Resources/Localizable.xcstrings Both new CLI keys (cli.remote-shell-init.usage, cli.remote-shell-init.error.unknownArguments) include translations for all 19 supported locales after prior review iteration.

Reviews (44): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/Workspace.swift Outdated
@austinywang
austinywang force-pushed the issue-3791-mosh-remote-cwd-detection branch 2 times, most recently from ad933a7 to 0efb191 Compare May 10, 2026 02:57
coderabbitai[bot]
coderabbitai Bot previously requested changes May 10, 2026

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

🤖 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/ghostty-fork.md`:
- Around line 179-185: Update the docs/ghostty-fork.md note to include a clear
merge timeline and action item: mention that the temporary branch
issue-3791-mosh-remote-cwd-detection (commit 6b8d558ce) will be merged into the
fork main once the fork main PR is approved, specify the expected date or
milestone for that merge, and add an instruction to regenerate and checksum-pin
the xcframework-6b8d558ce... release after the merge so the submodule can move
from the temporary branch to a stable main pointer.

In `@Sources/RemoteRelayZshBootstrap.swift`:
- Around line 60-67: The OSC 7 emitter __cmux_remote_report_prompt currently
inserts raw output from __cmux_remote_hostname and PWD (via
__cmux_remote_path_escape) which can contain control/non-printable bytes; update
__cmux_remote_report_prompt to sanitize both host and path by running them
through a control-character stripping helper (e.g. __cmux_strip_control_chars or
similar) before building the query and calling printf, and add/ensure such a
helper function exists and is used for both the hostname (output of
__cmux_remote_hostname) and the path (result of __cmux_remote_path_escape
"${PWD:-/}") so the emitted OSC 7 file:// URI cannot contain control characters
or enable terminal-sequence injection.
- Around line 75-79: Bash PROMPT_COMMAND may be an array in Bash 5.1+, so the
current string-based case that manipulates PROMPT_COMMAND can clobber array-mode
hooks; update the block handling PROMPT_COMMAND to first detect array-mode (use
declare -p PROMPT_COMMAND 2>/dev/null and test for "declare -a" or [[ "$(declare
-p PROMPT_COMMAND 2>/dev/null)" =~ "declare -a" ]]), and if it is an array,
check for the presence of "__cmux_remote_report_prompt" in
"${PROMPT_COMMAND[@]}" and, if missing, prepend or append it via
PROMPT_COMMAND=( "__cmux_remote_report_prompt" "${PROMPT_COMMAND[@]}" ) or
PROMPT_COMMAND+=( "__cmux_remote_report_prompt" ); otherwise keep the existing
string-mode case that checks PROMPT_COMMAND and sets
PROMPT_COMMAND="__cmux_remote_report_prompt${PROMPT_COMMAND:+;$PROMPT_COMMAND}".
Ensure you reference PROMPT_COMMAND and __cmux_remote_report_prompt in the
updated logic.

In `@Sources/TabManager.swift`:
- Around line 3940-3955: The switch handling location.gitBranchSignal doesn't
always clear existing panel pull-request state when the path is remote; update
the .branch and .unspecified cases (and the similar block around the other
occurrence at lines referenced) to call tab.clearPanelPullRequest(panelId:
surfaceId) whenever location.isRemote (in addition to existing
tab.clearPanelGitBranch / clearWorkspacePullRequestTracking calls), so remote
classification always clears stale panel pull-request state; locate uses of
location.gitBranchSignal, tab.updatePanelGitBranch, tab.clearPanelGitBranch,
tab.clearPanelPullRequest, and clearWorkspacePullRequestTracking to make the
change.

In `@Sources/Workspace.swift`:
- Around line 6689-6847: Move the entire TerminalLocation struct and the private
String extension (including symbols TerminalLocation, Source, GitBranchSignal,
parseOSC7Sequence(_:), parseReportedDirectory(_:source:),
gitBranchSignal(from:isRemote:), normalizedHost(_:), isLocalHost(_:),
localHostnames(), and prefixBeforeFirstTerminalTerminator()) into a new
dedicated Swift file as a standalone model; remove the original definition from
Workspace.swift so Workspace only imports/uses TerminalLocation and
SidebarGitBranchState, ensure the new file has the same access levels (or mark
types/internal methods public if needed by other modules), add any required
imports at the top, and run the build to update references where Workspace used
these functions so callers reference the moved symbols.
🪄 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: cbeff522-6d47-4e76-8eb7-8db9fc25c723

📥 Commits

Reviewing files that changed from the base of the PR and between 8cdca36 and ad933a7.

📒 Files selected for processing (13)
  • CLI/cmux.swift
  • README.md
  • Resources/Localizable.xcstrings
  • Sources/RemoteRelayZshBootstrap.swift
  • Sources/TabManager.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • cmuxTests/SidebarOrderingTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift
  • docs/cli-contract.md
  • docs/ghostty-fork.md
  • ghostty
  • scripts/ghosttykit-checksums.txt

Comment thread docs/ghostty-fork.md Outdated
Comment thread Sources/RemoteRelayZshBootstrap.swift
Comment thread Sources/RemoteRelayZshBootstrap.swift Outdated
Comment thread Sources/TabManager.swift
Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/TabManager.swift
@austinywang
austinywang force-pushed the issue-3791-mosh-remote-cwd-detection branch from 4b60077 to d18da68 Compare May 10, 2026 03:14
Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/TabManager.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 10, 2026

@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

Caution

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

⚠️ Outside diff range comments (2)
Sources/TabManager.swift (1)

3934-3974: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Trigger an immediate refresh when the OSC 7 branch hint changes in-place.

Line 3963 only schedules refreshes on directory changes. After this refactor, local branch changes can arrive through location.gitBranchSignal with the same cwd, so git checkout in place can keep showing the old PR until the next 10s/60s poll.

💡 Suggested fix
 func updateSurfaceLocation(tabId: UUID, surfaceId: UUID, location: TerminalLocation) {
     guard let tab = tabs.first(where: { $0.id == tabId }) else { return }
     let shouldProbeLocalGit = !location.isRemote && !tab.isRemoteWorkspace
     let shouldClearLocalPullRequestState = !shouldProbeLocalGit
     let previousDirectory = shouldProbeLocalGit ? gitProbeDirectory(for: tab, panelId: surfaceId) : nil
     let probeKey = WorkspaceGitProbeKey(workspaceId: tabId, panelId: surfaceId)
+    let previousBranch = tab.panelGitBranches[surfaceId]
     tab.updatePanelLocation(panelId: surfaceId, location: location)
+
+    var explicitLocalBranchChange = false

     if shouldClearLocalPullRequestState {
         tab.clearPanelPullRequest(panelId: surfaceId)
         clearWorkspacePullRequestTracking(for: probeKey)
     }

     switch location.gitBranchSignal {
     case .branch(let branch):
         let normalizedBranch = Self.normalizedBranchName(branch.branch) ?? branch.branch
+        explicitLocalBranchChange =
+            shouldProbeLocalGit &&
+            (previousBranch?.branch != normalizedBranch || previousBranch?.isDirty != branch.isDirty)
         tab.updatePanelGitBranch(panelId: surfaceId, branch: normalizedBranch, isDirty: branch.isDirty)
         if !shouldClearLocalPullRequestState {
             clearWorkspacePullRequestTracking(for: probeKey)
         }
     case .clear where location.isRemote:
         tab.clearPanelGitBranch(panelId: surfaceId)
     case .unspecified where location.isRemote:
         tab.clearPanelGitBranch(panelId: surfaceId)
     case .clear, .unspecified:
         break
     }

     let nextDirectory = shouldProbeLocalGit ? normalizedWorkingDirectory(location.path) : nil
-    if shouldProbeLocalGit, previousDirectory != nextDirectory {
+    if shouldProbeLocalGit, previousDirectory != nextDirectory || explicitLocalBranchChange {
+        let reason = explicitLocalBranchChange && previousDirectory == nextDirectory
+            ? "branchChange"
+            : "directoryChange"
         scheduleWorkspacePullRequestRefresh(
             workspaceId: tabId,
             panelId: surfaceId,
-            reason: "directoryChange"
+            reason: reason
         )
         scheduleWorkspaceGitMetadataRefreshIfPossible(
             workspaceId: tabId,
             panelId: surfaceId,
-            reason: "directoryChange"
+            reason: reason
         )
     }
 }
🤖 Prompt for 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.

In `@Sources/TabManager.swift` around lines 3934 - 3974, updateSurfaceLocation
currently only schedules pull-request and git-metadata refreshes when the
working directory changes; you need to also trigger an immediate refresh when
the OSC7 gitBranchSignal changes in-place. Before calling
tab.updatePanelGitBranch/clearPanelGitBranch, capture the panel's previous
branch state (e.g. the panel's git branch name/isDirty from the tab or via
gitProbeDirectory lookup), then after handling location.gitBranchSignal compare
previous vs new branch state (treat normalized names and clear/unspecified as
branch removals). If the branch changed and shouldProbeLocalGit is true (even if
previousDirectory == nextDirectory), call
scheduleWorkspacePullRequestRefresh(workspaceId: tabId, panelId: surfaceId,
reason: "branchChange") and
scheduleWorkspaceGitMetadataRefreshIfPossible(workspaceId: tabId, panelId:
surfaceId, reason: "branchChange"). Ensure you reference updateSurfaceLocation,
location.gitBranchSignal, tab.updatePanelGitBranch, tab.clearPanelGitBranch,
previousDirectory/nextDirectory and the two schedule... methods when making the
change.
Sources/Workspace.swift (1)

8541-8557: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep remote cwd out of the generic local-directory fields.

updatePanelLocation now writes location.path into panelDirectories, surfaceTabBarDirectory, and currentDirectory for both local and remote locations. Those fields are still reused later in this file as local spawn/base cwd inputs, so splitting a plain ssh/mosh pane can try to open a new local terminal in a remote-only path. Please keep remote locations separate here, or only copy into these fields when the location is local.

🤖 Prompt for 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.

In `@Sources/Workspace.swift` around lines 8541 - 8557, updatePanelLocation
currently copies location.path into panelDirectories, surfaceTabBarDirectory,
and currentDirectory regardless of whether the TerminalLocation is local or
remote; change it so panelTerminalLocations[panelId] is always updated but only
assign trimmed into panelDirectories, surfaceTabBarDirectory, and
currentDirectory when the location represents a local filesystem (i.e. check
TerminalLocation's transport/type or an isLocal/isRemote flag on location before
writing local fields). Keep using the trimmed path variable and preserve the
existing focused-panel checks, but skip the assignments to panelDirectories,
surfaceTabBarDirectory and currentDirectory when the location is remote.
🤖 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 `@cmuxTests/WorkspaceRemoteConnectionTests.swift`:
- Around line 89-100: The test calls runProcess with executablePath
"/usr/bin/env" and passes environment variables via the arguments array, but it
omits the "-i" flag so host shell/git env can leak; update the arguments for the
runProcess invocation(s) (the one that builds the arguments list containing
"CMUX_REMOTE_HOST=remotehost", "GIT_AUTHOR_NAME=cmux", etc., and the similar
call around lines 158-166) to include "-i" immediately after "/usr/bin/env"
(i.e., arguments should start with "-i", then the explicit VAR=VALUE entries,
followed by shellPath and "-c" and script) so the subprocess runs with an
isolated environment.

In `@Sources/RemoteRelayZshBootstrap.swift`:
- Around line 77-79: The Zsh branch currently appends
__cmux_remote_report_prompt to the precmd_functions array unconditionally,
causing duplicate registrations when resourcing; update the else branch that
manipulates precmd_functions to check whether __cmux_remote_report_prompt is
already present before appending (mirror the deduplication logic used in the
Bash path for the PROMPT_COMMAND/array case), so only add
__cmux_remote_report_prompt to precmd_functions if it's not already in the
array.

In `@Sources/TerminalLocation.swift`:
- Around line 124-137: The isLocalHost(_:) function currently only treats
"127.0.0.1" as loopback; update it to treat the whole 127.0.0.0/8 range as local
by adding a check that returns true when the normalized host starts with "127."
(i.e., first octet is 127) before falling back to the localhost/name checks and
the localHostnames() candidates; keep the existing "::1" and "localhost" checks
and candidate set logic intact so short/full hostnames still work.

In `@Sources/Workspace.swift`:
- Around line 8871-8874: Sidebar now uses panelTerminalLocations but
move/restore/detach/attach paths still only preserve panelDirectories, so remote
panes lose TerminalLocation; update every place that assigns or preserves
panelDirectories (e.g., the move/restore/detach/attach handlers and any
functions that call normalizedSidebarDirectory) to also carry and assign the
corresponding TerminalLocation into panelTerminalLocations[panelId] (using the
same TerminalLocation instance whose displayDirectory yields the
normalizedSidebarDirectory), and when restoring use
panelTerminalLocations[panelId] if present instead of only panelDirectories so
the host:/path form is retained.

---

Outside diff comments:
In `@Sources/TabManager.swift`:
- Around line 3934-3974: updateSurfaceLocation currently only schedules
pull-request and git-metadata refreshes when the working directory changes; you
need to also trigger an immediate refresh when the OSC7 gitBranchSignal changes
in-place. Before calling tab.updatePanelGitBranch/clearPanelGitBranch, capture
the panel's previous branch state (e.g. the panel's git branch name/isDirty from
the tab or via gitProbeDirectory lookup), then after handling
location.gitBranchSignal compare previous vs new branch state (treat normalized
names and clear/unspecified as branch removals). If the branch changed and
shouldProbeLocalGit is true (even if previousDirectory == nextDirectory), call
scheduleWorkspacePullRequestRefresh(workspaceId: tabId, panelId: surfaceId,
reason: "branchChange") and
scheduleWorkspaceGitMetadataRefreshIfPossible(workspaceId: tabId, panelId:
surfaceId, reason: "branchChange"). Ensure you reference updateSurfaceLocation,
location.gitBranchSignal, tab.updatePanelGitBranch, tab.clearPanelGitBranch,
previousDirectory/nextDirectory and the two schedule... methods when making the
change.

In `@Sources/Workspace.swift`:
- Around line 8541-8557: updatePanelLocation currently copies location.path into
panelDirectories, surfaceTabBarDirectory, and currentDirectory regardless of
whether the TerminalLocation is local or remote; change it so
panelTerminalLocations[panelId] is always updated but only assign trimmed into
panelDirectories, surfaceTabBarDirectory, and currentDirectory when the location
represents a local filesystem (i.e. check TerminalLocation's transport/type or
an isLocal/isRemote flag on location before writing local fields). Keep using
the trimmed path variable and preserve the existing focused-panel checks, but
skip the assignments to panelDirectories, surfaceTabBarDirectory and
currentDirectory when the location is remote.
🪄 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: 157018c2-7c21-4acc-bbaf-9349b836356b

📥 Commits

Reviewing files that changed from the base of the PR and between ad933a7 and 5156d7a.

📒 Files selected for processing (15)
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • README.md
  • Resources/Localizable.xcstrings
  • Sources/RemoteRelayZshBootstrap.swift
  • Sources/TabManager.swift
  • Sources/TerminalController.swift
  • Sources/TerminalLocation.swift
  • Sources/Workspace.swift
  • cmuxTests/SidebarOrderingTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift
  • docs/cli-contract.md
  • docs/ghostty-fork.md
  • ghostty
  • scripts/ghosttykit-checksums.txt

Comment thread cmuxTests/WorkspaceRemoteConnectionTests.swift
Comment thread Sources/RemoteRelayZshBootstrap.swift
Comment thread Sources/Terminal/TerminalLocation.swift
Comment thread Sources/Workspace.swift
Comment thread Sources/RemoteRelayZshBootstrap.swift
Comment thread Sources/RemoteRelayZshBootstrap.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 10, 2026

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

🤖 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/RemoteRelayZshBootstrap.swift`:
- Around line 52-63: The git-status probe in __cmux_remote_git_branch_query runs
on every prompt and should be controllable; add an opt-out guard that checks a
new env var (e.g. CMUX_REMOTE_DISABLE_GIT=1 or CMUX_REMOTE_GIT_DIRTY=0) at the
top of __cmux_remote_git_branch_query and, when set, skip running `git status
--porcelain --untracked-files=normal` (still determine branch via git
symbolic-ref/rev-parse if desired) and emit cmux_git_dirty=0 (or omit the dirty
field) so the function remains backward-compatible while avoiding expensive
per-prompt status checks on slow remote filesystems.
- Around line 65-71: The host inserted into the file:// URI in
__cmux_remote_report_prompt is only stripped of control chars and can contain
characters that break the URI; update the composition to URI-escape the host by
passing the stripped host through __cmux_remote_uri_escape (same helper used for
branch names) before inserting into the printf that builds the
'\\033]7;file://%s%s%s\\033\\\\' string so the authority is always well-formed;
locate the host assignment and the printf in __cmux_remote_report_prompt and
replace the direct host use with the escaped-host variable.

In `@Sources/TabManager.swift`:
- Around line 3943-3945: When clearing local PR bookkeeping (the branch with
shouldClearLocalPullRequestState that calls tab.clearPanelPullRequest(panelId:
surfaceId) and clearWorkspacePullRequestTracking(for: probeKey)), also cancel or
mark as invalid any in-flight workspacePullRequestRefreshTask for that
WorkspaceGitProbeKey and update the surface classification state (e.g. set a
"remote" flag or clear cached classification) so that
applyWorkspacePullRequestRefreshResults(...) will re-check current
classification before writing; additionally, inside
applyWorkspacePullRequestRefreshResults ensure it reads the latest surface
classification (or the cancelled/invalidated flag for the probe key) and returns
without applying results if the surface is now classified as remote or the
refresh task was cancelled/invalidated.
- Around line 4005-4012: Move the remote-surface guard so it runs before calling
tab.updatePanelGitBranch(...) to avoid mutating local branch state for remote
surfaces; specifically, check tab.isRemoteWorkspace and
tab.terminalLocation(for: surfaceId)?.isRemote and perform
tab.clearPanelPullRequest(panelId: surfaceId) and
clearWorkspacePullRequestTracking(for: WorkspaceGitProbeKey(workspaceId: tabId,
panelId: surfaceId)) then return, only after that call
tab.updatePanelGitBranch(panelId: surfaceId, branch: normalizedBranch, isDirty:
isDirty). Ensure the guard uses the same expressions (tab.isRemoteWorkspace and
tab.terminalLocation(for: surfaceId)?.isRemote) and keep
WorkspaceGitProbeKey/clearWorkspacePullRequestTracking logic unchanged.

In `@Sources/TerminalLocation.swift`:
- Around line 84-86: The code currently trims whitespace from the decoded path
(variables path and trimmedPath) which can remove significant trailing spaces
from valid cwd values; update the logic in TerminalLocation (the path
decoding/validation around components.percentEncodedPath, path, and trimmedPath)
to stop trimming characters and instead validate emptiness using the decoded
path exactly (e.g., treat an empty string as nil but preserve any whitespace
characters), and apply the same change to the analogous check at the other
occurrence (the block referenced by lines 165-166) so session/sidebar restore
uses the exact decoded path.
- Around line 88-95: The persisted TerminalLocation is keeping local machine
names in host (e.g., "localhost" or this-Mac), causing local panes to be treated
as remote; update the normalization so that Self.normalizedHost(...) or the
logic that assigns `host` returns nil for local hosts by using
Self.isLocalHost(...) (i.e., set `host` to nil when isLocalHost is true) before
creating TerminalLocation (the current code that computes `host`, `remote`, and
`branchSignal` should assign `host = host.flatMap { Self.isLocalHost($0) ? nil :
$0 }` or equivalent), and apply the same change where TerminalLocation is
constructed later (the block around lines 168–176) so local file:// hosts are
not persisted as non-nil host values.

In `@Sources/Workspace.swift`:
- Around line 8711-8713: The code is incorrectly clearing panelTerminalLocations
during context resets which loses remote host classification used by
terminalLocation(for:) and sidebar fallback; in resetSidebarContext(reason:)
stop clearing panelTerminalLocations (remove or comment out the
panelTerminalLocations.removeAll() call) so that panelDirectories can continue
to map to host:/path entries and remote-aware git/PR suppression remains
intact—only clear panelGitBranches and pullRequest as before (or add a guarded
clear that only empties panelTerminalLocations when the actual host identity
changes).
🪄 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: f5db88c1-5fa1-4248-a05e-ff6dc11d4907

📥 Commits

Reviewing files that changed from the base of the PR and between 5156d7a and b41f32a.

📒 Files selected for processing (19)
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • README.md
  • Resources/Localizable.xcstrings
  • Sources/RemoteRelayZshBootstrap.swift
  • Sources/SessionPersistence.swift
  • Sources/TabManager.swift
  • Sources/TerminalController.swift
  • Sources/TerminalLocation.swift
  • Sources/Workspace+DetachedSurfaceTransfer.swift
  • Sources/Workspace.swift
  • cmuxTests/SessionPersistenceTests.swift
  • cmuxTests/SidebarOrderingTests.swift
  • cmuxTests/TabManagerUnitTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift
  • docs/cli-contract.md
  • docs/ghostty-fork.md
  • ghostty
  • scripts/ghosttykit-checksums.txt

Comment thread Sources/RemoteRelayZshBootstrap.swift
Comment thread Sources/RemoteRelayZshBootstrap.swift Outdated
Comment thread Sources/TabManager.swift
Comment thread Sources/TabManager.swift Outdated
Comment thread Sources/TerminalLocation.swift Outdated
Comment thread Sources/TerminalLocation.swift Outdated
Comment thread Sources/Workspace.swift
@austinywang
austinywang force-pushed the issue-3791-mosh-remote-cwd-detection branch from b41f32a to 9dcfc56 Compare May 10, 2026 03:54
Comment thread Sources/TabManager.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 10, 2026

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

♻️ Duplicate comments (1)
Sources/TerminalLocation.swift (1)

67-76: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve significant spaces in plain-path cwd updates.

This still trims valid leading/trailing spaces from non-URI paths. A cwd like /tmp/repo will be stored as /tmp/repo, which can misgroup panes and restore the wrong directory.

Suggested fix
-        let trimmed = directory.trimmingCharacters(in: .whitespacesAndNewlines)
+        let trimmed = directory.trimmingCharacters(in: .newlines)
         guard !trimmed.isEmpty else { return nil }
🤖 Prompt for 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.

In `@Sources/TerminalLocation.swift` around lines 67 - 76, The code currently
trims both whitespace and newlines into the variable trimmed which removes
significant leading/trailing spaces for plain paths; instead only strip newline
characters for non-URI paths so a path like "/tmp/repo " is preserved. Modify
the logic around trimmed and the guard that decides between
parseOSC7Sequence(trimmed) / local(path: trimmed, source: source) so that you
use a newline-only trim (e.g. trimmingCharacters(in: .newlines)) when
determining/returning a plain local(path:), while still detecting OSC7/URI
prefixes and using full whitespace trimming only for those branches; update
references to trimmed, parseOSC7Sequence(trimmed), and local(path: trimmed,
source: source) accordingly.
🤖 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 `@cmuxTests/SidebarOrderingTests.swift`:
- Around line 580-617: Add parallel test cases for the alternate URI scheme
"kitty-shell-cwd://" in TerminalOSC7LocationTests to prevent regressions: create
tests that call TerminalLocation.parseReportedDirectory and
TerminalLocation.parseOSC7Sequence (or a new helper if needed) with
"kitty-shell-cwd://..." inputs mirroring the existing file:// cases (remote with
git params, loopback IPv4 treated as local, percent-decoded path preserving
whitespace, and localhost normalizing sessionSnapshot.host to nil) and assert
the same properties (isRemote, remoteHost, path, displayDirectory, and gitBranch
via SidebarGitBranchState) so both URI forms are covered.

In `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 706-718: The test is flaky because the commandRunnerForTesting
closure only checks executable == "git" and misses resolved paths like
"/usr/bin/git"; change the guard to compare the executable's lastPathComponent
(e.g. URL(fileURLWithPath: executable).lastPathComponent == "git") so any git
path will hit the probe, and add an explicit precondition/assertion that the
workspace has no active probes (e.g.
XCTAssertTrue(workspace.activeProbes.isEmpty)) before calling
workspace.updatePanelDirectory(panelId:directory:) to avoid race conditions;
keep using the releaseProbe semaphore in the closure and signal it at the
intended point after the OSC7 update completes.

In `@ghostty`:
- Line 1: The repo currently pins the unmerged commit
6b8d558cea313b09c10ac107254d6ee41a5044d9 from branch
issue-3791-mosh-remote-cwd-detection while origin/main is at 41ab6c5ab; fix this
by either (A) preparing and opening a merge PR to bring
issue-3791-mosh-remote-cwd-detection into Ghostty main (create a branch, run CI,
request reviewers, and after merge update any dependency pins to the resulting
main commit or tag), or (B) if you must continue pinning the feature branch, add
a documented mitigation in the repo (e.g., DEPENDENCIES.md or README) that
records the commit hash, the reason for pinning, a review/retention policy, a
plan for rebasing/snapshots, and an automated alert to revisit the pin when the
branch is merged or changes; choose one of these two paths and implement the
corresponding changes and metadata.

In `@Sources/RemoteRelayZshBootstrap.swift`:
- Around line 33-41: The path-escaping function __cmux_remote_path_escape is not
percent-encoding backslashes, so a remote cwd like /tmp/a\b can produce an
invalid file:// payload; update __cmux_remote_path_escape to replace backslash
characters with %5C (and add the same replacement in the other analogous escape
sites referenced around lines 75-77) so backslashes are percent-encoded in
emitted OSC 7 paths, ensuring the replacement uses the shell string-replace
operation consistent with the other encodings in that function.

In `@Sources/Workspace.swift`:
- Around line 8545-8560: updatePanelLocation currently copies location.path into
panelDirectories, surfaceTabBarDirectory, and currentDirectory regardless of
whether the TerminalLocation is remote (OSC 7). Change it so
panelTerminalLocations[panelId] = location remains the single source of truth
for all locations, but only mirror location.path into the legacy local fields
(panelDirectories, surfaceTabBarDirectory, currentDirectory) when the location
represents a local directory; i.e., add a guard that checks the
TerminalLocation's locality flag (e.g., location.isLocal or !location.isRemote /
whatever TerminalLocation exposes to detect OSC 7 remote paths) before assigning
to panelDirectories[panelId], surfaceTabBarDirectory, and currentDirectory,
leaving remote OSC 7 paths only in panelTerminalLocations.

---

Duplicate comments:
In `@Sources/TerminalLocation.swift`:
- Around line 67-76: The code currently trims both whitespace and newlines into
the variable trimmed which removes significant leading/trailing spaces for plain
paths; instead only strip newline characters for non-URI paths so a path like
"/tmp/repo " is preserved. Modify the logic around trimmed and the guard that
decides between parseOSC7Sequence(trimmed) / local(path: trimmed, source:
source) so that you use a newline-only trim (e.g. trimmingCharacters(in:
.newlines)) when determining/returning a plain local(path:), while still
detecting OSC7/URI prefixes and using full whitespace trimming only for those
branches; update references to trimmed, parseOSC7Sequence(trimmed), and
local(path: trimmed, source: source) accordingly.
🪄 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: da302cab-5f47-4175-ab15-44e39a31e004

📥 Commits

Reviewing files that changed from the base of the PR and between b41f32a and 9dcfc56.

📒 Files selected for processing (19)
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • README.md
  • Resources/Localizable.xcstrings
  • Sources/RemoteRelayZshBootstrap.swift
  • Sources/SessionPersistence.swift
  • Sources/TabManager.swift
  • Sources/TerminalController.swift
  • Sources/TerminalLocation.swift
  • Sources/Workspace+DetachedSurfaceTransfer.swift
  • Sources/Workspace.swift
  • cmuxTests/SessionPersistenceTests.swift
  • cmuxTests/SidebarOrderingTests.swift
  • cmuxTests/TabManagerUnitTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift
  • docs/cli-contract.md
  • docs/ghostty-fork.md
  • ghostty
  • scripts/ghosttykit-checksums.txt

Comment thread cmuxTests/SidebarOrderingTests.swift
Comment thread cmuxTests/TabManagerUnitTests.swift
Comment thread ghostty Outdated
Comment thread Sources/RemoteRelayZshBootstrap.swift
Comment thread Sources/Workspace.swift

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

No issues found across 22 files

Re-trigger cubic

Comment thread Sources/Workspace.swift Outdated
Comment thread Resources/Localizable.xcstrings

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 065d8cf. Configure here.

Comment thread Sources/Workspace.swift
Comment thread Sources/Terminal/TerminalLocation.swift
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — a98992eb Deployed May 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect remote cwd and git branch when using mosh inside cmux

3 participants