Skip to content

Fix ssh PTY input loss and reordering at reconnect and backpressure seams - #7717

Merged
austinywang merged 8 commits into
mainfrom
issue-7708-ssh-input-ordering
Jul 9, 2026
Merged

austinywang merged 8 commits into
mainfrom
issue-7708-ssh-input-ordering

Conversation

@austinywang

@austinywang austinywang commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #7708

Problem

cmux ssh keystrokes could be garbled, reordered, or silently lost at reconnect and backpressure seams. The rendering-side bug was fixed in #6831; this PR closes the input direction, where pty.write had no sequencing, no acks, and no loss surfacing. Four root causes, all present on main:

  1. Reconnect input filter over-stripping (CLI/SSHPTYAttachReconnectInputFilter.swift): the post-reattach probe-reply filter window was unbounded until first output, so real ESC-prefixed keystrokes (arrows, alt-combos, bracketed paste) typed in that window could be eaten (prior bugs: Fix remote PTY restore probe reply leak #6070, Fix stale cmux ssh pane resize: reconcile remote PTY size after arming SIGWINCH #5989).
  2. Reattach seam drop/interleave (daemon/remote/cmd/cmuxd-remote/ws_pty.go): input chunks queued by a superseded attachment were silently dropped at reattach — bytes typed just before a disconnect vanished, and the seam was not quiesced.
  3. Silent overflow loss: the daemon atomically rejected a whole pty.write on queue overflow after the bytes were already consumed from local stdin, and the app-side RemotePTYBridgeSession closed the whole session on window overflow — accepted bytes lost either way.
  4. Structural enabler: pty.write was fire-and-forget — nothing could detect a gap or reorder.

Fix

Sequenced, cumulatively-acked pty.write with sender-side windowed flow control, capability-gated for mixed versions:

  • Wire protocol (all additive): daemon hello advertises pty.input.seq_ack; pty.attach takes optional input_seq_ack; pty.write takes optional seq (strictly last+1 per attachment, gaps rejected with wire-pinned pty_input_seq_gap → visible pty.error + daemon-side detach, never a silent write; a present-but-malformed seq is rejected with invalid_params); new coalesced cumulative pty.input_ack event emitted only to opted-in attachments after bytes hit the PTY fd, with out-of-range acks treated as a protocol violation by the app. Writes stay async notifications — the typing-latency win from 719a231 is preserved.
  • Seam ordering without drops: input a superseded attachment already had accepted stays queued and reaches the PTY ahead of anything the replacement enqueues — session.input is FIFO with writeInputLoop as its only consumer, whole writes enqueue atomically under inputEnqueueMu, and writeInputChunk no longer drops chunks whose attachment was replaced (the original loss bug). Reattach never waits on that drain, so a wedged PTY (stopped foreground process) cannot hang the attach path; the seam test asserts attach completes while the PTY writer is stalled.
  • Backpressure instead of loss: RemotePTYBridgeSession gets a queue-confined flow controller (RemotePTYBridgeInputFlow, new file) that pauses socket receives at the window (4 MiB / 256 writes), splits writes to ≤256 KiB so no RPC frame can exceed the daemon's 4 MiB limit, and resumes on drain — cumulative acks in seq_ack mode, write completions in legacy mode. No accepted byte is ever dropped and the session never closes on overflow, in either mode.
  • Bounded filter: the reconnect input filter self-disables at a monotonic deadline (injectable clock; poll timeout capped by the remaining deadline, EINTR retries anchored to an absolute deadline) and flushes pending bytes on every pump exit (EOF, read error, poll failure) — it can only strip exact probe-reply sequences, never prefix bytes of real key sequences.

Compatibility: the capability is optional (not part of the required handshake set). New app ↔ old daemon falls back to legacy completion-drained windowing; old app ↔ new daemon sees no enforcement and no acks; raw /terminal websocket clients never see ack frames.

Two-commit structure (regression policy)

  • Commit 1 (test:) adds only the two RED regression tests, written against main's APIs. Verified failing on main:
    • Go TestWebSocketPTYReattachWritesAcceptedOldInputBeforeNew: times out — "OLD" bytes dropped at the seam.
    • Swift legacy input overflow pauses instead of closing and preserves order: session closes, 4,182,004 of 5,242,880 bytes delivered.
  • Commit 2 (fix:) adds the fix plus the GREEN tests (seq enforcement, seq-gap → pty.error, cumulative/coalesced ack emission, acked-mode flow control, pty.error mid-stream teardown, filter deadline + seeded fuzz with keys before/between/after probe replies including lone ESC, capability/error-code pinning on both sides) and mechanically updates the RED tests to the new signatures.

Verification

  • cd daemon/remote && go test ./cmd/cmuxd-remote/ -count=1 ✅ (and go vet ./...; new tests also pass with -race; the one -race failure, TestPersistentDaemonPTYReattachSurvivesClientDisconnect, reproduces on unmodified main — pre-existing writer-lifetime race, not introduced here)
  • swift test --package-path Packages/macOS/CmuxRemoteWorkspace ✅ 51/51
  • swift test --package-path Packages/macOS/CmuxRemoteDaemon ✅ 20/20
  • python3 scripts/swift_file_length_budget.py: no TSV touched; all touched files at/under budget (RemotePTYBridgeSession.swift 479/506, filter 496/<500, WorkspaceRemoteConnectionTests.swift 7312/7312 line-neutral, cmux.swift untouched); remaining script failures are pre-existing files this PR does not modify
  • ./scripts/lint-pbxproj-test-wiring.sh ✅ (new SSHPTYAttachReconnectInputFilterPumpIO.swift wired into both targets)
  • Localization audit: no user-facing strings added — only wire identifiers (pty.input.seq_ack, pty_input_seq_gap, pty.input_ack); errors surface through the existing localized RemotePTYBridgeStrings path. No Localizable.xcstrings changes needed.

Related (not closed by this PR): #2969, #6082, #6821.

🤖 Generated with Claude Code


Note

High Risk
Touches live SSH stdin forwarding, persistent PTY reconnect semantics, and daemon input ordering—bugs could garble, reorder, or lose keystrokes or hang reattach.

Overview
Adds optional, capability-gated sequenced PTY input end-to-end: daemon advertises pty.input.seq_ack, attach can opt in with input_seq_ack, pty.write may carry monotonic seq (gaps → pty_input_seq_gap, visible pty.error, detach), and pty.input_ack reports cumulative progress after bytes hit the PTY. Swift RPC/bridge layers plumb inputAck, supportsInputSeqAck, and optional seq on writes.

On the daemon, reattach no longer drops input already accepted from a superseded attachment—writeInputChunk is session-scoped so queued FIFO input still reaches the PTY (without blocking reattach on a wedged writer). App-side RemotePTYBridgeInputFlow replaces naive pending-write counters with windowed flow control: pause socket reads at the cap, split large payloads, resume on write completion (legacy) or cumulative acks (seq mode).

SSH reconnect stdin filtering gets a 2s monotonic deadline (poll timeouts capped; EINTR-safe absolute deadlines in extracted pump I/O), flushes pending bytes on pump exit, and F_SETNOSIGPIPE on stop pipes.

Regression tests cover seam ordering, seq enforcement/acks, overflow pause vs close, filter deadline/fuzz, and capability pinning.

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

…essure seams

Two deterministic reproductions of #7708
(garbled / out-of-order / lost keystrokes over cmux ssh). Both fail on main
by design; the fix lands in the next commit.

- TestWebSocketPTYReattachWritesAcceptedOldInputBeforeNew: input accepted by
  a superseded attachment must reach the PTY, in order, before input from the
  replacement attachment. On main the replaced-attachment check in
  writeInputChunk silently drops the queued bytes (times out reading OLDNEW).
- "legacy input overflow pauses instead of closing and preserves order":
  input-window overflow in RemotePTYBridgeSession must backpressure the
  socket, not close the session. On main the session close(detach:)s and
  ~1MiB of accepted bytes are lost (delivered 4182004 of 5242880).

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

vercel Bot commented Jul 9, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jul 9, 2026 1:17pm
cmux-staging Building Building Preview, Comment Jul 9, 2026 1:17pm

@coderabbitai

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

Adds deadline-aware reconnect filtering for SSH PTY stdin and extends PTY input with seq-tagged writes, acknowledgements, and gap reporting across the daemon, Swift bridge, and workspace session flow.

Changes

SSH reconnect input filter deadline fix

Layer / File(s) Summary
Deadline-aware filter state
CLI/SSHPTYAttachReconnectInputFilterState.swift, CLI/SSHPTYAttachReconnectInputFilter.swift
Adds deadline-related closures and computed deadline checks to the reconnect input filter state and implementation.
Stdin pump deadline shutdown
CLI/SSHPTYAttachReconnectInputFilter.swift
Computes a reconnect deadline, adjusts pump polling, stops stripping at deadline, and flushes pending bytes before shutdown on poll and read termination paths.
Pump IO helper extraction
CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift, cmux.xcodeproj/project.pbxproj
Moves write and poll helpers into a new pump IO file and registers that file in the Xcode project.
Deadline and fuzz tests
cmuxTests/SSHPTYAttachReconnectInputFilterTests.swift
Adds deadline-flush coverage and deterministic fuzz coverage for the reconnect input filter.

Sequenced, acknowledged PTY input protocol

Layer / File(s) Summary
Seq-ack contracts and events
Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Capabilities/RemoteDaemonCapability.swift, Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonPTYEvent.swift, Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/PTYBridge/RemotePTYBridgeEvent.swift, Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/PTYBridge/RemotePTYBridgeRPCClient.swift, Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient.swift, Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+Events.swift, Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+RPC.swift, Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+RemotePTYBridgeRPCClient.swift, Packages/macOS/CmuxRemoteDaemon/Tests/CmuxRemoteDaemonTests/RemoteDaemonRPCClientCapabilityTests.swift, Packages/macOS/CmuxRemoteDaemon/Tests/CmuxRemoteDaemonTests/RemoteDaemonStringsTests.swift
Adds the seq-ack capability, input-ack event cases, seq-aware attach/write RPC parameters, capability tracking, and wire-value tests.
Workspace input flow and bridge wiring
Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeInputFlow.swift, Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession+Input.swift, Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession.swift, Packages/macOS/CmuxRemoteWorkspace/Tests/CmuxRemoteWorkspaceTests/RemotePTYBridgeServerTests.swift, cmuxTests/WorkspaceRemoteConnectionTests.swift
Adds queued input flow control, ack handling, seq-aware session wiring, and updated bridge mocks for seq-tagged writes and events.
Daemon seq validation and ack emission
daemon/remote/cmd/cmuxd-remote/main.go, daemon/remote/cmd/cmuxd-remote/main_test.go, daemon/remote/cmd/cmuxd-remote/ws_pty.go, daemon/remote/cmd/cmuxd-remote/ws_pty_test.go
Extends the daemon RPC surface and websocket PTY hub to accept seq-tagged writes, validate gaps, emit input acknowledgements, and surface gap errors.

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

Sequence Diagram(s)

sequenceDiagram
  participant RemotePTYBridgeServer.Session
  participant RemotePTYBridgeRPCClient
  participant RemoteDaemonRPCClient
  participant cmuxd-remote

  RemotePTYBridgeServer.Session->>RemotePTYBridgeRPCClient: supportsInputSeqAck
  RemotePTYBridgeServer.Session->>RemoteDaemonRPCClient: attachPTY(inputSeqAck)
  RemoteDaemonRPCClient->>cmuxd-remote: pty.attach {input_seq_ack}
  RemotePTYBridgeServer.Session->>RemoteDaemonRPCClient: writePTY(data, seq)
  RemoteDaemonRPCClient->>cmuxd-remote: pty.write {seq}
  cmuxd-remote-->>RemoteDaemonRPCClient: pty.input_ack {seq}
  RemoteDaemonRPCClient-->>RemotePTYBridgeServer.Session: .inputAck(seq)
Loading
sequenceDiagram
  participant RemotePTYBridgeServer.Session
  participant RemotePTYBridgeInputFlow
  participant RemoteDaemonRPCClient
  participant wsPTYHub

  RemotePTYBridgeServer.Session->>RemotePTYBridgeInputFlow: enqueue(data)
  RemotePTYBridgeInputFlow-->>RemotePTYBridgeServer.Session: write batch or buffer
  RemotePTYBridgeServer.Session->>RemoteDaemonRPCClient: writePTY(data, seq)
  RemoteDaemonRPCClient->>wsPTYHub: pty.write {seq}
  wsPTYHub->>wsPTYHub: validate contiguous seq
  wsPTYHub-->>RemoteDaemonRPCClient: pty.input_ack {seq}
  RemoteDaemonRPCClient-->>RemotePTYBridgeServer.Session: handleInputAck(seq)
  RemotePTYBridgeServer.Session->>RemotePTYBridgeInputFlow: acknowledge(upTo: seq)
Loading

Suggested reviewers: lawrencecchen

🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers the change and testing, but it does not follow the required template sections for Summary, Demo Video, Review Trigger, or Checklist. Reformat the description to match the template and add the missing Summary, Testing, Demo Video (or N/A), Review Trigger, and Checklist sections.
✅ Passed checks (23 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses the linked goals with bounded reconnect filtering, sequenced/acked pty.write, seam ordering, backpressure, and tests.
Out of Scope Changes check ✅ Passed No clearly unrelated changes stand out; the project wiring and ignore rule appear ancillary to the PTY input fix.
Cmux Swift Actor Isolation ✅ Passed No new MainActor/UI isolation issues; added mutable PTY input state remains queue-confined and the new Sendable APIs stay callback-based.
Cmux Swift Blocking Runtime ✅ Passed New clock-based timeout is a bounded deadline abstraction, and the added queue.sync/polling stays in low-level bridge/legacy code; no material new blocking primitive.
Cmux Browser Automation Off-Main ✅ Passed PASS: The PR only changes PTY/reconnect/daemon files; no browser-automation files or browser.* socket commands appear in the diff, so the off-main WebKit-waits rule isn't implicated.
Cmux Expensive Synchronous Load ✅ Passed No synchronous agent-history/file loads or MainActor interactive loads were added; touched Swift code is PTY/network plumbing, not session-history access.
Cmux Cache Substitution Correctness ✅ Passed No persistence/history/snapshot path swaps a fresh authoritative read for a cache; the only cache is transient capability gating, populated by hello and cleared on transport open.
Cmux No Hacky Sleeps ✅ Passed PASS: The only production Go change adds seq-ack cleanup/dropAttachment handling; no new fixed sleeps, delayed dispatch, or polling were introduced. Sleep/timeout code is test-only.
Cmux Algorithmic Complexity ✅ Passed PASS: New scans are bounded by explicit caps (256-window input queue, 512-byte probe buffer, 256KiB stdout buffer); no nested scalable rescans were added.
Cmux Swift Concurrency ✅ Passed PASS: The PR only extends existing queue-confined RPC/Network boundaries; no new Combine or unmanaged Tasks were introduced, and the detached stdin pump task was preexisting.
Cmux Swift @Concurrent ✅ Passed No new @concurrent or nonisolated-async misuse in touched Swift files; the new async pump is offloaded with Task.detached and queue confinement.
Cmux Swift File And Package Boundaries ✅ Passed The new logic is either small CLI-specific POSIX glue or lives in package targets; no new oversized production Swift file or clear package-boundary violation was introduced.
Cmux Swiftpm Lockfiles ✅ Passed No changed Package.resolved; daemon/remote/.gitignore only ignores /cmuxd-remote, and the Xcode edit is source-file wiring, not SwiftPM package refs.
Cmux Swift Logging ✅ Passed No touched production Swift file adds print/debugPrint/dump/NSLog or Logger-based logging; no MainActor-coupled Logger constants found.
Cmux User-Facing Error Privacy ✅ Passed New user-facing errors are generic PTY diagnostics (seq gap, queue full, invalid params) and don't expose vendor names, secrets, or raw payloads.
Cmux Full Internationalization ✅ Passed No new user-facing text was added; the commit only changes backend flow control, protocol tokens, and tests, with no catalog edits.
Cmux Swiftui State Layout ✅ Passed PR diff is non-SwiftUI: changed files contain no ObservableObject/@Published/GeometryReader/LazyVStack row-store patterns or render-time state writes.
Cmux Architecture Rethink ✅ Passed PASS: state ownership stays clear (Session/InputFlow/RPC client), and the new polling/deadline code is bounded bridge logic, not a split-owner symptom patch.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No standalone window code changed; the Swift edits are PTY/input plumbing and tests, with no NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Only source/test files changed (ws_pty.go, ws_pty_test.go); no logs, caches, build output, scratch dirs, or copied artifacts were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Touched production Swift code adds production input-flow logic only; no #if DEBUG/test-hook members, and widened Session methods are used by same-module production code, not tests.
Cmux No Ambient Global State ✅ Passed All new runtime behavior is instance-scoped; the diff adds only type-scoped helpers/constants, with no new globals or singletons.
Title check ✅ Passed The title clearly matches the PR’s main fix: preventing SSH PTY input loss and reordering during reconnect and backpressure.
✨ 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-7708-ssh-input-ordering

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.

Comment thread daemon/remote/cmd/cmuxd-remote/ws_pty.go

@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 using default effort and found 1 potential issue.

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 b31099b. Configure here.

Comment thread daemon/remote/cmd/cmuxd-remote/ws_pty.go 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 `@CLI/SSHPTYAttachReconnectInputFilter.swift`:
- Around line 201-203: The stop-request branch in
SSHPTYAttachReconnectInputFilter still uses the full pending-probe wait and can
outlive reconnectInputFilter.remainingDeadlineMilliseconds. Update the secondary
pending-input wait in SSHPTYAttachReconnectInputFilter to cap its timeout the
same way the main poll does, using the reconnect deadline and
pendingProbeContinuationTimeoutMilliseconds together. Use the existing
timeout/remainingDeadline logic around reconnectInputFilter,
pendingProbeContinuationTimeoutMilliseconds, and pending bytes handling so the
stop acknowledgement cannot block past the reconnect boundary.

In `@CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift`:
- Around line 36-50: The retry loop in pollStdinPump currently reuses the same
relative timeout after EINTR, which can extend the reconnect window
unexpectedly. Update pollStdinPump to work from an absolute deadline and
recompute the remaining timeout before each Darwin.poll call, so repeated signal
interruptions do not keep the reconnect filter active past the intended cutoff.
Keep the fix localized to pollStdinPump and its timeout calculation path.

In `@daemon/remote/cmd/cmuxd-remote/main.go`:
- Around line 2067-2072: The seq parsing in the request handling logic should
reject present-but-invalid values instead of silently treating them as missing.
Update the seq extraction path around getIntParam in the attachment request flow
so that if the "seq" parameter is present but negative or malformed, the handler
returns invalid_params rather than leaving hasSeq false. Preserve the current
valid behavior for accepted seq values, and make sure the logic in the
seq-ack/legacy attachment handling branches uses the presence of seq to
distinguish absent from invalid input.

In
`@Packages/macOS/CmuxRemoteWorkspace/Tests/CmuxRemoteWorkspaceTests/RemotePTYBridgeServerTests.swift`:
- Line 387: The test currently uses a fixed usleep in
RemotePTYBridgeServerTests, which makes the “no unacked drain” assertion
timing-dependent. Replace that sleep with a causal wait in the relevant test
flow by waiting on a real predicate or completion signal that the input window
has filled before asserting the byte cap and only then emitting ACKs. Use the
existing test helpers and symbols around the assertion site in
RemotePTYBridgeServerTests to gate on readiness instead of wall-clock delay.
🪄 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: 3bd811c5-a6ab-40d3-8755-59e577f05271

📥 Commits

Reviewing files that changed from the base of the PR and between 986c25d and 5666413.

📒 Files selected for processing (25)
  • CLI/SSHPTYAttachReconnectInputFilter.swift
  • CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift
  • CLI/SSHPTYAttachReconnectInputFilterState.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Capabilities/RemoteDaemonCapability.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonPTYEvent.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+Events.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+RPC.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+RemotePTYBridgeRPCClient.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/PTYBridge/RemotePTYBridgeEvent.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/PTYBridge/RemotePTYBridgeRPCClient.swift
  • Packages/macOS/CmuxRemoteDaemon/Tests/CmuxRemoteDaemonTests/RemoteDaemonRPCClientCapabilityTests.swift
  • Packages/macOS/CmuxRemoteDaemon/Tests/CmuxRemoteDaemonTests/RemoteDaemonStringsTests.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeInputFlow.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession+Input.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession.swift
  • Packages/macOS/CmuxRemoteWorkspace/Tests/CmuxRemoteWorkspaceTests/RemotePTYBridgeServerTests.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/SSHPTYAttachReconnectInputFilterTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift
  • daemon/remote/cmd/cmuxd-remote/main.go
  • daemon/remote/cmd/cmuxd-remote/main_test.go
  • daemon/remote/cmd/cmuxd-remote/ws_pty.go
  • daemon/remote/cmd/cmuxd-remote/ws_pty_test.go
  • daemon/remote/cmuxd-remote

Comment thread CLI/SSHPTYAttachReconnectInputFilter.swift
Comment thread CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift
Comment thread daemon/remote/cmd/cmuxd-remote/main.go
@greptile-apps

greptile-apps Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes the input direction of SSH PTY data loss/reordering by adding optional sequenced, cumulatively-acked pty.write end-to-end, with capability-gated mixed-version fallback. Four root causes are addressed: unbounded reconnect filter window, reattach seam drop, silent overflow-triggered session closure, and lack of sequencing on pty.write.

  • Go daemon (ws_pty.go): Adds pty.input.seq_ack capability; pty.write accepts an optional strictly-increasing seq validated atomically under per-session inputEnqueueMu; superseded-attachment chunks stay in the FIFO and drain before new-attachment writes; pty.input_ack coalesces acks back to opted-in attachments; saturated ack queues detach rather than block.
  • App bridge (RemotePTYBridgeInputFlow, RemotePTYBridgeSession+Input): New queue-confined flow controller pauses NWConnection reads at a 4 MiB / 256-write window, splits payloads to ≤256 KiB to stay under the RPC frame limit, and resumes on cumulative acks (seq-ack mode) or write completions (legacy); invalid acks tear down the session instead of continuing silently.
  • CLI reconnect filter: Probe-reply stripping self-disables at a 2 s monotonic deadline; poll EINTR retries are anchored to an absolute deadline; pending bytes flush on every exit path (EOF, read error, poll failure, deadline expiry).

Confidence Score: 4/5

The change is safe to merge for the vast majority of users; the new seq-ack path is capability-gated and the mixed-version fallback is correct. The two previously-flagged concurrent-write concerns appear to be resolved: inputEnqueueMu serialises every writeInput call for the same session end-to-end, and flushAcceptedInput no longer exists.

The core PTY input paths have been substantially rewritten across the Go daemon, Swift bridge session, and CLI reconnect filter. The seq-ack mechanism is new wire protocol with real-time keystroke implications. The changes are well-tested and the key concurrency invariant (inputEnqueueMu scope) is sound, but the cross-language protocol logic and three-layer change warrant an extra pair of eyes before merge. No blocking defects were found in the new code.

daemon/remote/cmd/cmuxd-remote/ws_pty.go — the lastAcceptedSeq two-lock pattern and its dependency on inputEnqueueMu scope deserves close reading; see inline comment. Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeInputFlow.swift — the local-buffer nil-return path versus the daemon-window backpressure path should be documented more precisely.

Important Files Changed

Filename Overview
daemon/remote/cmd/cmuxd-remote/ws_pty.go Core seq-ack implementation: inputEnqueueMu serializes seq validation + lastAcceptedSeq update + channel sends atomically; writeInputChunk intentionally drops the attachment-currency check so superseded bytes still reach the PTY; enqueueInputAck coalesces acks; the two previously-flagged TOCTOU concerns appear to be addressed by the inputEnqueueMu design.
daemon/remote/cmd/cmuxd-remote/main.go Adds pty.input.seq_ack to the advertised capability set; seq field parsing with float64/json.Number/int64 coverage via getIntParam; pty_input_seq_gap triggers the same detach path as pty_input_queue_full.
Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeInputFlow.swift New queue-confined flow controller. Correctly splits writes, reserves window slots, buffers overflow, and flushes on ack (seq-ack) or completion (legacy). drainCompletedWrite uses O(n) Array.remove but maxPendingWrites=256 keeps this bounded. The nil-return-closes-session path is a safety net that fires only after the 4 MiB app-side buffer is also exhausted.
Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession+Input.swift Correctly factors input forwarding into its own extension; sendInputWrite dispatches to rpcQueue then returns to queue, maintaining queue-confined invariant for inputFlow; invalid acks close the session via handleInputAck → acknowledge nil guard.
Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession.swift inputFlow.isPaused correctly gates receiveNext() calls; private members widened to internal are a necessary consequence of splitting input logic into a same-module extension file, not a test seam. inputSeqAckEnabled is set once at init and passed consistently to both attach and the flow controller.
CLI/SSHPTYAttachReconnectInputFilter.swift Deadline correctly anchored at probe start; remainingDeadlineMilliseconds caps the poll timeout; isDeadlineReached checked at loop head before timeout calculation; flushPendingThenShutdown called on every exit path (poll failure, EINTR limit, EOF, error, deadline expiry); F_SETNOSIGPIPE set on pipe write ends to prevent SIGPIPE.
CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift pollStdinPump EINTR retry now anchors to an absolute monotonic deadline, preventing signal storms from stretching the reconnect window. writeAll unchanged. Visibility widened from private static to internal static solely because the callers moved to a different file in the same target.
daemon/remote/.gitignore Adds /cmuxd-remote to prevent the compiled Go binary from being tracked. If the binary was committed in a prior PR, it remains in git history and a git filter-branch or BFG run would be needed to fully expunge it.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App as App (RemotePTYBridgeSession)
    participant Flow as RemotePTYBridgeInputFlow
    participant Daemon as cmuxd-remote (ws_pty.go)

    Note over App,Daemon: Capability handshake
    Daemon-->>App: "hello {capabilities: [pty.input.seq_ack, ...]}"
    App->>Daemon: "pty.attach {input_seq_ack: true}"

    Note over App,Daemon: Seq-ack mode: window-controlled writes
    App->>Flow: enqueue(keystroke data)
    Flow-->>App: "DrainResult{writes:[{data, seq:1}]}"
    App->>Daemon: "pty.write {seq:1, data_base64:...}"
    Note right of Daemon: validated under inputEnqueueMu, written to PTY fd
    Daemon-->>App: "pty.input_ack {seq:1} (coalesced)"
    App->>Flow: acknowledge(upTo: 1)
    Flow-->>App: "DrainResult{shouldResumeReads: true}"
    App->>App: receiveNext() — resume NWConnection reads

    Note over App,Daemon: Seq gap → visible error + detach
    App->>Daemon: "pty.write {seq:3} — gap!"
    Daemon-->>App: "error {code: pty_input_seq_gap}"
    Daemon-->>App: pty.error + detach

    Note over App,Daemon: Reconnect seam ordering
    Note left of App: OLD attachment bytes stay queued in FIFO
    App->>Daemon: pty.attach (new attachment)
    Note right of Daemon: OLD bytes drain first, then NEW bytes follow
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 App as App (RemotePTYBridgeSession)
    participant Flow as RemotePTYBridgeInputFlow
    participant Daemon as cmuxd-remote (ws_pty.go)

    Note over App,Daemon: Capability handshake
    Daemon-->>App: "hello {capabilities: [pty.input.seq_ack, ...]}"
    App->>Daemon: "pty.attach {input_seq_ack: true}"

    Note over App,Daemon: Seq-ack mode: window-controlled writes
    App->>Flow: enqueue(keystroke data)
    Flow-->>App: "DrainResult{writes:[{data, seq:1}]}"
    App->>Daemon: "pty.write {seq:1, data_base64:...}"
    Note right of Daemon: validated under inputEnqueueMu, written to PTY fd
    Daemon-->>App: "pty.input_ack {seq:1} (coalesced)"
    App->>Flow: acknowledge(upTo: 1)
    Flow-->>App: "DrainResult{shouldResumeReads: true}"
    App->>App: receiveNext() — resume NWConnection reads

    Note over App,Daemon: Seq gap → visible error + detach
    App->>Daemon: "pty.write {seq:3} — gap!"
    Daemon-->>App: "error {code: pty_input_seq_gap}"
    Daemon-->>App: pty.error + detach

    Note over App,Daemon: Reconnect seam ordering
    Note left of App: OLD attachment bytes stay queued in FIFO
    App->>Daemon: pty.attach (new attachment)
    Note right of Daemon: OLD bytes drain first, then NEW bytes follow
Loading

Reviews (5): Last reviewed commit: "review: cmux policy cleanups — file-scop..." | Re-trigger Greptile

#7708)

Closes the four input-path holes behind garbled / out-of-order keystrokes
over cmux ssh at reconnect and backpressure seams:

- pty.write now carries an optional per-attachment monotonic seq
  (capability "pty.input.seq_ack", opt-in via pty.attach input_seq_ack).
  The daemon rejects gaps with the wire-pinned rpc error
  pty_input_seq_gap, which surfaces as a visible pty.error instead of
  silently writing whatever arrives. Cumulative, coalesced
  pty.input_ack events flow back after bytes hit the PTY fd. Writes stay
  async notifications, so the typing-latency win from 719a231 is kept.
- Reattach seam is quiesced: superseding an attachment drains every
  already-accepted input chunk to the PTY through the single input-loop
  consumer (flush-barrier sentinel under inputEnqueueMu) before the
  replacement may enqueue, and the supersede slot is re-checked after the
  barrier so a concurrent attach for the same id is superseded too, never
  silently overwritten. Old and new bytes can no longer interleave or drop.
- RemotePTYBridgeSession no longer close(detach:)s on input-window
  overflow: a queue-confined flow controller (RemotePTYBridgeInputFlow)
  pauses socket receives at the window and resumes on drain — acks in
  seq_ack mode, write completions in legacy mode — so accepted bytes are
  never dropped in either mode.
- The reconnect input filter is bounded by a monotonic deadline (injectable
  clock, poll timeout capped by the remaining deadline) and flushes pending
  bytes on every pump exit (EOF, read error, poll failure), so it can never
  eat real ESC-prefixed keystrokes indefinitely; the fuzz test interleaves
  keys before/between/after probe replies, including a lone ESC.

Mixed versions stay compatible: the capability is optional (never part of
the required handshake set), old daemons ignore the attach param and seq,
and old apps see no acks and no enforcement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@austinywang
austinywang force-pushed the issue-7708-ssh-input-ordering branch from b31099b to d71c376 Compare July 9, 2026 09:08

@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

♻️ Duplicate comments (2)
CLI/SSHPTYAttachReconnectInputFilter.swift (1)

217-222: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Cap the secondary pending-input wait by the reconnect deadline.

Line 220 still waits the full pendingProbeContinuationTimeoutMilliseconds after a stop request. If the reconnect deadline is sooner, pending bytes and the stop acknowledgement can still be delayed past the cutoff.

Proposed fix
+                let pendingTimeoutMilliseconds: Int32
+                if let remaining = reconnectInputFilter?.remainingDeadlineMilliseconds {
+                    let cappedRemaining = Int32(min(Int64(Int32.max), max(Int64(0), remaining)))
+                    pendingTimeoutMilliseconds = min(
+                        pendingProbeContinuationTimeoutMilliseconds,
+                        cappedRemaining
+                    )
+                } else {
+                    pendingTimeoutMilliseconds = pendingProbeContinuationTimeoutMilliseconds
+                }
                 guard let pendingReadiness = pollStdinPump(
                     inputFD: inputFD,
                     stopSignalFD: nil,
-                    timeoutMilliseconds: pendingProbeContinuationTimeoutMilliseconds
+                    timeoutMilliseconds: pendingTimeoutMilliseconds
                 ) else {

As per coding guidelines, “In cmux-sensitive Swift paths such as typing, terminal rendering, socket telemetry, and focus handling, blocking or sleep-based coordination should fail CI.”

🤖 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 `@CLI/SSHPTYAttachReconnectInputFilter.swift` around lines 217 - 222, The
secondary pending-input wait in SSHPTYAttachReconnectInputFilter should not
exceed the reconnect deadline, since pollStdinPump can currently block longer
than allowed after a stop request. Update the logic around the pendingReadiness
handling to compute the remaining time until the reconnect cutoff and pass that
capped timeout instead of always using
pendingProbeContinuationTimeoutMilliseconds, while preserving the
flushPendingThenShutdown path when no readiness is returned.

Sources: Coding guidelines, Path instructions

CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift (1)

36-50: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the absolute timeout across EINTR retries.

pollStdinPump retries poll with the original relative timeout, so repeated signals can extend the reconnect-filter window and keep stripping ESC-prefixed input past the intended cutoff. Compute an absolute end time once, then recompute the remaining timeout before each retry.

🤖 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 `@CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift` around lines 36 - 50, The
poll retry loop in SSHPTYAttachReconnectInputFilterPumpIO’s polling helper is
reusing the original relative timeout after EINTR, which can unintentionally
extend the reconnect-filter window. Update the polling logic in pollStdinPump
(or the surrounding poll loop) to compute a single absolute deadline before the
first poll, then derive the remaining timeout on each retry after EINTR so the
total wait time stays bounded. Keep the existing inputReady/stopRequested
behavior unchanged while only adjusting how timeoutMilliseconds is calculated
across retries.

Sources: Coding guidelines, Path instructions

🤖 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
`@Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeInputFlow.swift`:
- Around line 68-79: `acknowledge(upTo:)` in `RemotePTYBridgeInputFlow` always
returns a `DrainResult`, so invalid or stale ack values are currently accepted
and the nil-check in `RemotePTYBridgeSession+Input` is unreachable. Add seq
validation inside `acknowledge(upTo:)` against the highest sent/pending sequence
(using the existing `pendingWrites`, `seqAckEnabled`, and `flushBufferedInput`
flow), and return nil or otherwise signal failure for out-of-range
acknowledgements so the caller’s protocol-error handling can trigger.

---

Duplicate comments:
In `@CLI/SSHPTYAttachReconnectInputFilter.swift`:
- Around line 217-222: The secondary pending-input wait in
SSHPTYAttachReconnectInputFilter should not exceed the reconnect deadline, since
pollStdinPump can currently block longer than allowed after a stop request.
Update the logic around the pendingReadiness handling to compute the remaining
time until the reconnect cutoff and pass that capped timeout instead of always
using pendingProbeContinuationTimeoutMilliseconds, while preserving the
flushPendingThenShutdown path when no readiness is returned.

In `@CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift`:
- Around line 36-50: The poll retry loop in
SSHPTYAttachReconnectInputFilterPumpIO’s polling helper is reusing the original
relative timeout after EINTR, which can unintentionally extend the
reconnect-filter window. Update the polling logic in pollStdinPump (or the
surrounding poll loop) to compute a single absolute deadline before the first
poll, then derive the remaining timeout on each retry after EINTR so the total
wait time stays bounded. Keep the existing inputReady/stopRequested behavior
unchanged while only adjusting how timeoutMilliseconds is calculated across
retries.
🪄 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: 8745523a-24f7-432e-8854-28e83dcf27d0

📥 Commits

Reviewing files that changed from the base of the PR and between 5666413 and d71c376.

📒 Files selected for processing (25)
  • CLI/SSHPTYAttachReconnectInputFilter.swift
  • CLI/SSHPTYAttachReconnectInputFilterPumpIO.swift
  • CLI/SSHPTYAttachReconnectInputFilterState.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Capabilities/RemoteDaemonCapability.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonPTYEvent.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+Events.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+RPC.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient+RemotePTYBridgeRPCClient.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/Client/RemoteDaemonRPCClient.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/PTYBridge/RemotePTYBridgeEvent.swift
  • Packages/macOS/CmuxRemoteDaemon/Sources/CmuxRemoteDaemon/PTYBridge/RemotePTYBridgeRPCClient.swift
  • Packages/macOS/CmuxRemoteDaemon/Tests/CmuxRemoteDaemonTests/RemoteDaemonRPCClientCapabilityTests.swift
  • Packages/macOS/CmuxRemoteDaemon/Tests/CmuxRemoteDaemonTests/RemoteDaemonStringsTests.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeInputFlow.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession+Input.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession.swift
  • Packages/macOS/CmuxRemoteWorkspace/Tests/CmuxRemoteWorkspaceTests/RemotePTYBridgeServerTests.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/SSHPTYAttachReconnectInputFilterTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift
  • daemon/remote/.gitignore
  • daemon/remote/cmd/cmuxd-remote/main.go
  • daemon/remote/cmd/cmuxd-remote/main_test.go
  • daemon/remote/cmd/cmuxd-remote/ws_pty.go
  • daemon/remote/cmd/cmuxd-remote/ws_pty_test.go

Addresses PR #7717 review feedback (CodeRabbit):

- pollStdinPump anchors its timeout to an absolute monotonic deadline so
  EINTR retries cannot extend the reconnect-filter window under repeated
  signal delivery.
- pty.write with a present-but-malformed seq (non-integer or negative) is
  rejected with invalid_params instead of being treated as absent, which
  produced a misleading 'got 0' gap for seq-ack attachments and silently
  accepted malformed input for legacy ones.
- RemotePTYBridgeInputFlow.acknowledge(upTo:) rejects acks for seqs that
  were never sent, making the session's protocol-violation teardown branch
  reachable instead of trusting a malformed daemon ack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@austinywang
austinywang enabled auto-merge (squash) July 9, 2026 09:41
Addresses two structured-review findings on #7717:

- prepareAttachment no longer waits on a flush barrier before registering a
  replacement attachment. The barrier could block the pty.attach RPC
  indefinitely behind a wedged PTY (stopped foreground process + large
  paste), holding the attach path hostage. Ordering does not need it:
  session.input is FIFO with writeInputLoop as its only consumer, whole
  writes enqueue atomically under inputEnqueueMu, and writeInputChunk does
  not require the chunk's attachment to still be registered — so accepted
  old-attachment bytes reach the PTY ahead of the replacement's input by
  construction. The reattach-seam test now also asserts attach completes
  while the PTY writer is stalled.
- A pty_input_seq_gap notification error now detaches the attachment
  daemon-side (like pty_input_queue_full) instead of leaving a stale
  attachment registered and streaming ignored output after the client
  closed.

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

@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 `@daemon/remote/cmd/cmuxd-remote/ws_pty.go`:
- Around line 828-835: Reword the supersession comment in ws_pty.go so it
clearly documents the OLD→NEW ordering contract without the garbled phrasing.
Keep the explanation tied to the attachment reattach path and the
writeInputLoop/inputEnqueueMu/writeInputChunk behavior: old attachment input
already accepted must remain queued and reach the PTY before any replacement
input, and reattach must never block waiting for a drain. Make the wording
concise and grammatical while preserving the concurrency invariant.
🪄 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: 1d9960bf-6b75-46a5-bc49-4f83d45f6381

📥 Commits

Reviewing files that changed from the base of the PR and between fe00eb3 and 03c1b53.

📒 Files selected for processing (3)
  • daemon/remote/cmd/cmuxd-remote/main.go
  • daemon/remote/cmd/cmuxd-remote/ws_pty.go
  • daemon/remote/cmd/cmuxd-remote/ws_pty_test.go

Comment thread daemon/remote/cmd/cmuxd-remote/ws_pty.go
austinywang and others added 4 commits July 9, 2026 03:02
A saturated seq-ack attachment's enqueueInputAck cancels the attachment but
left it registered in session.attachments, blocking idle-reaping and
leaving stale size/input state under the old token. Mirror the output
enqueue path: dropAttachment on ack-queue failure, outside ptyWriteMu
(dropAttachment can resize via applyCurrentPTYSize, which takes it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A pre-attach stdin buffer near the 4 MiB input window became a single
pty.write whose base64 payload exceeded the daemon's 4 MiB RPC frame
limit; the daemon rejects such frames before parsing attachment identity,
so in seq-ack mode the write was never acked and the input window stayed
full forever, silently freezing terminal input. RemotePTYBridgeInputFlow
now splits enqueued data into <=256 KiB writes (own seq each, buffered
pieces stay ordered across the window boundary).

Also documents why writeInputChunk is deliberately session-scoped rather
than attachment-scoped: input accepted before a detach still executes
(persistent-session semantics); discarding it is the silent-loss bug
class this PR removes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deadline stop path (and any pump exit) closes the stop-signal pipe
read end while the output-side control can be mid-write, and control
deinit closes the acknowledgement read end while the pump acks — either
write could raise SIGPIPE and kill the ssh-pty-attach CLI. Set
F_SETNOSIGPIPE on both write ends at creation so racing writers surface
EPIPE (already handled) instead. Inlined rather than using
configureCLIWriteFDNoSIGPIPE because this file also compiles into the
cmuxTests target, which does not build CMUXCLI+Process.swift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…symbols

- Move the pure uint64 payload helper to a file-scope private func instead
  of a private static on the client (static-as-namespace policy).
- Add DocC to supportsInputSeqAck (adapter) and the legacy-default
  protocol extension.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@austinywang
austinywang merged commit c914b23 into main Jul 9, 2026
35 of 37 checks passed
@vercel
vercel Bot temporarily deployed to Preview – cmux July 9, 2026 13:17 Inactive

This branch was previously deployed

1 inactive deployment
Preview – cmux — 79aa8e85 Deployed Jul 9, 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.

cmux ssh: garbled / out-of-order keystrokes at reconnect and backpressure seams — pty.write input path has no sequencing, acks, or loss surfacing

1 participant