HomeMethodClipboard failure ladder
Copy does nothing: finding the clipboard layer that fails silently
Clipboard bugs are almost never exceptions. The write resolves, the toast appears, and the target still pastes yesterday's text — because between your function call and the paste there are five independent gates, and three of them fail quietly. This is the failure half of a clipboard bridge. The success path, how a host channel reaches a system writer, is documented by the project's own case study, which we are not affiliated with.
Independent guide. Behaviour and failure modes, reconstructed from public docs and a published trace. Not official documentation for REA or for any app named here. This page does not read another application's clipboard, and it does not help collect pasteboard data.
The five-gate ladder
Test top-down. Every rung has an observable that is different from its neighbour.
| # | Gate | What it really looks like | Check in one line |
|---|---|---|---|
| 1 | Secure context | navigator.clipboard is undefined — no error, no promise, nothing to catch | isSecureContext is false on plain HTTP and on LAN-IP dev origins |
| 2 | User activation | Write rejected with NotAllowedError; reads pop a menu 1 s too late for your test to notice | Called from setTimeout, an async continuation, or an observer callback? Activation is gone |
| 3 | Permission / browser policy | Chrome asks once then remembers; Firefox and Safari show an ephemeral Paste menu instead — and never grant the permission your code waits for | Any branch gated on clipboard-write permission state hangs there |
| 4 | Frame policy | Silent deny inside an embed: same code works top-level, fails in the iframe, empty console | Chromium needs clipboard-read and clipboard-write allowed on the frame |
| 5 | Host / IPC | Everything above passes, the promise resolves, and the paste is still wrong — the drop happened after the browser | Sender check, channel name, format mismatch, or a stale private payload |
Where the silent drops live. Gates 1, 3 and 5. Gates 1 and 3 produce no exception at all, and gate 5 is a host-side decision your JavaScript cannot observe. Most "clipboard is broken" reports are one of those three, and all three look identical from the UI.
Gate 2 is the one people blame the API for
MDN's security section separates read from write. A read is specified to need recent transient user activation plus the user actually acting on a paste affordance — though in practice browsers often allow reads that do not satisfy those requirements, and impose other requirements instead. A write expects the clipboard-write permission, and the browser may also require transient user activation.
- Chromium: writing requires either the
clipboard-writepermission or transient activation, and once the permission is granted it persists. A background retry that failed the first time may succeed later for reasons unrelated to your fix. - Firefox and Safari: writing requires transient activation, and the
clipboard-read/clipboard-writepermissions are not supported and not planned. A feature-detect copied from a Chrome-centric blog waits on a permission that will never exist. - The 1-second detail: on Firefox and Safari a disallowed read surfaces as a one-item context menu whose Paste option becomes enabled after 1 second. Automated tests time out while a person clicking through never sees a problem. The paste prompt is suppressed for same-origin content, not cross-origin.
- Workers: the API is not exposed via
WorkerNavigator. Moving clipboard I/O into a worker does not fail loudly; the object is simply not there. - Sync listeners:
clipboardchangefires only with sticky activation, or afterclipboard-readis granted. A listener that never fires is usually this.
Nothing here reads your clipboard. It only reports what the environment permits.
Secure context
checked on click
Activation
checked on click
Permissions
per browser — see the table above
Frame
checked on click
Press Check now. Nothing here reads or writes the clipboard.
// press Check now — results are computed here, not assertedCompare the two samples. Same page, same browser, 200 ms apart — the difference is whether you are still inside the gesture. That gap is gate 2.
Gate 5: the failure that happens after the browser
Electron apps do not write the clipboard from the page. They post an IPC message that a main-process handler may or may not act on. The published Notion trace (Notion Desktop 7.6.1) shows the shape as a ternary:
// From the public case study, names restored for reading.
// If the sender check fails, the handler returns undefined.
// No exception. No log. Nothing was copied.
handleRequestFromRenderer.addListener(
"notion:clipboard:write",
(event, data) =>
isNotionWebContents(event.sender)
? clipboard.write(data)
: undefined
);Read that as a debugging rule: in a bridged app, "the call succeeded" and "the clipboard changed" are two different claims, and only the second one matters. The gate between them is a sender identity check. It fails for a webview that is not the app's own, a secondary window, or a call injected from devtools, and nobody logs it.
The same case study describes a second half: a rich app often writes two payloads — a public one (text plus html, sometimes with a marker comment in the HTML) and a private one keyed to locally saved data. On paste it looks for the marker, matches it against the local store, and when the entry is stale it degrades to text and HTML only. That is the usual rich-text complaint: copy inside the app and paste inside the app looks perfect; paste into another app, or paste after a restart, loses structure.
The first test. Paste into a plain-text target, then into the same app, then into the same app after restarting it. Three targets, three payloads. If they differ, you are looking at a private-format lookup that failed. Rewriting writeText will not fix that.
How to make the agent show its work
Trace the copy path only. Report each hop with its evidence — file,
line or column range — and stop at the system clipboard call. Do not
explain the app's design. Then state two things separately:
(1) what you confirmed exists, (2) what you saw but could not confirm
is reached. Name every silent return or no-op branch you find.A summary of a bridge will not volunteer that the else-branch of a ternary discards the payload, because nothing in the code looks like an error. Ask for silent branches by name. Related: step 4 of the loop, and the acceptance window when the deciding half is not in the client at all.
Findings and non-findings
| Claim | Evidence | Confidence | What it does not prove |
|---|---|---|---|
| Gate 1–5 symptoms and the browser divergence | MDN Clipboard API, page last modified 5 Mar 2026 | High | Your browser build is not the doc; test in the build you ship |
| A sender check can discard a write with no error | Excerpt in the public Notion case study (Notion Desktop 7.6.1, app.asar, main index.js) | High | That specific build only. Other Electron apps differ |
| Stale private-format entry degrades to text + HTML | Same case study, web assets cached 13 Jul 2026 | Medium | That page states the probes describe the probes rather than a captured desktop session — no real paste was observed |
| Linux/Wayland clipboard managers drop writes | — | Not verified here | Widely reported, not tested by us. Treat it as a hypothesis to rule out |