Skip to content

Keep clipboard ownership while a native paste is unresolved #78

Description

@iamnbutler

Current status — implementation landed in #67

Merged as 8fb8ccf from signed head eff5e49. Literal insertion extends the existing shared clipboard gate with content-free unresolved-ownership metadata. Returning unknown does not admit another automated write. A fresh operation can resolve ownership from exact read-only receiver evidence or termination of the original process generation. Delivery, consumption, cleanup, and ownership have separate outcomes; no input is replayed to resolve uncertainty. The implementation retains unresolved ownership across GUI restart without persisting private clipboard contents; the live crash/restart cases below remain unverified.

Verified on the final signed build

  • Two distinct signed native clients exercised a real 10-second trusted-paste delay. A returned unknown with ownership reserved; B refused without input and without changing the clipboard generation. A consumed only its own payload once. A fresh B request then resolved A through native read-only proof and completed once. This is two native callers, not two channels. The first observer stopped when its state file appeared before the matching event log; a narrow continuation verified the original event and ran only the remaining fresh B request. A was never resent. Evidence: /tmp/ace-literal-ownership.9JB9tN/validation-active/result.json and validation-resume/result.json.
  • A real pi/Anthropic channel was stopped during a trusted paste that had actually begun waiting in its receiver. The durable result retained the partial-input warning; the original paste arrived exactly once afterward. Restarting the idle channel worker replayed exact tool/results/images with unchanged provider usage and no new input. This does not establish native completed/consumed/released fields for the stopped result. Evidence: /tmp/ace-literal-channel.5gwa0r/stop/validation-stop/result.json.
  • Completed insertion and the actual Ace composer proved observed consumption, clipboard restoration, and released ownership. Disposable receivers/vaults were settled and closed; final cleanup left no fixture apps and preserved the current clipboard generation.

Still open

Validate separate channel callers, native-client death, response loss, GUI/helper crash and restart while input is pending, exact receiver-process termination/PID-reuse recovery, and newer human clipboard changes on this final implementation. Public persistent clipboard writes (#122, #142, #147) already go through the same unresolved-paste gate; any future temporary paste in #72 must too. Hosted insertion and another physical machine remain broader #65/#8 work. Earlier proofs from other input paths are not evidence for these remaining cases.

Original problem

Keep shared clipboard ownership while a native paste is unresolved.

Source review of the literal insertion work in #67 found that ClipboardPasteTransactionGate.withExclusiveTransaction releases its in-process flag and file lock when the request closure returns. Literal insertion can return unknown, or finish after Stop/client death, while a previously dispatched Cmd+V may still be waiting in the receiver's event queue. It deliberately retains the replacement clipboard instead of restoring private prior contents, but a second channel can immediately acquire the same gate and replace that payload.

The resulting sequence is: channel A queues a paste into a slow receiver; A's verification ends without proving consumption; channel B writes its own temporary payload; A's receiver resumes and pastes B's text. Telling A's agent to inspect before retrying does not coordinate B. This is a source-derived failure path; the cross-channel race has not been reproduced in a live smoke test.

The relevant code is the new LiteralInsert.swift and PeekabooBridgeLiteralInsert.swift in apps/desktop/native/patches/peekaboo-insert.patch, together with Peekaboo's existing ClipboardPasteTransactionGate.swift. The literal insertion prerequisite and the public clipboard work (originally #75, now #72) need the same ownership rule. Track this under full computer use in #8.

Acceptance

  • Extend the existing clipboard transaction gate/ownership mechanism; do not introduce an independent second lock. Other Ace clipboard mutations cannot replace a payload whose native consumption remains unresolved.
  • Preserve that reservation across an unknown result, Stop, client death, and response loss. Report delivery, consumption, clipboard cleanup, and unresolved ownership separately. Never replay the input to resolve uncertainty.
  • A timer expiring, the caller returning, or keys being released is not proof that Cmd+V consumed the clipboard. Resolve ownership using actual receiver evidence or evidence that the original receiver can no longer consume the queued input, including termination of its exact process generation; PID reuse must not release a reservation for a different generation.
  • Define what GUI/helper restart and crash recovery do with an unresolved reservation. Do not silently admit a new automated clipboard write because the previous connection or lock holder disappeared. Recovery must not persist private clipboard contents or insert them into logs or model history.
  • Preserve newer ordinary human clipboard changes. Human copy/paste remains external interference; do not claim the gate controls it or overwrite newer contents to recover an older reservation.
  • Validate with real native receivers and separate channel callers: delayed consumption beyond the normal verification deadline, Stop, native-client death, response loss, and relevant GUI/helper restart. A second attempted automated clipboard mutation must not cause the first receiver to consume the second operation's payload. After verified resolution, subsequent operations should proceed normally without duplicate input.

Related: #8, #67, #72, #76. (#75 was closed as superseded.)

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions