Skip to content

Fix stale cmux ssh pane resize: reconcile remote PTY size after arming SIGWINCH - #5989

Merged
austinywang merged 9 commits into
manaflow-ai:mainfrom
kylejcaron:fix-ssh-pty-resize-attach-race
Jun 19, 2026
Merged

austinywang merged 9 commits into
manaflow-ai:mainfrom
kylejcaron:fix-ssh-pty-resize-attach-race

Conversation

@kylejcaron

@kylejcaron kylejcaron commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5700

Problem

Resizing a terminal pane in a cmux ssh workspace does not propagate to the remote PTY. The remote PTY stays frozen at its attach-time size, so full-screen TUIs (roborev, claude, htop, …) render at the wrong size and only Ctrl+L partially redraws (at the wrong dimensions).

Root cause

The GUI runs cmux ssh-pty-attach as the surface's command in a local PTY, and the resize chain exists: ghostty resizes the local PTY → SIGWINCH → CLI forwards workspace.remote.pty_resize → app → pty.resize on cmuxd-remote. The bug is a lost-resize race at attach time.

In CLI/cmux.swift (ssh-pty-attach), the CLI:

  1. captures the terminal size once for the bridge handshake and opens the remote PTY at that size, then
  2. arms its SIGWINCH DispatchSource only afterward.

The window between (1) and (2) spans the entire remote pty.attach round-trip, so it is wide. Any SIGWINCH delivered in that window hits SIGWINCH's default disposition (ignore) and is lost, and nothing reconciles afterward. When the surface's final grid size lands during attach/reattach — the common case, since SwiftUI lays the surface out after the helper spawns — the remote PTY stays frozen at the handshake size until a later manual resize happens to fire SIGWINCH.

Fix

After arming the SIGWINCH source, push the current size once to reconcile any resize missed during the attach window. The send logic is extracted into a shared sendSSHPTYResize helper used by both the SIGWINCH handler and the reconcile, so both read the freshest size and serialize on the same lock. The reconcile is a no-op on the daemon when the size already matches.

Verification

Verified on macOS 15 / M4 Pro (the affected config). With the fix, a freshly opened remote workspace terminal reports the correct stty size immediately on attach and across reconnects, with no manual resize; full-screen TUIs render correctly without nudging the window.

Notes on testing

This is a SIGWINCH-delivery timing race inside a monolithic syscall/socket flow; a deterministic automated test would require a full integration harness with a fake bridge + remote daemon, which is disproportionate to the one-line-of-behavior fix. It was instead verified empirically on the reporter's exact configuration as described above. Happy to add a harness-level test if maintainers prefer.


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


Summary by cubic

Fixes a lost-resize race in cmux ssh so the remote PTY picks up the pane size right after attach and on reconnect. Full-screen TUIs now render at the correct size without manual nudges.

  • Bug Fixes

    • After arming SIGWINCH, issue one immediate reconcile via SSHPTYResizeMonitor.requestCurrentResize().
    • Sample TTY size inside the same lock as the send to prevent stale overwrites; include surface_id and attachment_token when present.
    • Integration tests now expect the initial workspace.remote.pty_resize after bridge ready and validate cols/rows, surface_id, and attachment_token; test harness now includes attachment_token in the bridge “ready”.
  • Refactors

    • Kept the SSH PTY resize sender local to ssh-pty-attach; SSHPTYResizeMonitor now exposes requestCurrentResize().

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Fixed terminal resize synchronization for remote sessions. Remote PTY size is now properly reconciled when the resize handler becomes active, ensuring terminal dimensions remain consistent between local and remote environments.
  • Tests

    • Updated integration tests to account for additional resize synchronization messages and improved remote PTY validation.

…esize)

`cmux ssh-pty-attach` captures the terminal size once for the bridge
handshake and opens the remote PTY at that size, then arms its SIGWINCH
DispatchSource only afterward. The window between the handshake-size
capture and arming the source spans the entire remote `pty.attach`
round-trip, so it is wide. Any SIGWINCH delivered in that window hits
SIGWINCH's default disposition (ignore) and is lost, and nothing
reconciles afterward — so when the surface's final grid size lands during
attach/reattach (the common case, since SwiftUI lays the surface out
after the helper spawns), the remote PTY stays frozen at the handshake
size forever, corrupting full-screen TUIs (roborev, claude, htop, …).
Only a later manual resize would fire SIGWINCH and correct it.

Fix: after arming the SIGWINCH source, push the current size once to
reconcile any resize missed during the attach window. Extract the send
into a shared `sendSSHPTYResize` helper used by both the SIGWINCH handler
and the reconcile so both read the freshest size and serialize on the
same lock. The reconcile is a no-op on the daemon when the size already
matches.

Verified on macOS 15 / M4 Pro (the affected config): with the fix, a
freshly opened remote workspace terminal reports the correct `stty size`
immediately on attach and across reconnects, with no manual resize.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

@kylejcaron 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 Jun 12, 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

SIGWINCH PTY resize handling is refactored in CLI/cmux.swift to extract a sendCurrentSSHPTYResize() closure that centralizes lock acquisition, terminal size reading, and workspace.remote.pty_resize message sending. This closure is called immediately after arming the SIGWINCH handler to reconcile remote PTY size at attach time. Integration tests and test support are updated to match.

Changes

PTY Resize Helper Consolidation

Layer / File(s) Summary
sendCurrentSSHPTYResize helper and startSSHPTYResizeSource refactor
CLI/cmux.swift, cmuxTests/CLINotifyProcessTestSupport.swift
sendCurrentSSHPTYResize() is introduced to lock controlSocketLock, read current terminal cols/rows, assemble workspace.remote.pty_resize params from workspace/session/attachment metadata with optional surface fields, and send the message. It is called once immediately after startSSHPTYResizeSource arms the SIGWINCH handler to reconcile PTY size at attach time. startSSHPTYResizeSource is simplified to only invoke the provided onResize closure on signal fire. The test support "ready" payload gains attachment_token: "attach-token" to satisfy the new resize RPC.
Integration test updates for workspace.remote.pty_resize
cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
Mock server switch statements across all SSH PTY attach scenarios are extended to handle workspace.remote.pty_resize, asserting attachment_token, surface_id presence/absence, and resize dimensions (132×43 in the terminal-size test) and returning resized: true. Expected RPC method sequences are updated to include workspace.remote.pty_resize. The resize-before-EOF test replaces its SIGWINCH polling loop with a direct resizeRequestReceived.wait(...). Several method-list assertions are reformatted from multi-line to single-line array literals.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • #5700 (Pane resizing is broken for TUIs within SSH sessions): The immediate sendCurrentSSHPTYResize() call after arming the SIGWINCH handler ensures the remote PTY size is reconciled at attach time, directly addressing the stale-size behavior described.
  • #6306: The centralized sendCurrentSSHPTYResize() helper and the initial reconciliation call after arming match the fix described for PTY resize staleness.
  • #6379: The refactored SIGWINCH handler with immediate reconciliation addresses the reported failure of remote PTY size to update after terminal resize in SSH sessions.

Suggested reviewers

  • lawrencecchen

🐇 A SIGWINCH once silent, now sings right away,
No stale PTY sizes to ruin your day!
sendCurrentSSHPTYResize() leaps into the fray,
Locking and reading and sending — hooray!
The rabbit approves of this tidy relay. 🎉


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error PR introduces new code accessing unisolated SocketClient (mutable reference class, not @MainActor/Sendable) from background DispatchQueue in sendCurrentSSHPTYResize closure. Mark SocketClient as @MainActor, mark Sendable with explicit safety explanation, or use weak capture like the existing vm-pty pattern (e.g., [weak self] in closure).
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main fix: reconciling remote PTY size after arming SIGWINCH to fix stale resizing in cmux ssh.
Linked Issues check ✅ Passed The PR directly addresses #5700 by fixing the lost-resize race: arming SIGWINCH before sampling size (root cause) and immediately pushing current size to reconcile missed resizes during attach.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the resize race: refactoring resize logic into a shared closure, updating tests to expect the initial resize, and adding attachment_token to test harness.
Cmux Swift Blocking Runtime ✅ Passed PR does not introduce new blocking runtime primitives. The NSLock usage is pre-existing (from parent commit), used around low-level socket communication (sendV2), minimal duration, and documented....
Cmux Expensive Synchronous Load ✅ Passed This PR adds no expensive synchronous loaders to main actor or interactive paths. The new sendCurrentSSHPTYResize() function only does lightweight ioctl(TIOCGWINSZ) terminal-size reads, not expen...
Cmux Cache Substitution Correctness ✅ Passed This PR does not involve cache substitution. It reads terminal size fresh from the OS (ioctl TIOCGWINSZ) each time in sendCurrentSSHPTYResize(), moving the read inside a lock for atomicity to fix a...
Cmux No Hacky Sleeps ✅ Passed PR changes only Swift files (CLI/cmux.swift, CMUXCLI+SSHCommandSupport.swift, test files). The rule explicitly states Swift timing is out of scope, covered by swift-blocking-runtime.md instead.
Cmux Algorithmic Complexity ✅ Passed Changes involve constant-time terminal size sampling via ioctl and one-off resize RPC sends; no nested collection scans, loops, or scalable algorithmic complexity introduced in production code.
Cmux Swift Concurrency ✅ Passed The PR's Swift concurrency usage is compliant. DispatchSourceSignal for SIGWINCH handling is an OS/Darwin callback boundary (explicitly allowed). The sendCurrentSSHPTYResize helper uses existing lo...
Cmux Swift @Concurrent ✅ Passed No @concurrent annotation violations found. The code properly isolates network-heavy I/O work with explicit DispatchQueue dispatch to background queues, introduces no async functions without @concu...
Cmux Swift File And Package Boundaries ✅ Passed The PR makes focused bug-fix changes that preserve clear file boundaries: CMU X.swift (+33 lines net) centralizes SIGWINCH resize logic, new CMUXCLI+SSHCommandSupport.swift (33 lines) cleanly extra...
Cmux Swiftpm Lockfiles ✅ Passed PR makes only code changes to CLI/cmux.swift and tests; no Package.swift, .gitignore, or Xcode project changes that would trigger SwiftPM lockfile policy rules.
Cmux Swift Logging ✅ Passed PR adds no logging statements (print, debugPrint, dump, NSLog) in production code. New functions sendCurrentSSHPTYResize and startSSHPTYResizeSource contain only RPC calls and signal handling. Test...
Cmux User-Facing Error Privacy ✅ Passed No user-facing error messages, alerts, or output added. All changes either internal RPC communication with generic parameters (cols/rows) or test code. Tokens sent only internally to app/daemon, no...
Cmux Full Internationalization ✅ Passed PR contains only code refactoring and test updates with no new or modified user-facing text, no string catalog additions, and no web i18n changes.
Cmux Swiftui State Layout ✅ Passed PR modifies CLI backend code and tests only; zero SwiftUI state management changes—no @Published, @Observable, @State, GeometryReader, or view state violations detected.
Cmux Architecture Rethink ✅ Passed Fix is required platform SIGWINCH bridge code with clear invariant (arm handler before reconcile send) and no prohibited timing patterns; uses lock for safe serialization, one synchronous reconcile...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR makes SIGWINCH resize handling and test updates; no NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup code added or changed. Test-only modifications are allowed per the rule.
Cmux Source Artifacts ✅ Passed All changed files are legitimate Swift source, test files, or dependency submodules with no prohibited artifacts (logs, screenshots, build output, caches, temp folders, etc.).
Description check ✅ Passed The PR description is comprehensive and well-structured, addressing the problem, root cause, fix, and verification. It follows the template's Summary and Testing sections effectively.
✨ 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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a lost-resize race in cmux ssh where any SIGWINCH delivered between the bridge handshake size capture and the DispatchSource arm was silently dropped, leaving the remote PTY frozen at its attach-time dimensions. The fix adds a single forced reconcile call (requestCurrentResize()) immediately after the monitor is armed, pushing the current terminal size through the same actor-serialized event pipeline used by live SIGWINCH events.

  • SSHPTYResizeMonitor: new nonisolated requestCurrentResize() yields a force: true resize event to the AsyncStream continuation; the bufferingNewest(1) buffer plus the post-send size re-check in drainPendingResizes() ensure a concurrent SIGWINCH always wins or is self-corrected in the next drain iteration.
  • cmux.swift: requestCurrentResize() is called after SSHPTYResizeMonitor init (which is after readSSHPTYBridgeReady), so it fires only when the bridge handshake has fully succeeded — early-failure paths are unaffected.
  • Tests: expected RPC sequences updated to include the initial workspace.remote.pty_resize for all bridge-ready scenarios; the SIGWINCH polling retry loop is replaced with a direct semaphore wait on the reconcile resize, and the test bridge now returns an attachment_token so token propagation is verified end-to-end.

