Skip to content

Ignore remote stdin writes after child exit - #9080

Closed
lawrencecchen wants to merge 28 commits into
mainfrom
issue-5750-remote-stdin-broken-pipe
Closed

lawrencecchen wants to merge 28 commits into
mainfrom
issue-5750-remote-stdin-broken-pipe

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Closes #5750

Supersedes #6863. Current main already owns the shared POSIX process-pipe writer and the TextBox call site. The remaining unsafe production write was RemoteSessionProcessRunner feeding a child that had already closed stdin.

The runner now uses writeProcessPipeInput and treats a closed reader as a completed best-effort stdin handoff. The shared helper suppresses SIGPIPE, handles partial writes and EINTR, and reports EPIPE instead of allowing Foundation to abort the process.

Regression proof:

  • c6b4c45 adds a 1 MiB write to /usr/bin/true and exits with signal 13 before the fix.
  • 2a63ff6 routes that write through the safe helper; the focused test passes.

Validation:

  • swift test --package-path Packages/macOS/CmuxRemoteSession --filter childClosingStdinDoesNotAbortRunner
  • Canonical autoreview: clean, 0.98 confidence

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Prevents aborts when a child closes stdin early, makes completion event‑driven and lock‑free, and ensures timeouts fire even if stdin is unread or descendants inherit the pipes. Surfaces unexpected stdin write errors deterministically, even if the child ignores TERM and is SIGKILLed.

  • Bug Fixes

    • Route stdin through RemoteProcessStdinWriter: ignore SIGPIPE; nonblocking writes with POLLOUT polling; handle partial/EINTR/EAGAIN; treat EPIPE as expected when the child closes stdin.
    • Deliver stdin off‑thread with a stop signal; deadlines and cancellations fire even if stdin is unread; inherited writers can’t delay completion.
    • Bound stdout/stderr with ProcessPipeStopAwareReader and ProcessPipeStopSignal; return buffered bytes on exit so inherited output pipes can’t stall.
    • Surface non‑EPIPE stdin errors as cmux.remote.process code 3; terminate and preserve the error even after forced SIGKILL; wins over earlier exits.
  • Refactors

    • Event‑driven, lock‑free completion via RemoteProcessExitSignal, RemoteProcessCompletionWaiter, and RemoteProcessStdinWriteState; inject RemoteProcessStdinWriting (default RemoteProcessStdinWriter).
    • Add ProcessPipeStopSignal and ProcessPipeStopAwareReader; normalize non‑positive chunk sizes to 1; keep a minimal read‑handle observation seam for tests.

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved writing to pipe-backed standard input for launched processes, including safely handling races where the child closes stdin early.
    • If an unexpected stdin write error occurs, the runner now performs thorough cleanup, terminates and waits for the process (when still running), and surfaces the failure (cmux code 3).
  • Tests

    • Added coverage for large stdin payloads when the child closes stdin early, plus a regression test for unexpected stdin write errors.
  • Documentation

    • Updated RemoteSessionProcessRunning.run docs to explicitly document stdin-write as a possible failure mode.

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai

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

The process runner now uses an injectable stdin writer for pipe-backed input, ignores expected broken-pipe races, reports other write failures with code 3, documents the new error case, and adds regression coverage.

Changes

Stdin pipe handling

Layer / File(s) Summary
Injectable stdin writer contract
Packages/macOS/CmuxRemoteSession/.../RemoteProcessStdinWriting.swift, Packages/macOS/CmuxRemoteSession/.../RemoteSessionProcessRunner.swift, Packages/macOS/CmuxRemoteSession/.../RemoteSessionProcessRunning.swift
Defines the stdin-writing abstraction, wires its default implementation into the runner, and documents stdin-write failures with code 3.
Stdin write error flow and regression coverage
Packages/macOS/CmuxRemoteSession/.../RemoteSessionProcessRunner.swift, Packages/macOS/CmuxRemoteSession/.../RemoteSessionProcessRunnerTests.swift
Ignores EPIPE, terminates and cleans up after other write errors, and tests early child-side stdin closure plus injected write failures.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: austinywang, azooz2003-bit

Sequence Diagram(s)

sequenceDiagram
  participant RemoteSessionProcessRunner
  participant RemoteProcessStdinWriter
  participant ChildProcess
  RemoteSessionProcessRunner->>RemoteProcessStdinWriter: Write stdin data
  RemoteProcessStdinWriter->>ChildProcess: Write process pipe input
  ChildProcess-->>RemoteProcessStdinWriter: Return success or POSIX error
  RemoteProcessStdinWriter-->>RemoteSessionProcessRunner: Return or throw
  RemoteSessionProcessRunner->>ChildProcess: Terminate and wait on unexpected error
Loading

Important

Pre-merge checks failed

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

❌ Failed checks (2 errors)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error RemoteSessionProcessRunner adds a new code-3 NSError description ('Failed to write stdin...') without String(localized:) or a matching catalog entry. Localize the code-3 message with String(localized:defaultValue:) and add matching entries to Resources/Localizable.xcstrings for every supported locale.
Cmux No Test Or Debug Seam In Production Source ❌ Error RemoteSessionProcessRunner.swift adds readHandlesDidInstall, an explicit test-observation hook in production Sources with no production caller. Move the observation into Tests/ via @testable import, or widen needed state to internal and remove the production seam.
✅ Passed checks (23 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes match the linked issue's remaining broken-pipe fix by routing remote stdin through the safe helper and treating EPIPE as expected.
Out of Scope Changes check ✅ Passed The added protocol, runner injection, and tests are all directly related to the stdin-write fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Cmux Swift Actor Isolation ✅ Passed No actor-isolation regression: the new stdin writer is an immutable Sendable protocol with only struct conformers; no MainActor/UI/shared-mutable reference type was introduced.
Cmux Swift Blocking Runtime ✅ Passed No new production blocking/sync was added; the diff only changes stdin writing and adds a test-only semaphore/uptime check.
Cmux Browser Automation Off-Main ✅ Passed PR only changes RemoteSessionProcessRunner/stdin-writing tests; no browser.* commands, WebKit/AppKit routing, or policy-test paths are touched.
Cmux Expensive Synchronous Load ✅ Passed Diff only refactors stdin writing in RemoteSessionProcessRunner and adds tests/docs; no agent-history load, JSON parse, or main-actor/interactive sync load was introduced.
Cmux Cache Substitution Correctness ✅ Passed PR only refactors stdin writing in the process runner; no fresh-read-to-cache substitution or persistence/history/undo/snapshot cache path is introduced.
Cmux No Hacky Sleeps ✅ Passed The PR only touches Swift files; no new fixed sleeps/timers were added, and the remaining waits are bounded process timeouts or test-only synchronization.
Cmux Algorithmic Complexity ✅ Passed Changed production code is a single stdin write plus existing capture logic; no nested scans, rescans, or repeated filtering/sorting were introduced.
Cmux Swift Concurrency ✅ Passed No new legacy async patterns were introduced; the existing DispatchQueue readers are unchanged and the PR adds only synchronous stdin-writer injection/tests.
Cmux Swift @Concurrent ✅ Passed The PR only adds synchronous stdin-writer injection and tests; no @concurrent, nonisolated async, or actor-isolation changes appear in touched Swift files.
Cmux Swift Package Boundaries ✅ Passed PASS: The new stdin logic and protocol live in Packages/macOS/CmuxRemoteSession, and the app target only instantiates the package runner as glue.
Cmux Swiftpm Lockfiles ✅ Passed No Package.resolved, Package.swift, .gitignore, workflow, or Xcode project files changed; only source/test Swift files were modified, so the lockfile rule isn’t implicated.
Cmux Swift Logging ✅ Passed Diff only adds a DEBUG-gated CMUXDebugLog call; no print/NSLog/Logger misuse or sensitive-data logging was introduced.
Cmux User-Facing Error Privacy ✅ Passed No prohibited user-facing data added; the new error text is generic cmux.remote.process wording with only the executable basename and POSIX description.
Cmux Swiftui State Layout ✅ Passed PASS: The diff only touches process-runner/protocol/test files; no SwiftUI views or state/layout patterns are introduced.
Cmux Architecture Rethink ✅ Passed Small local runner fix with explicit stdin owner/invariant; added test-only seams, no new timing, polling, shared-state, or duplicate lifecycle wiring.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: PR only changes RemoteSessionProcessRunner stdin handling plus tests/docs; no NSWindow/NSPanel/WindowGroup code or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Only intentional Swift source and test files changed; no logs, caches, build output, temp dirs, or other source artifacts appear in the diff.
Cmux No Ambient Global State ✅ Passed The new protocol/struct are instance-based and injectable; no free functions, mutable globals, static-only namespaces, or singletons were added.
Title check ✅ Passed The title clearly describes the main fix: handling remote stdin writes safely after the child exits.
Description check ✅ Passed It covers the change, rationale, and validation, with only noncritical template sections like demo video, checklist, and review trigger missing.
✨ 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-5750-remote-stdin-broken-pipe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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
`@Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift`:
- Around line 191-193: Update the stdin write in RemoteSessionProcessRunner to
catch errors from writeProcessPipeInput explicitly, ignore only the POSIX EPIPE
case caused by the child closing the pipe, and propagate or route all other
failures—including EBADF, EIO, fcntl, and write errors—through the runner’s
failure path instead of continuing to the normal timeout or exit-status result.
🪄 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 Plus

Run ID: 9304f8bb-670e-487d-9b99-d183b7f2d2c3

📥 Commits

Reviewing files that changed from the base of the PR and between 664c6e1 and 2a63ff6.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift
  • Packages/macOS/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemoteSessionProcessRunnerTests.swift

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 6e2567cdb8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@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
`@Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift`:
- Around line 23-24: Remove the test-only StdinWriter/writeStdin injection from
the production RemoteSessionProcessRunner type, including its stored property
and initializer plumbing. Make stdin writes use the normal FileHandle path
directly, and update callers/tests to exercise failures through a controlled
pipe or process fixture instead of injecting behavior.
🪄 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 Plus

Run ID: 8f9995a8-18df-43da-a550-cbb984d6701d

📥 Commits

Reviewing files that changed from the base of the PR and between ef6c786 and 462e80b.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift
  • Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunning.swift

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

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

Caution

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

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift (1)

221-240: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Avoid blocking the caller during stdin-write failure cleanup.

Line 231 now invokes terminateProcessAndWait() from synchronous run, blocking for up to two seconds and potentially in waitUntilExit(). Use the existing completion-driven process lifecycle or an async cancellation-aware owner instead of adding a blocking repair path here.

🤖 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
`@Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift`
around lines 221 - 240, Update the stdin-write failure cleanup in the
synchronous run path to avoid calling blocking terminateProcessAndWait(). Reuse
the existing completion-driven process lifecycle or an asynchronous
cancellation-aware owner to terminate the process without waiting synchronously,
while preserving handle cleanup, logging, and error propagation.

Source: Coding guidelines

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

Outside diff comments:
In
`@Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift`:
- Around line 221-240: Update the stdin-write failure cleanup in the synchronous
run path to avoid calling blocking terminateProcessAndWait(). Reuse the existing
completion-driven process lifecycle or an asynchronous cancellation-aware owner
to terminate the process without waiting synchronously, while preserving handle
cleanup, logging, and error propagation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2ac85422-7d2e-47bc-872f-cd01b5cbd28c

📥 Commits

Reviewing files that changed from the base of the PR and between 462e80b and 5b2cd96.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift
  • Packages/macOS/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemoteSessionProcessRunnerTests.swift

@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

Caution

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

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift (1)

197-204: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Avoid blocking the caller during stdin-write failure cleanup.

The new error path calls terminateProcessAndWait(), which waits up to two seconds and may then call process.waitUntilExit(). This materially expands synchronous blocking into the write-failure path; use the runner’s lifecycle/completion signaling without adding another blocking wait.

As per coding guidelines and path instructions, non-test Swift must not add or materially expand blocking waits for process or async coordination.

Also applies to: 214-218

🤖 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
`@Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift`
around lines 197 - 204, Update the stdin-write failure cleanup to avoid calling
terminateProcessAndWait(), which can block for up to two seconds or wait for
process exit. Reuse the runner’s existing lifecycle/completion signaling to
initiate termination and let asynchronous cleanup handle completion without
adding a synchronous process or coordination wait.

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/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemoteSessionProcessRunnerTests.swift`:
- Around line 98-122: Replace the systemUptime-based assertion in
unexpectedStdinWriteErrorTerminatesChildAndKeepsPinnedRunnerError with a
test-owned exit marker or process handle that observes /bin/sleep termination.
After runner.run throws, await or poll the termination predicate using the test
framework’s deadline-bounded mechanism, while preserving the existing error
assertions.

---

Outside diff comments:
In
`@Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift`:
- Around line 197-204: Update the stdin-write failure cleanup to avoid calling
terminateProcessAndWait(), which can block for up to two seconds or wait for
process exit. Reuse the runner’s existing lifecycle/completion signaling to
initiate termination and let asynchronous cleanup handle completion without
adding a synchronous process or coordination wait.
🪄 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 Plus

Run ID: 77c1925f-9189-47b7-99d1-191adcd734fc

📥 Commits

Reviewing files that changed from the base of the PR and between 5b2cd96 and 8ff8d2e.

📒 Files selected for processing (3)
  • Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteProcessStdinWriting.swift
  • Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Process/RemoteSessionProcessRunner.swift
  • Packages/macOS/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemoteSessionProcessRunnerTests.swift

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe9f1b236c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
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.

CLI broken-pipe writes: 10.9M-event Sentry flood + SIGPIPE and SIGABRT crashes (all one root cause)

2 participants