Skip to content

Add opt-in setting to paste clipboard images into agents with Ctrl+V - #17711

Closed
azooz2003-bit wants to merge 8 commits into
mainfrom
feat/image-paste-ctrl-v-agents
Closed

azooz2003-bit wants to merge 8 commits into
mainfrom
feat/image-paste-ctrl-v-agents

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

With an image on the clipboard, Cmd+V in a terminal saves the image to a temporary file and pastes its path. Claude Code and Codex can attach an image straight from the macOS clipboard when they receive Ctrl+V, the way they work in other terminals, but cmux never sends it.

This adds terminal.agentImagePasteSendsCtrlV, off by default, with a row in Settings > Terminal ("Paste Images into Agents with Ctrl+V"). When it's on, Cmd+V (and Edit > Paste) sends Ctrl+V to the pane instead of pasting a path, but only when all of these hold. Anything else runs the existing paste unchanged:

  • A live Claude Code or Codex is in the pane. This reuses the existing agent detection (WorkspaceContentView.terminalAgentContext + TextBoxAgentDetection) and requires the hook-reported agentPIDKey. A launch command or restored snapshot alone doesn't count, because Ctrl+V sent to a shell after the agent exits would drop the image. TextBoxAgentDetection gains a per-agent matchesActive(context:), next to the existing supportsActiveAgentPrefixes.
  • The agent owns the terminal's foreground. Stale PIDs are pruned first with Workspace.clearStaleAgentPIDs(panelId:) (as the TextBox submit-action cycle does), so an agent killed without an exit hook no longer counts. Then one of the pane's recorded Claude/Codex PIDs must be in the terminal's foreground process group (TerminalSurface.foregroundProcessID(), which is Ghostty's tcgetpgrp). A Ctrl+Z-suspended agent, or one in another tmux window, keeps the temp-path paste instead of sending ^V to the shell or tmux. Limit: a child the agent runs in its own process group, such as its external editor, shares that group and still counts.
  • The clipboard holds only image data. The pasteboard type list (not the data) must include an image type and no text, rich text, URL, file or promised-file type. Clipboards that carry an image and text (for example a browser "Copy Image" with HTML) keep the regular paste.
  • The pane is local (resolvedImageTransferTarget == .local). For cmux ssh, detected SSH and Cloud panes the agent would read another machine's clipboard, so they keep the upload path.
  • The panel isn't hibernated, and the key is delivered. If sendNamedKey("ctrl+v") fails, the regular paste runs.

Ctrl+V goes through TerminalPanel.sendNamedKey, so Ghostty encodes it for the pane's active keyboard mode like any typed key. The checks run in order of cost: the setting first, then agent context, then the pasteboard type list, then target resolution. With the setting off, paste(_:) does one UserDefaults read before its existing code.

Wiring: CmuxSettings catalog key, cmux.json section mapping and supported paths, web/data/cmux.schema.json (plus the regenerated embedded CmuxConfigSchema.generated.swift) with descriptionKey: schemaDescriptions.terminal.agentImagePasteSendsCtrlV, Settings row, curated and app search index entries, and skills/cmux-settings/references/all-keys.md.

Related but separate: #11314 (Cmd+C/Cmd+V routing), #7046 and #5586 (remote image upload paths). This PR changes neither routing nor uploads.