Confidence Score: 5/5

Safe to merge — the change is a small, well-contained addition: one new nonisolated method and one call site, both consistent with the existing actor/AsyncStream design.

The reconcile write goes through the same actor-serialized event pipeline as live SIGWINCH events. The bufferingNewest(1) buffer and the post-send size re-check in drainPendingResizes() prevent stale sizes from persisting. The bridge-ready guard means early-failure paths are never affected. Tests cover the new initial resize across all attach scenarios and verify token propagation.

No files require special attention.

Important Files Changed

Filename Overview
CLI/SSHPTYResizeMonitor.swift Adds nonisolated func requestCurrentResize() that reads the current PTY size and yields it to the event stream with force: true; no actor isolation issues — AsyncStream.Continuation.yield is safe from any context and the bufferingNewest(1) buffer ensures the last-arriving size wins.
CLI/cmux.swift Calls resizeMonitor.requestCurrentResize() immediately after the monitor is initialized (post-bridge-ready) to reconcile any PTY size missed during the SIGWINCH-dead window; positioned correctly after readSSHPTYBridgeReady so it only fires when a monitor actually exists.
cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift Updated expected RPC sequences to include the new initial workspace.remote.pty_resize in scenarios where bridge ready is reached; pre-ready failure tests correctly retain the old two-element sequence; SIGWINCH polling loop replaced with a direct semaphore wait on the initial resize.
cmuxTests/CLINotifyProcessTestSupport.swift Bridge ready payload now includes attachment_token: attach-token so test assertions on the reconcile resize RPC can verify attachment_token propagation end-to-end.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GUI as Ghostty / SwiftUI
    participant CLI as ssh-pty-attach (CLI)
    participant Mon as SSHPTYResizeMonitor (actor)
    participant Daemon as cmuxd-remote

    GUI->>CLI: spawn ssh-pty-attach
    CLI->>Daemon: workspace.remote.pty_bridge (size S0)
    Daemon-->>CLI: bridge port + token
    CLI->>CLI: "TCP handshake (cols=S0, rows=S0)"
    Daemon-->>CLI: ready + attachment_token

    Note over GUI,CLI: SIGWINCH dead window (before PR fix, resize lost here)
    GUI-->>CLI: (optional) SIGWINCH S1 lost

    CLI->>Mon: init(initialSize: S0)
    Note over Mon: arms DispatchSource SIGWINCH handler
    CLI->>Mon: requestCurrentResize() [NEW]
    Mon->>Mon: yield(size: current, force: true)
    Mon->>Daemon: workspace.remote.pty_resize (S_current)

    loop live resizes
        GUI-->>CLI: SIGWINCH S2
        CLI->>Mon: yield(size: S2, force: true)
        Mon->>Daemon: workspace.remote.pty_resize (S2)
    end
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 GUI as Ghostty / SwiftUI
    participant CLI as ssh-pty-attach (CLI)
    participant Mon as SSHPTYResizeMonitor (actor)
    participant Daemon as cmuxd-remote

    GUI->>CLI: spawn ssh-pty-attach
    CLI->>Daemon: workspace.remote.pty_bridge (size S0)
    Daemon-->>CLI: bridge port + token
    CLI->>CLI: "TCP handshake (cols=S0, rows=S0)"
    Daemon-->>CLI: ready + attachment_token

    Note over GUI,CLI: SIGWINCH dead window (before PR fix, resize lost here)
    GUI-->>CLI: (optional) SIGWINCH S1 lost

    CLI->>Mon: init(initialSize: S0)
    Note over Mon: arms DispatchSource SIGWINCH handler
    CLI->>Mon: requestCurrentResize() [NEW]
    Mon->>Mon: yield(size: current, force: true)
    Mon->>Daemon: workspace.remote.pty_resize (S_current)

    loop live resizes
        GUI-->>CLI: SIGWINCH S2
        CLI->>Mon: yield(size: S2, force: true)
        Mon->>Daemon: workspace.remote.pty_resize (S2)
    end
Loading

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

Comment thread CLI/cmux.swift 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: 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 `@CLI/cmux.swift`:
- Around line 10930-10933: sendSSHPTYResize currently calls
currentCLITerminalSize() before acquiring socketLock which allows samples from
startSSHPTYResizeSource and the post-attach reconcile to race and be serialized
out-of-order; fix by moving the call to currentCLITerminalSize() so it occurs
while socketLock is held (inside the critical section that includes
socketLock.lock()/defer { socketLock.unlock() }) inside sendSSHPTYResize,
ensuring the sampled size used in the params dictionary is the one protected by
the lock.
🪄 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: 1154f33d-b206-4cb3-8440-7653856046ca

📥 Commits

Reviewing files that changed from the base of the PR and between aeb8847 and b586e9c.

📒 Files selected for processing (1)
  • CLI/cmux.swift

Comment thread CLI/cmux.swift Outdated
Address review: the ioctl(TIOCGWINSZ) was sampled before acquiring
socketLock, so the SIGWINCH handler and the post-attach reconcile could
sample different sizes and serialize only the sends — letting a stale
sample win the lock last and overwrite a fresher size on the daemon,
re-creating the frozen-PTY symptom. Move the sample inside the lock so
it protects both the read and the send.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@kylejcaron

kylejcaron commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Addressed in 0cfeda6. Moved the currentCLITerminalSize() sample inside socketLock so the lock now serializes both the read and the send. This closes the TOCTOU window where the SIGWINCH handler and the post-attach reconcile could sample different sizes and have only the sends serialized, letting a stale sample win the lock last and re-create the frozen-PTY symptom. Verified it still builds (tagged Debug).

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@kylejcaron: Great fix — moving the currentCLITerminalSize() sample inside socketLock is exactly the right correction. With both the read and the send under the same lock, the SIGWINCH handler and the post-attach reconcile are now fully atomic with respect to each other, eliminating the window where a stale sample could slip through last and re-freeze the PTY. Thanks for the thorough follow-through on this!

🐇 ✨

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

The fix in 0cfeda6 is correct and complete. Moving currentCLITerminalSize() inside socketLock closes the TOCTOU window cleanly — both the ioctl(TIOCGWINSZ) read and the send are now atomic with respect to any concurrent call, so the "last writer wins" ordering is also the "most-recent sample wins" ordering. The concern is fully addressed.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@vercel

vercel Bot commented Jun 19, 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 Jun 19, 2026 9:11pm

@austinywang
austinywang merged commit ed00998 into manaflow-ai:main Jun 19, 2026
22 checks passed
austinywang added a commit that referenced this pull request Jun 19, 2026
The #5989 merge added 7 lines to CLI/cmux.swift; regenerate the budget
so the length check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
austinywang added a commit that referenced this pull request Jun 19, 2026
* Add regression for Claude restore cwd drift

* Restore Claude agent hooks from launch cwd

* fix: prefer claude hook tty surface for resume binding

* fix: keep non-agent resume bindings unchanged when retargeting

* chore: refresh swift file length budget

* fix: keep invalid explicit claude surface from falling through

* fix: document restore cwd snapshot fallback

* test: cover mapped claude surface priority

* fix: keep mapped claude session surface authoritative

* test: move resume coverage to Swift Testing

* test: separate ambient claude hook surface

* test: keep claude hook session fixture current

* test: cover Claude hook leaked workspace routing

* fix: prefer Claude hook TTY routing before leaked env

* fix: return Claude hook test server semaphore

* fix: harden Claude hook restore routing

* fix: localize Claude hook surface errors

* fix: initialize shortcut test state before skip

* test: rely on main shortcut skip handling

* test: cover stale Claude TTY workspace binding

* fix: ignore stale Claude TTY workspace bindings

* test: cover zsh claude wrapper after user function override

* fix: restore Claude wrapper after zsh startup overrides

* fix: prefer Claude process binding for hook routing

* fix: isolate Claude hook process probe

* fix: connect Claude PID probe socket

* fix: authenticate Claude PID probe socket

* test: cover Claude resume self-heal for mismatched CLAUDE_CONFIG_DIR

When cmux is launched with a foreign CLAUDE_CONFIG_DIR (e.g. the .app is
opened from a terminal whose agent set one), a restored
`claude --resume <id>` resumes against the wrong config root and reports
"No conversation found", dropping the user to a bare shell (#6194).

This test fails until the wrapper self-heals CLAUDE_CONFIG_DIR to the
config root that actually holds the transcript.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: self-heal CLAUDE_CONFIG_DIR on Claude resume

A restored `claude --resume <id>` only resolves a session under the
current CLAUDE_CONFIG_DIR. When the cmux app inherits a foreign
CLAUDE_CONFIG_DIR (e.g. the .app is opened by cmd-clicking a link from a
terminal whose agent set one), it propagates that dir to every restored
pane, so sessions created under a different config root resume against
the wrong namespace and fail with "No conversation found" — leaving the
user at a bare shell instead of their conversation (#6194).

The wrapper now relocates CLAUDE_CONFIG_DIR to the config root that
actually holds the transcript when resuming an explicit session id, and
only when the current root lacks it (a correct resume is never
repointed). Session ids are filename-token validated before any
glob-walk.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: cover Claude prompt resume text parsing

* fix: stop Claude resume parsing at prompt text

* fix: harden Claude resume restore targeting

* fix: define Claude resume parser helper before passthrough

* fix: tolerate Claude value flags before resume

* test: cover Claude resume auth selection

* fix: preserve Claude resume auth selection

* fix: constrain Claude resume self-heal

* fix: bound Claude resume resolution

* fix: harden Claude resume wrapper resolution

* fix: route Claude hooks past stale tty bindings

* test: keep Claude resume expectations out of XCTest diff

* Add stale Claude shell wrapper regression

* Route Claude shell functions through shim resolver

* chore: refresh Swift file length budget after origin/main merge

The #5989 merge added 7 lines to CLI/cmux.swift; regenerate the budget
so the length check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — b84a0ee0 Deployed Jun 19, 2026 by vercel[bot]
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.

Pane resizing is broken for TUIs within ssh sessions

2 participants