LearnNewsExamplesServices
Frontmatter
id17231
titleThe wake adapter verifies the target holds focus, then types into a FRESH frontmost read — a TOCTOU that delivers one seat''s wake into another''s window
stateClosed
labels
bugai
assigneesneo-opus-vega
createdAtAug 16, 2026, 2:52 AM
updatedAtAug 16, 2026, 9:22 PM
githubUrlhttps://github.com/neomjs/neo/issues/17231
authorneo-opus-vega
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 16, 2026, 9:22 PM

The wake adapter verifies the target holds focus, then types into a FRESH frontmost read — a TOCTOU that delivers one seat's wake into another's window

neo-opus-vega
neo-opus-vega commented on Aug 16, 2026, 2:52 AM

Context

Split from #17227's investigation. That ticket asks whether to retire the osascript adapter; this is a defect inside it that should be fixed regardless, because the two Codex seats have no alternative transport and will keep using this path.

Observed end-to-end: a wake addressed to @neo-opus-ada was delivered into @neo-fable-clio's session (~00:47Z). The operator confirms he was writing to Clio at that moment; Clio's transcript independently puts him typing there at ~00:45–00:46Z. The payload followed the operator's focus.

The Problem

ai/daemons/wake/localWakeAdapters.mjs, three lines apart:

:659   if not targetRaised then my assertTargetFrontmost(…)   -- CHECK: is the target frontmost?
:660   tell application "System Events"
:661     set frontmostProcess to first application process whose frontmost is true   -- RE-READ
:662     tell frontmostProcess                                 -- USE: type into the re-read

The process that is verified and the process that is typed into are two different reads. assertTargetFrontmost proves the target holds focus; the very next statement discards that result and asks the system again for "whoever is frontmost right now", then sends every keystroke there.

Classic time-of-check-to-time-of-use. The window is not theoretical — the keystroke sequence that follows carries delay 0.5 after the tab shortcut and delay 0.2 between seed keys, so focus has hundreds of milliseconds to move while the payload is being typed.

This is why the third failure class reports success. The -2700 errors in the receiver log are assertTargetFrontmost working: focus was lost before the check, and the delivery aborted loudly. When focus is lost after the check, nothing errors — the keystrokes simply land in the wrong window, the adapter returns success, and the wake is recorded as delivered.

It also explains the operator's symptom exactly: "focuses harness prompt fields, not always copies" — the tab shortcut lands in the right window, focus moves during the delay, and the paste goes somewhere else.

The Fix

Bind the keystroke target once. Resolve the verified process into a variable and tell that process, rather than re-resolving first application process whose frontmost is true. The target's pid and bundle id are already known — targetProcessId and targetBundleId are computed at :641 and :637.

Re-asserting frontmost between keystrokes is a possible second layer, but the primary fix is not asserting more often — it is never typing into a process the check did not approve.

Acceptance Criteria

  • Keystrokes are sent to the process assertTargetFrontmost verified, never to a fresh frontmost read.
  • Red-proved: with focus moved to another application between the check and the keystrokes, the delivery fails or lands in the target — it must not type into the window that stole focus. A test that only passes with focus undisturbed does not exercise this.
  • A delivery that cannot complete against the verified target is recorded as undelivered, so a misroute can never again be reported as success.
  • The existing -2700 behaviour is unchanged: losing focus before the check must still abort loudly.
  • No new dependency on the operator not touching his machine — the fix must be correct while the machine is in use, which is the condition it exists for.

Out of Scope

  • Retiring the adapter#17227 owns the transport question. This fix must land whether or not that happens, because the Codex seats have no alternative today.
  • The identity→target mapping. Whether @neo-opus-ada's route points at the right window is a separate question; this defect misroutes even when the route is perfectly correct.
  • Delivery concurrency between our own writers. Not the competitor — the operator is.

Avoided Traps

  • "The guard is missing." It is not. I proposed adding a frontmost check to #17227 before reading the code; it already exists and works. The defect is that its result is discarded one line later — which is worse than a missing guard, because the log looks like a working guard.
  • Reading the -2700 count as the failure rate. Those are the deliveries that failed safely. The dangerous ones produce no log line at all.
  • Adding more delay or more retries. Both widen the window this defect lives in.

Evidence class

L1 for the misroute — observed by the receiving seat, with the operator and the receiver's transcript independently placing his focus in that window at delivery time. L2 for the mechanism — read directly from the adapter source at dev, lines 659–662.

Related

#17227 (the transport question this was split from) · #16741 (ingress delivery) · #15405 (wake policy)

Origin Session ID: 5cd926fa-77e1-4309-8bbf-ca563ab07403

Retrieval Hint: query_raw_memories("wake adapter TOCTOU verifies target then types into fresh frontmost read") · falsification anchor: ai/daemons/wake/localWakeAdapters.mjs:659-662 — the check and the tell resolve the process twice.

tobiu referenced in commit 126c4ed - "fix(ai): the wake adapter types into the process it verified, not a fresh frontmost read (#17231) (#17233) on Aug 16, 2026, 9:22 PM
tobiu closed this issue on Aug 16, 2026, 9:22 PM