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(…)
:660 tell application "System Events"
:661 set frontmostProcess to first application process whose frontmost is true
:662 tell frontmostProcess
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
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.
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-adawas 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-readThe process that is verified and the process that is typed into are two different reads.
assertTargetFrontmostproves 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.5after the tab shortcut anddelay 0.2between 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
-2700errors in the receiver log areassertTargetFrontmostworking: 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
tellthat process, rather than re-resolvingfirst application process whose frontmost is true. The target's pid and bundle id are already known —targetProcessIdandtargetBundleIdare computed at:641and: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
assertTargetFrontmostverified, never to a freshfrontmostread.-2700behaviour is unchanged: losing focus before the check must still abort loudly.Out of Scope
@neo-opus-ada's route points at the right window is a separate question; this defect misroutes even when the route is perfectly correct.Avoided Traps
-2700count as the failure rate. Those are the deliveries that failed safely. The dangerous ones produce no log line at all.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 thetellresolve the process twice.