Repository navigation
Revoke the local clipboard offer when the clipboard changes - #63
Merged
Merged
Conversation
When the active host's offer took the local clipboard, watch_local returned before sending anything, so every Bridge kept the previous local offer. File requests are served from the local_files snapshot after a serial check alone, so any connected host, including a background tab, could still request the last locally copied files by index and read their current contents. watch_local now sends RevokeLocal to every session on each clipboard change, before the file listing starts and before the early return for the server's offer. The Bridge clears the current offer, its mime types and its files, and aborts the transfers it is serving through a new Transfers::abort_serving. While the server's offer holds the clipboard no local offer follows, so every request is refused until the next local copy. The serial counter is kept apart from the current offer, so serials keep increasing across revocations and a stale request cannot match a reused one. A file request whose open was in flight during a revocation is refused once the open completes. Transfers::serve also aborts a serve it displaces when a peer reuses a request id. Before, the second serve replaced the first one's abort handle without aborting it, so the first task kept streaming with no handle for abort_serving to reach. The ignored clipboard test that scripts/e2e.sh runs now also offers a copied file to two sessions through watch_local, puts the server's offer on the clipboard with set_remote_offer, checks that both sessions are revoked and get no new local offer, and shows a Bridge refusing a request for the old offer's file. It clears the clipboard before it ends, so it leaves no selection behind for the next e2e step.
This was referenced Oct 16, 2026
[Medium impact] The PR appears safe to merge; no actionable issues were found. SummaryThe client now withdraws its previous clipboard offer from every session whenever the clipboard changes.
TechLuddite explicitly defers notifying the host about revocation to #68. Cancellation on clipboard changes is intentional and documented. Tests were inspected but not run during this review.
|
dhh
pushed a commit
that referenced
this pull request
Oct 24, 2026
Every running session received every local clipboard offer, so the host in a background tab could request each copy without a paste (#29). Local offers now go only to the session the window shows. This builds on the revocation from #63: each local change still revokes the offer in every running session first, and only the offer that follows is limited to the shown session. Switching tabs, or showing no session, withdraws the offer from the session that was shown. It gets RevokeLocal, which refuses further requests and aborts what it is serving, then an empty offer, which tells the host to drop the selection it advertised for us. The newly shown session is then offered the current local selection, since the clipboard reports only changes. watch_local takes the running sessions for revocations and the shown session for offers, and asks for both when it sends, so a slow file listing that finishes after a switch goes only to the shown session. Each local offer carries the UI's offer generation, and the bridge hands it back with every read. When the read starts, the UI checks that the session is still shown and the generation is still the latest, and refuses it otherwise. A request the worker accepted before a switch or a new copy can therefore not read what the clipboard holds by then. The ClipboardRead handler body is serve_local_read, which the tests call too. The offer routing is display-free where it can be (LocalSelection's changed, begin, finish and generation), so the tests drive the same routing functions as the app. The ignored e2e clipboard test now has one shown session and checks that a hidden session is only revoked. --no-clipboard and offers from the host are unchanged. The README says which machine receives local copies, and no longer says nothing is sent until you paste, since a host can ask for the contents without one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a host's offer takes the local clipboard, the client did not revoke its previous local offer.
watch_localreturned early for the server's proxy provider, so no new LocalOffer reached any session, andBridge::on_requestserves file requests from thelocal_filessnapshot after checking only the serial. Every connected host, including a background tab, could still request the last locally copied files by index and receive their current contents. #43 left this gap.The client now revokes the local offer in every session on each clipboard change, before the file listing and before that early return. A revocation clears the offer, its MIME types and files, and aborts transfers being served. The revocation is local to the client, and #68 adds the message that tells the host. Serials keep increasing across revocations. A file open still in flight during a revocation is refused when it completes, and a serve displaced by a reused request id is aborted. Wire messages are unchanged.
Validation:
scripts/e2e.shruns (step 5b) now driveswatch_localwith a realRemoteProviderand checks that both of two sessions are revoked. It passed in all eight e2e runs (nested Hyprland, AMD Radeon 840M). The intermittent step 6 failures in those runs are the e2e race that Wait for the client's clipboard item in e2e, not a MIME type #65 fixes.scripts/check.shpasses.#68 builds on this one and offers the local clipboard only to the shown session. This conflicts with #80 in the clipboard bridge. I am happy to rebase whichever lands second.
Opus 5.5 high, on behalf of TechLuddite.
Implemented with Claude Opus 5.5 at high effort and reviewed by GPT-6.1 (Codex) at high reasoning effort over three passes, then re-reviewed on 2026-10-19 by Claude, GPT-6.1 and Grok 4.7 at medium effort.