Skip to content

remote-tmux: recover interactive SSH auth when a ProxyCommand transport closes silently - #7020

Merged
austinywang merged 9 commits into
manaflow-ai:mainfrom
ejc3:ejc3/remote-tmux-proxy-retry
Jul 1, 2026
Merged

austinywang merged 9 commits into
manaflow-ai:mainfrom
ejc3:ejc3/remote-tmux-proxy-retry

Conversation

@ejc3

@ejc3 ejc3 commented Jun 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When cmux mirrors a remote host's tmux over ssh tmux -CC, the first step is a BatchMode=yes discovery probe over the shared SSH ControlMaster. If the host is reached through an ssh ProxyCommand / ProxyJump whose own pre-handshake step (authentication, host-key, or a 2FA/MFA leg) needs a terminal, that step can't prompt under BatchMode, so the proxy closes the pipe before OpenSSH emits any auth-failure string. cmux saw only OpenSSH's pipe-transport placeholder (Connection closed by/to UNKNOWN port 65535) and treated it as a hard failure — so it never offered the interactive retry it already uses for Permission denied / host-key TOFU / MFA, and the connection failed outright.

Fix

Classify a silent ProxyCommand transport closure as interactive-retry-recoverable, so the existing "run ssh in your terminal, then retry over the now-authenticated master" path kicks in.

  • Adds indicatesProxyCommandTransportClosed and a composed indicatesInteractiveRetryWillHelp, routed through all three discovery/attach sites so no entrypoint silently regresses.
  • Only silent closures are treated as recoverable — an interactive retry can't fix a host that's refusing, unreachable, or unresolvable. The placeholder is ignored when stderr also carries an explanatory marker: connect failed:, : open failed:, stdio forwarding failed, kex_exchange_identification:, Connection refused, No route to host, could not resolve hostname (OpenSSH's DNS wrapper), and nodename nor servname provided (BSD/macOS getaddrinfo NXDOMAIN, e.g. a nc-based ProxyCommand).

Tests

cmuxTests/RemoteTmuxAuthTests — positive cases for the silent closures we must recover, and negative cases for every explained closure we must skip. 42 tests pass.


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


Summary by cubic

Restores interactive SSH auth when a ProxyCommand/ProxyJump closes the transport silently during BatchMode discovery. Adds a composed retry check and skips explained proxy failures (including DNS, timeouts, banner/pre-auth, port-forward errors, and local ProxyCommand launch errors) to avoid futile prompts.

  • Bug Fixes
    • Added indicatesProxyCommandTransportClosed and composed indicatesInteractiveRetryWillHelp; all discovery/attach paths use the composed predicate.
    • Treat only silent proxy closures as recoverable; exclude stderr with diagnostic markers (connect/open/stdio/port-forward; refused/no route/unreachable; DNS resolution via OpenSSH wrapper and BSD/Linux/macOS getaddrinfo; TCP timeouts on macOS/Linux; ssh_exchange_identification:/kex_exchange_identification:; shell launch errors like “command not found”, “: not found”, “no such file or directory”, “exec format error”).
    • Added focused tests (RemoteTmuxProxyTransportRetryTests) covering silent vs explained proxy closures, including nc DNS NXDOMAIN (Linux/BSD), Linux connect timeouts, banner-exchange closures, and shell launch failures.

Written for commit 9d7c180. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Improved remote Tmux retry handling so the app can better recognize when an interactive SSH retry is likely to help.
    • Expanded failure detection to include additional recoverable connection-closure cases.
  • Bug Fixes

    • Made attach and window-mirroring flows more likely to retry correctly after recoverable SSH errors.
    • Reduced false positives by excluding clearly non-recoverable connection failures from retry suggestions.
  • Tests

    • Added coverage for proxy-closure, auth-required, and non-recoverable SSH failure scenarios.

@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

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

This PR adds a new stderr classification predicate, indicatesProxyCommandTransportClosed, and a composed indicatesInteractiveRetryWillHelp predicate to RemoteTmuxSSHTransport. Three call sites in RemoteTmuxController switch from indicatesAuthRequired to the new composed predicate. Project file wiring and new unit tests are added.

Changes

Interactive SSH Retry Routing

Layer / File(s) Summary
New SSH transport predicates
Sources/RemoteTmuxSSHTransport+InteractiveRetry.swift
Adds indicatesProxyCommandTransportClosed, a nonRecoverableProxyMarkers constant, and a composed indicatesInteractiveRetryWillHelp predicate.
Controller call sites switched to composed predicate
Sources/RemoteTmuxController.swift, Sources/RemoteTmuxError.swift
preflightControlAttach, authRequiredAttachArgv, and mirrorHostInNewWindow switch from indicatesAuthRequired to indicatesInteractiveRetryWillHelp; comment updated; doc comment list extended.
Tests and project wiring
cmuxTests/RemoteTmuxProxyTransportRetryTests.swift, cmux.xcodeproj/project.pbxproj
New test suite validates proxy-closure classification, disjointness with auth-required, and composed predicate behavior; project file registers both new Swift files.

Sequence Diagram(s)

sequenceDiagram
  participant RemoteTmuxController
  participant RemoteTmuxSSHTransport
  participant Host
  RemoteTmuxController->>RemoteTmuxSSHTransport: indicatesInteractiveRetryWillHelp(stderr)
  RemoteTmuxSSHTransport-->>RemoteTmuxController: true (auth-required or proxy-closed)
  RemoteTmuxController->>Host: interactiveAuthInvocation()
Loading

🎯 2 (Simple) | ⏱️ ~15 minutes

A rabbit hops through SSH static, 🐇
sniffing stderr for clues, no debate.
"Port 65535, closed and quiet—
retry it, friend, don't deny it!"
Hop, compile, test, and celebrate.

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: recovering interactive SSH auth for silent ProxyCommand transport closures.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed No new Swift actor-isolation debt: changes are pure classifier helpers in an actor, existing @MainActor controller routing, plus docs/tests.
Cmux Swift Blocking Runtime ✅ Passed Touched production code only adds stderr classifiers and predicate routing; no sleeps, waits, semaphores, sync dispatch, or locks were added, and tests are assertion-only.
Cmux Browser Automation Off-Main ✅ Passed PR only changes RemoteTmux* files/tests and pbxproj; the browser-automation rule targets TerminalController.swift and ControlCommandExecutionPolicy.swift, which weren’t touched.
Cmux Expensive Synchronous Load ✅ Passed Touched Swift files only add SSH stderr classifiers and tests; no RestorableAgentSessionIndex.load(), JSON/JSONL parsing, Task.detached, or new main-actor sync load.
Cmux Cache Substitution Correctness ✅ Passed The PR only changes SSH retry classification and tests; no persistence/history/undo/snapshot cache-substitution path was altered.
Cmux No Hacky Sleeps ✅ Passed The PR changes only Swift sources/tests and Xcode wiring; no non-Swift runtime scripts or fixed-sleep/timer hacks were introduced.
Cmux Algorithmic Complexity ✅ Passed New work is fixed-size stderr marker matching; no nested scans or scalable collection rescans were added, and the extra file is test-only.
Cmux Swift Concurrency ✅ Passed Diff adds sync stderr predicates/tests only; no new DispatchQueue, Combine, completion APIs, or lifecycle-less Tasks were introduced.
Cmux Swift @Concurrent ✅ Passed The PR only adds synchronous string classifiers and swaps call-site predicates; no new nonisolated async work or invalid @concurrent annotations were introduced.
Cmux Swift File And Package Boundaries ✅ Passed PASS: the PR adds a 52-line cohesive SSH stderr-classification extension plus tests; it doesn't materially grow an oversized file or mix responsibilities.
Cmux Swiftpm Lockfiles ✅ Passed Only project.pbxproj source/test file wiring changed; no Package.resolved, Package.swift, .gitignore, or package-reference edits.
Cmux Swift Logging ✅ Passed Changed Swift code only adjusts retry predicates/comments; no new print/debugPrint/dump/NSLog, ad hoc stdout/file logging, or sensitive logging was added.
Cmux User-Facing Error Privacy ✅ Passed Changes only add internal stderr classifiers, tests, and comments; user-facing error strings remain generic/sanitized and no vendor/raw upstream text is exposed.
Cmux Full Internationalization ✅ Passed Touched files add internal retry logic, tests, and project wiring only; no user-facing Swift text, catalogs, or locale files were introduced.
Cmux Swiftui State Layout ✅ Passed The SwiftUI diff is a toolbar/title refactor; no new ObservableObject/@Published/@observable, GeometryReader, lazy-row store refs, or render-time state writes appear.
Cmux Architecture Rethink ✅ Passed PASS: This is a small correctness fix; no sleeps, polling, locks, or split ownership were added, and recovery flows are centralized behind one predicate.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff is transport/auth logic plus tests; no NSWindow/NSPanel/WindowGroup changes or cmuxAuxiliaryWindowIdentifiers assignments, so the auxiliary-window rule isn’t implicated.
Cmux Source Artifacts ✅ Passed Changed paths are source/test/config only, and the pbxproj additions reference Swift source/test files, not logs, caches, tmp, or build artifacts.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The PR adds real SSH-retry classification used by production callers; no new DEBUG/test-only seam or test-hook accessor was added in Sources.
Cmux No Ambient Global State ✅ Passed PASS: New code stays inside an existing actor/extension; no new top-level funcs/vars/singletons or static-only namespace were introduced.
Description check ✅ Passed The PR description includes the required summary and testing details, and is mostly complete despite missing the demo video, review trigger, and checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@ejc3
ejc3 marked this pull request as ready for review June 28, 2026 03:38
@greptile-apps

greptile-apps Bot commented Jun 28, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent failure in the remote-tmux SSH discovery probe: when a ProxyCommand/ProxyJump needs a terminal for its own pre-handshake auth step, it closes the pipe under BatchMode=yes without emitting an auth-failure string, and cmux was treating that as a hard failure instead of routing to the existing interactive retry path.

  • Adds indicatesProxyCommandTransportClosed in a new RemoteTmuxSSHTransport+InteractiveRetry.swift extension, matching OpenSSH's pipe-transport placeholder (to/by UNKNOWN port 65535) while suppressing the match when stderr carries any of 17 non-recoverable diagnostic markers (DNS, TCP timeout, refused, banner exchange, missing binary, etc.).
  • Composes the new predicate with indicatesAuthRequired into indicatesInteractiveRetryWillHelp; all three retry-routing catch sites in RemoteTmuxController are updated to the composed predicate, ensuring future recovery signals propagate to every entrypoint automatically.
  • Adds RemoteTmuxProxyTransportRetryTests covering silent/explained closure classification, false-positive anchoring, disjointness of the two constituent predicates, and composed predicate correctness.

Confidence Score: 5/5

Safe to merge — the change is narrow, the non-recoverable marker list is well-considered, and the existing indicatesNoServer / indicatesAuthRequired call in the transport's listSessions() throw-gate is correctly left alone because it is not a routing site.

The new predicate is anchored to a highly specific OpenSSH pipe-transport placeholder, suppressed by 17 non-recoverable markers (covering DNS, TCP timeout, refused, banner exchange, missing binary on all OSes), and composed cleanly with the existing auth predicate. All three retry-routing sites in the controller are updated. Test coverage spans silent positive cases, all explained-closure negatives, false-positive anchoring, and composed predicate behavior. No behavioral regressions are introduced at unmodified call sites.

No files require special attention.

Important Files Changed

Filename Overview
Sources/RemoteTmuxSSHTransport+InteractiveRetry.swift New extension file introducing indicatesProxyCommandTransportClosed, nonRecoverableProxyMarkers, and the composed indicatesInteractiveRetryWillHelp; logic, marker coverage, and case folding are correct.
Sources/RemoteTmuxController.swift All three retry-routing catch sites updated from indicatesAuthRequired to indicatesInteractiveRetryWillHelp; the remaining indicatesAuthRequired call in the transport's listSessions() throw-gate is not a routing decision and is correctly left alone.
Sources/RemoteTmuxError.swift Doc-comment-only update adding indicatesProxyCommandTransportClosed to the list of stderr-classification paths; no logic change.
cmuxTests/RemoteTmuxProxyTransportRetryTests.swift New test suite covering silent proxy closures (positive), all explained-closure markers (negative), false-positive anchoring, real-port non-matches, predicate disjointness, and composed predicate behavior.
cmux.xcodeproj/project.pbxproj Adds RemoteTmuxSSHTransport+InteractiveRetry.swift to the app target and RemoteTmuxProxyTransportRetryTests.swift to the test target; also fixes a pre-existing indentation inconsistency in the build-file list.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["BatchMode=yes SSH probe\n(commandFailed thrown)"] --> B{indicatesInteractiveRetryWillHelp?}
    B --> C{indicatesAuthRequired?}
    C -- "Permission denied\nHost key mismatch\nMFA / Too many failures" --> D["✅ Interactive retry\n(route user to ssh in terminal)"]
    C -- no --> E{indicatesProxyCommandTransportClosed?}
    E --> F{"stderr contains\n'to/by UNKNOWN port 65535'?"}
    F -- no --> G["❌ Hard failure\n(rethrow to caller)"]
    F -- yes --> H{"stderr also contains\nnonRecoverableProxyMarker?"}
    H -- "connect failed / open failed\nstdio forwarding failed\nConnection refused / timed out\nDNS errors / kex errors\nmissing binary / etc." --> G
    H -- "no diagnostic marker\n(truly silent proxy closure)" --> D
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"}}}%%
flowchart TD
    A["BatchMode=yes SSH probe\n(commandFailed thrown)"] --> B{indicatesInteractiveRetryWillHelp?}
    B --> C{indicatesAuthRequired?}
    C -- "Permission denied\nHost key mismatch\nMFA / Too many failures" --> D["✅ Interactive retry\n(route user to ssh in terminal)"]
    C -- no --> E{indicatesProxyCommandTransportClosed?}
    E --> F{"stderr contains\n'to/by UNKNOWN port 65535'?"}
    F -- no --> G["❌ Hard failure\n(rethrow to caller)"]
    F -- yes --> H{"stderr also contains\nnonRecoverableProxyMarker?"}
    H -- "connect failed / open failed\nstdio forwarding failed\nConnection refused / timed out\nDNS errors / kex errors\nmissing binary / etc." --> G
    H -- "no diagnostic marker\n(truly silent proxy closure)" --> D
Loading

Reviews (10): Last reviewed commit: "remote-tmux: treat local ProxyCommand la..." | Re-trigger Greptile

Comment thread Sources/RemoteTmuxSSHTransport.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 `@Sources/RemoteTmuxSSHTransport.swift`:
- Around line 345-359: Add `ssh_exchange_identification:` to
`RemoteTmuxSSHTransport.nonRecoverableProxyMarkers` alongside
`kex_exchange_identification:` so `indicatesProxyCommandTransportClosed(_:)`
treats both banner-failure variants as non-recoverable. Keep the existing
proxy-closure detection path in `RemoteTmuxSSHTransport` unchanged otherwise,
and ensure the new marker matches the same stderr wording already covered by the
auth tests.
🪄 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: 0c8e9f17-faae-43b9-ab8b-3508fbcee8e2

📥 Commits

Reviewing files that changed from the base of the PR and between 05c03cf and d255d68.

📒 Files selected for processing (4)
  • Sources/RemoteTmuxController.swift
  • Sources/RemoteTmuxError.swift
  • Sources/RemoteTmuxSSHTransport.swift
  • cmuxTests/RemoteTmuxAuthTests.swift

Comment thread Sources/RemoteTmuxSSHTransport.swift Outdated
@ejc3

ejc3 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in 3a14766: added connection timed out (Linux nc/OpenSSH connect-timeout phrasing) to nonRecoverableProxyMarkers alongside the existing operation timed out (BSD/macOS), with an isolated negative test for a Linux nc ProxyCommand timeout. Good catch — thanks.

@ejc3
ejc3 marked this pull request as draft June 29, 2026 08:33
@ejc3
ejc3 force-pushed the ejc3/remote-tmux-proxy-retry branch from 3a14766 to 52fea4c Compare June 29, 2026 22:47
@ejc3
ejc3 marked this pull request as ready for review June 29, 2026 22:54
@ejc3
ejc3 force-pushed the ejc3/remote-tmux-proxy-retry branch from 52fea4c to 7474e94 Compare June 29, 2026 23:12
@vercel

vercel Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 1, 2026 11:34pm

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

ejc3 and others added 7 commits June 30, 2026 22:23
…etry recoverable

A BatchMode=yes discovery probe through an ssh `ProxyCommand` whose own
pre-handshake auth or 2FA leg silently aborts (no tty to prompt on) closes the
proxy pipe before SSH emits any auth-failure string. Catch this stderr signature
(OpenSSH's `to/by UNKNOWN port 65535` placeholder for pipe transports) and route
it to the same interactive ssh retry already used for `Permission denied` /
host-key TOFU / MFA.

Introduces `indicatesProxyCommandTransportClosed` next to the existing
`indicatesAuthRequired` and a composed `indicatesInteractiveRetryWillHelp` so
the three RemoteTmuxController routing sites that previously each spelled out
`indicatesAuthRequired` (mirrorHostInNewWindow's discovery catch,
preflightControlAttach's catch arm, and authRequiredAttachArgv) now go through
a single name — preventing the asymmetry where only one entrypoint would have
gotten the new recovery and the others silently regressed.
… recoverable

OpenSSH's `to/by UNKNOWN port 65535` placeholder is also emitted when a
ProxyCommand / ProxyJump fails for reasons no interactive ssh retry can fix:
target unreachable behind the jumphost, `nc` to a refused port, stdio
forwarding teardown, target spoke no SSH on the negotiated port, DNS NXDOMAIN.
Those failures stamp explicit diagnostic markers (`connect failed:`,
`: open failed:`, `stdio forwarding failed`, `kex_exchange_identification:`,
`Connection refused`, `No route to host`, etc.) into stderr alongside the
placeholder; route them to the real error instead of bouncing the user
through a futile interactive prompt.

Tightens indicatesProxyCommandTransportClosed to require the placeholder AND
no diagnostic marker before firing. Adds positive tests for the SILENT
closures we still need to catch and negative tests for the EXPLAINED closures
the predicate must now skip — including the precise stderr codex's review
reproduced via `ssh -J nowhere.invalid` and `ssh -oProxyCommand='nc localhost 9'`.
…classification

`xcodebuild test` against `cmuxTests/RemoteTmuxAuthTests` flagged that the
`Could not resolve hostname …\nConnection closed by UNKNOWN port 65535` stderr
slipped past the silent-closure check — OpenSSH wraps every `getaddrinfo`
failure with that prefix (across macOS / Linux / Windows getaddrinfo strerrors)
so the proxy DNS NXDOMAIN looked silent to the predicate.

Adds `could not resolve hostname` to the non-recoverable marker list alongside
the existing `name or service not known` / `temporary failure in name
resolution` constants (which only cover the underlying getaddrinfo wording,
not the OpenSSH wrapper).
…t-proxy-close

Second codex review pass reproduced a remaining false positive: when a
`ProxyCommand` uses `nc` and `nc` itself fails DNS, BSD/macOS netcat emits
`nc: getaddrinfo: nodename nor servname provided, or not known` raw —
OpenSSH's `Could not resolve hostname` wrapper only fires when OpenSSH does
the resolution, not when an inferior `ProxyCommand` does. The resulting
stderr has the proxy placeholder and no exclusion marker, so the predicate
returned true and routed the user through a futile interactive retry.

Adds `nodename nor servname provided` to the non-recoverable marker list and
extends `doesNotClassifyExplainedProxyClosures` with the exact stderr codex
reproduced via `ssh -o ProxyCommand='nc nonexistent.invalid 22'`.
…osures as non-recoverable

Review follow-up: two more explained ProxyCommand/ProxyJump closures were
slipping past the silent-closure check and routing the user through a futile
interactive retry:

- Linux TCP connect timeouts phrase it "Connection timed out" (only the
  BSD/macOS "Operation timed out" was covered), so an nc-based ProxyCommand
  timing out on Linux looked silent.
- "ssh_exchange_identification:" banner-exchange closures (the inner target
  dropping the connection pre-auth: fail2ban, tcpwrappers, not-SSH-on-port)
  were only excluded when a second marker happened to co-occur.

Adds both to nonRecoverableProxyMarkers with isolated negative tests (no
co-present marker) so each is genuinely exercised.
austinywang and others added 2 commits July 1, 2026 16:01
…y-retry

# Conflicts:
#	cmux.xcodeproj/project.pbxproj
A ProxyCommand that fails to launch (missing binary, bad path, wrong-arch
executable) emits only the shell's diagnostic plus OpenSSH's UNKNOWN-port
placeholder — verified on OpenSSH 10.2: no kex_exchange_identification
line precedes it. The silent-closure classifier treated those as
interactive-retry recoverable, hiding the actionable config error behind
a retry that fails identically. Add the shell launch diagnostics
(bash/zsh 'command not found', dash/busybox ': not found', 'no such file
or directory', 'exec format error') to nonRecoverableProxyMarkers, with
negative fixtures for each shell phrasing.

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 5 files

Re-trigger cubic

This branch was successfully deployed

1 active deployment
Preview – cmux — 9d7c180d Deployed Jul 1, 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.

2 participants