Testing

  • Added cmuxTests/TerminalAgentImagePasteRoutingTests.swift (wired with ./scripts/sync-test-wiring). It checks the decision matrix: setting on or off (off never evaluates the agent, pasteboard or target), live Claude/Codex vs other or exited agents, image-only vs image plus each text, URL or file type, and local vs remote or Cloud targets.
  • Stale and background agents: stalePIDWithoutAnExitHookKeepsTheTempPathPaste, liveAgentOutsideTheForegroundGroupKeepsTheTempPathPaste and liveForegroundAgentInAWorkspaceSendsCtrlV run the workspace-level decision against a real Workspace with recorded PIDs. foregroundCheckMatchesTheAgentsProcessGroup, agentOutsideTheForegroundKeepsTheTempPathPaste and onlyClaudeCodeAndCodexPIDsCountForTheForegroundCheck cover the injected foreground predicate.
  • Added SettingCatalogTests.agentImagePasteSendsCtrlVDefaultsOff and the new row path to SettingsRowAnchorResolutionTests.
  • Ran locally on Linux: python3 scripts/verify-local.py (swift-syntax, xcstrings, localization, project, config-schema, test-wiring, package-groups, feature-flags: 8/8 pass), plus tests/test_cmux_schema_parity.py and tests/test_cmux_settings_supported_paths.py. CI on bd8600e (rebased onto Add terminal.confirmUnsafePaste to confirm unsafe pastes in a window sheet #14951) is green: the app compiled, all of Agent image paste routing (including the six stale-PID and foreground tests above) ran and passed in app-host unit tests (changed suites), and agentImagePasteSendsCtrlVDefaultsOff passed in swift-package-tests.
  • Not dogfooded. A live check needs a tagged build: with the setting on, take a screenshot to the clipboard (Cmd+Ctrl+Shift+4), then press Cmd+V in a pane running claude or codex (the image should attach) and in a plain shell pane (a temp path should paste, as before).

Localization audit: four new keys in Resources/Localizable.xcstrings (the row title, the on and off subtitles, and the search alias), each with all nine app locales. scripts/localization_catalog.py check reports 0 parity errors and scripts/lint-xcstrings.py passes. The schema description has a descriptionKey with a translated entry in all 20 web locales (web/messages/*.json), added in ea84ae6 after review. The Korean subtitle uses 분할 창 for panes.

Checklist

  • Behavior changes have added or updated tests, or Testing says why not
  • UI, settings, menu, schema, help-text or user-facing docs change: localization audited, and the result is stated above
  • Docs and changelog updated if needed (settings reference; changelog left to release)

— Glasswren g1 🎲
run: run_paste_improvements_20260927_d07ca05d

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an optional Terminal setting to send Ctrl+V when pasting an image-only clipboard into a running Claude Code or Codex agent in a local pane. It’s off by default; otherwise, image pastes continue to use a temporary-file path.
    • Added support for configuring and searching for this setting.
    • Clipboards containing text, rich text, URLs, or files continue to use regular paste.
  • Documentation
    • Updated the settings reference and configuration schema with the new option and its behavior.

Migrated from #14948 after correcting the PR author identity. The head branch and commit history are preserved.


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

Adds an opt-in terminal.agentImagePasteSendsCtrlV setting (off by default) that makes Cmd+V send Ctrl+V to a live Claude Code or Codex in a local pane when the clipboard holds only image data, so the agent attaches the image directly from the clipboard. All other cases keep the existing behavior: saving the image to a temporary file and pasting or uploading its path.

The setting takes over only when every condition holds; otherwise the regular paste runs unchanged:

  • The agent is live in the pane per its hooks (stale PIDs are pruned first) and owns the terminal's foreground process group, so a Ctrl+Z-suspended agent or one in another tmux window still gets the temp-path paste.
  • The clipboard advertises an image type and no text, rich text, URL, or file type.
  • The pane is local; cmux ssh, detected SSH, and Cloud panes keep the upload path.

Also includes the Settings > Terminal row, cmux.json schema entry, search index entries, settings reference, and localization in all supported locales.

Written for commit 9754f85. Summary will update on new commits.

Review in cubic Turn on auto-fix

teamleaderleo and others added 8 commits September 27, 2026 10:05
Cmd+V with an image on the clipboard saves it to a temporary file and
pastes the path. Claude Code and Codex can attach an image straight from
the clipboard when they receive Ctrl+V, which is what users of those
agents expect in other terminals.

`terminal.agentImagePasteSendsCtrlV` (off by default) makes Cmd+V send
Ctrl+V instead, but only when all of these hold; otherwise the existing
paste runs unchanged:

- Claude Code or Codex is live in the pane according to its hooks
  (`agentPIDKey`), not merely launched or restored earlier.
- The clipboard advertises image data and no text, rich text, URL or
  file type. Only the type list is read on the main thread.
- The pane is local. cmux ssh, detected SSH and Cloud panes keep the
  upload path, since the agent there reads another machine's clipboard.
- The panel is not hibernated and the key is delivered.

The setting is wired through the CmuxSettings catalog, cmux.json schema,
Settings > Terminal row, search index and all-keys reference, with
strings in all nine app locales.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first version trusted the pane's recorded agent PIDs, which can be
stale or belong to an agent that isn't reading the keyboard, so a shell
or tmux could receive a stray ^V.

- Prune stale PIDs with `clearStaleAgentPIDs(panelId:)` before reading
  the agent context, as the TextBox submit-action cycle does. An agent
  killed without an exit hook no longer counts.
- Require one of the pane's recorded Claude Code or Codex PIDs to be in
  the terminal's foreground process group (`foregroundProcessID()`, which
  is Ghostty's tcgetpgrp). A Ctrl+Z-suspended agent, or one in another
  tmux window, keeps the temp-path paste.
- The workspace-level decision moves into `TerminalAgentImagePasteRouting`
  with the foreground group and getpgid injected, so both cases are
  tested against a real Workspace. The setting check still runs first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…gents

# Conflicts:
#	Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift
#	cmux.xcodeproj/project.pbxproj
Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: origin/main at 478e323.

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py
- Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift: generate-cmux-config-schema.py, regenerated from the merged schema (both sides changed the schema)

Catch-up-previous-head: ea84ae6
Catch-up-base: 478e323
Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: origin/main at 1b06f84.

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py

Catch-up-previous-head: cb9613a
Catch-up-base: 1b06f84
Resolved the settings reference conflict by keeping both sides.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: origin/main at 5eda931.

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py

Catch-up-previous-head: b0d1f5d
Catch-up-base: 5eda931

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: origin/main at ea6e02b.

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py
- Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift: generate-cmux-config-schema.py, regenerated from the merged schema (both sides changed the schema)

Catch-up-previous-head: 54bdee8
Catch-up-base: ea6e02b
@azooz2003-bit azooz2003-bit added the needs a call Finished and held for a team design or product decision (see #13742 and the gallery in #15427) label Oct 6, 2026
@cursor

cursor Bot commented Oct 6, 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 Oct 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 54 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 7e2584c9-77ea-4d9b-acf9-60492fb84013
📥 Commits

Reviewing files that changed from the base of the PR and between a8c4861 and 9754f85.

⛔ Files ignored due to path filters (1)
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift is excluded by !**/*.generated.*
📒 Files selected for processing (37)
  • Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/TerminalCatalogSection.swift
  • Packages/macOS/CmuxSettings/Tests/CmuxSettingsTests/SettingCatalogTests.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift
  • Resources/Localizable.xcstrings
  • Sources/CmuxSettingsFileStore+SupportedPaths.swift
  • Sources/CmuxSettingsJSONPathSupport.swift
  • Sources/GhosttyNSView+ClipboardActions.swift
  • Sources/SettingsSearchAliases.swift
  • Sources/SettingsSearchIndex.swift
  • Sources/TerminalAgentImagePasteRouting.swift
  • Sources/TextBoxAgentDetection.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/TerminalAgentImagePasteRoutingTests.swift
  • skills/cmux-settings/references/all-keys.md
  • web/data/cmux.schema.json
  • web/messages/ar.json
  • web/messages/bs.json
  • web/messages/da.json
  • web/messages/de.json
  • web/messages/en.json
  • web/messages/es.json
  • web/messages/fr.json
  • web/messages/it.json
  • web/messages/ja.json
  • web/messages/km.json
  • web/messages/ko.json
  • web/messages/no.json
  • web/messages/pl.json
  • web/messages/pt-BR.json
  • web/messages/ru.json
  • web/messages/th.json
  • web/messages/tr.json
  • web/messages/uk.json
  • web/messages/zh-CN.json
  • web/messages/zh-TW.json
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Note

Pull Request opener @azooz2003-bit is not an author or co-author of any commit in this PR (commit identities: teamleaderleo, claude). The CLA check will still proceed and requires every listed identity plus @azooz2003-bit to have signed.

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

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

Labels

needs a call Finished and held for a team design or product decision (see #13742 and the gallery in #15427)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants