Skip to content

fix: upload pasted images in preserved-ended remote PTY panels - #7046

Open
rocker-zhang wants to merge 1 commit into
manaflow-ai:mainfrom
rocker-zhang:fix/image-paste-in-preserved-remote-pty-session
Open

rocker-zhang wants to merge 1 commit into
manaflow-ai:mainfrom
rocker-zhang:fix/image-paste-in-preserved-remote-pty-session

Conversation

@rocker-zhang

@rocker-zhang rocker-zhang commented Jun 29, 2026 •

Copy link
Copy Markdown

Fixes #1660.

What

When a cmux ssh session ends and the panel is kept alive for reconnect (preserveAfterTerminalExit), pasting an image inserts a local macOS temp path (e.g. /var/folders/.../T/clipboard-DATE-UUID.png) instead of uploading the file to the remote host.

Root cause

resolvedImageTransferTarget() checks isRemoteTerminalSurface(), which returns false once markRemotePTYAttachEnded removes the surface from activeRemoteTerminalSurfaceIds. The fallback path runs TerminalSSHSessionDetector, which looks for ssh or et as the foreground TTY process. In persistent SSH PTY sessions the foreground process is cmux ssh-pty-attach, which the detector does not recognize, so it returns nil and the code falls through to .local.

The regression was introduced in #4807, which added preserveAfterTerminalExit and endedPersistentRemotePTYAttachSurfaceIds but did not update resolvedImageTransferTarget() to handle the new state. Before #4807 the panel closed immediately on session end, so this code path was unreachable.

Fix

Add isPreservedEndedRemoteTerminalSurface() on Workspace that checks endedPersistentRemotePTYAttachSurfaceIds, and extend the guard in resolvedImageTransferTarget() to return .remote(.workspaceRemote) for these surfaces.

The set already has correct lifecycle management — populated by markRemotePTYAttachEnded (only when preserveAfterTerminalExit) and cleared on disconnect, re-attach, discard, failure, and panel destruction — so no additional cleanup is needed.

Changes

  • Sources/Workspace.swift: add isPreservedEndedRemoteTerminalSurface(_ panelId:)
  • Sources/TerminalImageTransfer.swift: extend resolvedImageTransferTarget() guard

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


Summary by cubic

Fixes image paste in preserved-ended remote PTY panels so images upload to the remote host instead of inserting a local temp path. Applies when a cmux ssh session ends with preserveAfterTerminalExit (fixes #1660).

  • Bug Fixes
    • Extend resolvedImageTransferTarget() to treat preserved-ended remote terminal surfaces as .remote(.workspaceRemote).
    • Add Workspace.isPreservedEndedRemoteTerminalSurface(_:) using endedPersistentRemotePTYAttachSurfaceIds; no lifecycle changes needed.

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved how remote terminal surfaces are recognized after a session ends, so preserved remote terminal panes are handled more consistently.
    • Fixed image transfer behavior for terminal surfaces in preserved remote workspace states, helping content display correctly in more cases.

After a cmux ssh session ends with preserveAfterTerminalExit, the panel
stays alive for reconnect. resolvedImageTransferTarget() only checked
isRemoteTerminalSurface(), which returns false once the surface is
removed from activeRemoteTerminalSurfaceIds by markRemotePTYAttachEnded.

It then fell through to TerminalSSHSessionDetector, which looks for ssh
or et as the foreground TTY process. cmux ssh-pty-attach is the actual
foreground process in persistent SSH PTY sessions and is not recognized,
so the detector returned nil and the paste inserted a local macOS path
that the remote host cannot access.

Add isPreservedEndedRemoteTerminalSurface() to check
endedPersistentRemotePTYAttachSurfaceIds, and extend the guard in
resolvedImageTransferTarget() to return .remote(.workspaceRemote) for
these surfaces. The set already has correct lifecycle management: it is
populated by markRemotePTYAttachEnded (when preserveAfterTerminalExit)
and cleared on disconnect, re-attach, discard, failure, and panel
destruction.

Fixes the regression introduced in manaflow-ai#4807.
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

@rocker-zhang 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 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1d798c61-481e-4a52-b866-2f2fb10ec42b

📥 Commits

Reviewing files that changed from the base of the PR and between 5265559 and e9e097a.

📒 Files selected for processing (2)
  • Sources/TerminalImageTransfer.swift
  • Sources/Workspace.swift

📝 Walkthrough

Walkthrough

A new isPreservedEndedRemoteTerminalSurface(_:) method is added to Workspace, checking if a panel ID is in endedPersistentRemotePTYAttachSurfaceIds. TerminalSurface.resolvedImageTransferTarget() is updated to also return .remote(.workspaceRemote) when this new check matches, in addition to the existing isRemoteTerminalSurface check.

Changes

Remote image transfer for preserved ended surfaces

Layer / File(s) Summary
Workspace query and image transfer routing
Sources/Workspace.swift, Sources/TerminalImageTransfer.swift
Adds isPreservedEndedRemoteTerminalSurface(_:) querying endedPersistentRemotePTYAttachSurfaceIds, then expands resolvedImageTransferTarget() to return .remote(.workspaceRemote) when that check matches.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

  • manaflow-ai/cmux#4513: Introduced the remote-terminal session-end/disconnect state tracking that endedPersistentRemotePTYAttachSurfaceIds and the new helper are built upon.

Poem

🐇 A surface ends, but the image must flow,
Through SSH tunnels where remote roots grow.
isPreservedEnded — a query so neat,
Now clipboard pastes land on the right remote feet!
No more local paths lost in the void,
This bunny's quite proud, and very overjoyed. ✨

🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change and rationale, but it omits required Testing, Demo Video, Review Trigger, and Checklist sections. Add the missing template sections with test steps/results, any demo video link, the review-trigger block, and checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (23 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main fix: uploading pasted images in preserved-ended remote PTY panels.
Linked Issues check ✅ Passed The code matches #1660 by treating preserved-ended remote PTY surfaces as remote so pasted images upload instead of falling back local.
Out of Scope Changes check ✅ Passed Only the targeted workspace helper and image-transfer guard were changed, with no unrelated code paths introduced.
Cmux Swift Actor Isolation ✅ Passed PASS: Workspace is already @MainActor, the new helper stays there, and resolvedImageTransferTarget() is @MainActor UI code; no new sendability or isolation debt.
Cmux Swift Blocking Runtime ✅ Passed PASS: The PR only adds a MainActor Set.contains helper and a guard in resolvedImageTransferTarget; no new waits, sleeps, syncs, or locks are introduced or expanded.
Cmux Browser Automation Off-Main ✅ Passed PASS: The diff only adds a workspace helper and image-transfer guard; no browser.* socket commands, worker routing, or policy tests were changed.
Cmux Expensive Synchronous Load ✅ Passed The diff only adds a Set.contains helper and an extra guard in resolvedImageTransferTarget; no RestorableAgentSessionIndex.load()/JSON scan moved onto a main-actor path.
Cmux Cache Substitution Correctness ✅ Passed The new check only steers immediate image-transfer routing; it doesn’t replace a fresh authoritative read in persistence/history/snapshot code, and the set is event-driven.
Cmux No Hacky Sleeps ✅ Passed PASS: the only runtime shell-script diff (Resources/bin/cmux-claude-wrapper) adds no sleeps/timers; CI YAML sleeps are out of scope, and the Swift fix uses no waits.
Cmux Algorithmic Complexity ✅ Passed PR only adds an O(1) Set.contains lookup in a paste-time path; no nested scans, repeated rescans, or other scalable-collection complexity issues.
Cmux Swift Concurrency ✅ Passed Diff only adds a @MainActor Set-membership helper and a guard; no new queues, Combine, completion handlers, or lifecycle-less Tasks.
Cmux Swift @Concurrent ✅ Passed The PR only adds a @MainActor sync helper and a sync @MainActor guard; no nonisolated async work or @concurrent misuse was introduced.
Cmux Swift File And Package Boundaries ✅ Passed Focused 1-line/5-line bug fix in existing files; Workspace is already oversized but touched incidentally, and the new helper stays beside existing remote-state helpers with a clear extraction path.
Cmux Swiftpm Lockfiles ✅ Passed Only Sources/Workspace.swift and Sources/TerminalImageTransfer.swift are changed; no Package.swift, Package.resolved, .gitignore, or Xcode project diffs are present.
Cmux Swift Logging ✅ Passed The diff only adds a remote-surface helper and a guard in image transfer; no new print/debugPrint/dump/NSLog/Logger changes were introduced.
Cmux User-Facing Error Privacy ✅ Passed PASS: The change only expands image-transfer target detection; it adds no user-visible errors, alerts, command output, or sensitive copy.
Cmux Full Internationalization ✅ Passed The PR only changes remote-terminal detection logic; it adds no user-facing Swift text, no new xcstrings entries, and no web/message locale files.
Cmux Swiftui State Layout ✅ Passed The PR only adds a Workspace boolean helper and a terminal-image target guard; no new SwiftUI state/layout patterns are introduced.
Cmux Architecture Rethink ✅ Passed PASS: The patch only adds a Workspace query over existing preserved-remote state and uses it in the existing image-target resolver; no new timers, observers, or split ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only adds a remote-terminal helper and image-transfer routing; no NSWindow/NSPanel/WindowGroup ownership or close-shortcut identifiers changed.
Cmux Source Artifacts ✅ Passed PASS: the PR changes only hand-written source files (Sources/Workspace.swift, Sources/TerminalImageTransfer.swift); no artifact, temp, cache, or build-output paths appear.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Added helper is production-facing and used by TerminalImageTransfer; no DEBUG/test-only seam, no test-only naming, no visibility widening for tests.
Cmux No Ambient Global State ✅ Passed The change only adds an instance helper on Workspace and extends an existing resolver guard; no new file-scope func/var or singleton state was introduced.
✨ 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.

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a regression from #4807 where pasting an image into a preserveAfterTerminalExit panel (after the cmux ssh-pty-attach process ends) inserted a macOS-local temp path instead of uploading to the remote host.

  • resolvedImageTransferTarget() now checks isPreservedEndedRemoteTerminalSurface alongside the existing isRemoteTerminalSurface check, returning .remote(.workspaceRemote) for preserved-ended panels.
  • A new isPreservedEndedRemoteTerminalSurface(_ panelId:) helper on Workspace delegates to endedPersistentRemotePTYAttachSurfaceIds, whose lifecycle (populated on markRemotePTYAttachEnded, cleared on disconnect/re-attach/discard/failure/panel-destruction) was already correct and needs no changes.

Confidence Score: 5/5

Safe to merge — two-line change that closes a known regression in a well-bounded code path.

The change is minimal: a new one-liner predicate on Workspace that delegates to an already-correctly-managed set, and a single || extension to the guard in resolvedImageTransferTarget(). The lifecycle of endedPersistentRemotePTYAttachSurfaceIds is already comprehensive (cleared on disconnect, re-attach, discard, failure, configuration change, and panel destruction), so no new state management is needed. The new code path follows the identical pattern as the existing isRemoteTerminalSurface check right above it. No actor-isolation, concurrency, or data-model concerns are introduced.

No files require special attention.

Important Files Changed

Filename Overview
Sources/TerminalImageTransfer.swift Extends the resolvedImageTransferTarget() guard to also return .remote(.workspaceRemote) for preserved-ended remote PTY panels, fixing the image paste regression introduced in #4807.
Sources/Workspace.swift Adds isPreservedEndedRemoteTerminalSurface(_ panelId:) on Workspace, which delegates to the already-correctly-maintained endedPersistentRemotePTYAttachSurfaceIds set.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[resolvedImageTransferTarget] --> B{owningWorkspace?}
    B -->|nil| Z[local]
    B -->|workspace| C{isRemoteTerminalSurface?}
    C -->|yes| R[remote workspaceRemote]
    C -->|no| D{isPreservedEndedRemoteTerminalSurface? NEW}
    D -->|yes| R
    D -->|no| E{remoteTmuxController remoteUploadTarget?}
    E -->|found| RF[remote target]
    E -->|nil| F{TerminalSSHSessionDetector detect?}
    F -->|found| RS[remote detectedSSH]
    F -->|nil| Z

    style D fill:#90EE90,stroke:#228B22
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[resolvedImageTransferTarget] --> B{owningWorkspace?}
    B -->|nil| Z[local]
    B -->|workspace| C{isRemoteTerminalSurface?}
    C -->|yes| R[remote workspaceRemote]
    C -->|no| D{isPreservedEndedRemoteTerminalSurface? NEW}
    D -->|yes| R
    D -->|no| E{remoteTmuxController remoteUploadTarget?}
    E -->|found| RF[remote target]
    E -->|nil| F{TerminalSSHSessionDetector detect?}
    F -->|found| RS[remote detectedSSH]
    F -->|nil| Z

    style D fill:#90EE90,stroke:#228B22
Loading

Reviews (1): Last reviewed commit: "fix: upload pasted images in preserved-e..." | Re-trigger Greptile

@teamleaderleo teamleaderleo added area: remote cmux ssh, remote daemon, tunnels, device pairing area: input Keyboard, shortcuts, IME, mouse, clipboard and paste S3: minor Wrong behavior with a workaround ready-to-land Reviewed and ready to land when CI is green labels Sep 30, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator

The native SSH path now uploads remotely, but the legacy preserved-ended PTY case remains. Before landing your fix, please comment exactly: I have read the CLA Document v2.2 and I hereby sign the CLA :)

@github-actions

Copy link
Copy Markdown
Contributor


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document v2.2 and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: input Keyboard, shortcuts, IME, mouse, clipboard and paste area: remote cmux ssh, remote daemon, tunnels, device pairing ready-to-land Reviewed and ready to land when CI is green S3: minor Wrong behavior with a workaround

Projects

None yet

Development

Successfully merging this pull request may close these issues.

image paste via cmd+v broken in ssh sessions (shows local path) — ctrl+v works as workaround

2 participants