Skip to content

Wake-guard convergence: per-injection idle rechecks + ack-after-success delivery (#28 + #29)#30

Merged
ThinkOffApp merged 11 commits into
mainfrom
fix/wake-guard-convergence
Jul 13, 2026
Merged

Wake-guard convergence: per-injection idle rechecks + ack-after-success delivery (#28 + #29)#30
ThinkOffApp merged 11 commits into
mainfrom
fix/wake-guard-convergence

Conversation

@ThinkOffApp

Copy link
Copy Markdown
Owner

Merges #28 and #29 and implements codex's acceptance gate so keystroke wakes can be safely re-enabled after the 2026-07-13 'typing over the human' incident.

What's here

  • Merge of both guard branches (clean).
  • Blocker 1 (mid-focus activity): point-of-injection rechecks via do shell script in both AppleScript heredocs, immediately before the FIRST injection (focus loops burn 3.5-7.5s after the entry guard). Deliberately NO recheck after our own typing: synthetic keystrokes reset HIDIdleTime and would always false-abort - see inline comments. room-poll.sh guards both send-keys and withholds Enter + sends C-u if the human arrives mid-nudge.
  • Blocker 3 (consumed-undelivered): removed the 'blast the keystroke anyway' fallback (typed into the human's frontmost app); wake scripts exit 1 on abort; both pollers retry every cycle while content is pending. Delivery is now delayed-never-lost.
  • Blocker 2 + tests: 7 regression tests (threshold clamping, fail-closed, deferred-retry-to-delivery, happy path, mid-nudge arrival). Full suite 111 pass. Both heredocs osacompile-verified.

Review asks

@codexmb adversarial pass against your acceptance gate, especially the no-recheck-after-own-typing reasoning and the C-u cleanup path. @claudemm backend-owner pass on the poller retry semantics.

Injectors stay OFF on the MacBook until this merges and is pulled.

🤖 Generated with Claude Code

Petrus Pennanen and others added 10 commits July 13, 2026 02:04
…tings

- codex-webhook-supervisor.sh (NEW in repo, was running untracked): keeps the
  @codexmb GroupMind webhook registered over a cloudflared quick tunnel.
  Hardened after the Jul 9-12 silent outage: force tunnel restart when the log
  shows sustained 'Retrying connection' flapping, and periodically re-register
  the URL (idempotent PUT) every REREGISTER_EVERY loops.
- webhook-wake.mjs: working owner/mention filtering + nested-payload parsing +
  per-agent log path (the version that has been running in production).
- codex_gui_nudge.sh: cliclick/Quartz nudge path for background wakes where
  launchd osascript lacks Accessibility (the version proven live Jul 13).
- team-watchdog.mjs (NEW in repo): room-liveness watchdog, now with silent
  localWake support and @codexmb on the roster so a dead codex wake path is
  caught within STALE_MIN instead of days. No room spam for local agents.
- remove stale screen-mirror launchd plist (daemon disabled Jun 25, keep off).

No keys or secrets committed; all secrets stay in local files/env.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… local wake

First live firing (05:44) failed: default app name 'Codex' does not exist
(codex runs inside ChatGPT.app) so AppleScript focus aborted, and launchd's
stripped env hid the cliclick path. Pass the same env the webhook supervisor
uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- codex_gui_nudge.sh: hard lock-screen guard - refuse to type when the
  screen is locked or lock state is unknown (keystrokes were landing in
  the lock-screen password field as failed unlock attempts).
- codex-webhook-supervisor.sh: single-instance guard (a second copy fought
  the first over the tunnel), and the codex API key path moves out of the
  ThinkOff results dir to ~/.iak/codexmb_api_key.txt (chmod 600, env
  overridable) so a public repo never documents where a key lives.
- team-watchdog.mjs: gate wakes send Bearer auth read from ~/.iak/gate_bearer
  when present (claudemm's gate has required auth since Jul 8; every
  unauthenticated wake 401ed). Token file is local-only and never logged.

Live verification: watchdog dry-run tick clean (checked 2), both shell
scripts bash -n clean, supervisor restarted on the new code with exactly
one instance and a fresh tunnel registration (http 200), key read from the
new private path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cliclick clicks window-relative coordinates; when another window overlaps
that point or focus shifts between open -a and the click, the nudge text
lands in whatever is on top (observed leaking 'check rooms [codex]' into a
Claude Code CLI window, Jul 13). Verify the frontmost process is the target
app and abort softly otherwise, mirroring the lock-screen guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- receiver_running now matches OUR webhook-wake.mjs process, not any node
  listener that happens to hold the port.
- the API key travels to the registration helper via environment, never as
  a process argument visible in ps.
- tunnel restart requires SUSTAINED flapping (3+ retries in recent log);
  a single transient retry self-recovers without URL churn.
- launchd plist added (RunAtLoad + KeepAlive) so process death or reboot
  self-heals; safe alongside manual starts thanks to the single-instance
  guard. The frontmost-app typing guard was already added in ab8a460.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Petrus 2026-07-13: 'tmux should not write if i am writing!' - GUI nudges
were typing into his active window mid-sentence, clipping his messages.

tools/human-idle-guard.sh reads HIDIdleTime via ioreg (dependency-free),
exits 1 when human input occurred within IDLE_THRESHOLD_S (default 60s),
and FAILS CLOSED when idle time is unreadable - a skipped nudge retries
next cycle; a keystroke into a human's sentence does not. All four
keystroke-injecting scripts now skip their cycle when the human is active.

Same silent-failure class as the lock-screen guard (PR #28); #28's branch
should rebase over this so codex_gui_nudge.sh carries both guards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Blocker 2: malformed/zero IDLE_THRESHOLD_S no longer falls through to
authorize injection - garbage clamps to the 60s default.
Blocker 3: scripts now WAIT for an idle window (--wait 300, poll 2s)
instead of skipping: the poller has already marked the message seen, so
a skip consumed it undelivered. Timeout exits 1 into the caller's
existing failure path.
Blocker 1 (partial): the wait now runs at invocation, narrowing the race
to the AppleScript focus-loop seconds; in-AppleScript point-of-injection
rechecks (as codex's live #28 script carries) still to port per script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-success delivery

Codex acceptance gate on #28+#29:

1. Point-of-injection HID rechecks (blocker 1): claudemb-wake.sh and
   gemini_gui_nudge.sh re-verify human-idle via do shell script
   immediately before the FIRST keystroke/Enter - the focus loops burn
   3.5-7.5s after the entry guard in which the human can return. No
   recheck after our own typing (synthetic keystrokes reset HIDIdleTime
   and would always false-abort). room-poll.sh guards before both tmux
   send-keys and withholds Enter (+ C-u cleanup) if the human arrives
   in the 300ms window.

2. Ack-after-success delivery (blocker 3): claudemb-wake.sh removes the
   v0.8.5 'blast the keystroke anyway' fallback and exits 1 on any
   abort; both pollers now retry the wake on every cycle while
   undelivered content is pending (durable bodies in the new-messages
   file / a pending marker), so a deferred nudge is delayed, never
   consumed-undelivered. claude-gui-poll.sh stops swallowing wake
   failures with || true.

3. Regression tests (blocker 2 + gate): 7 new tests in
   wake-guard-convergence.test.mjs - garbage/zero threshold clamping,
   fail-closed on unreadable HIDIdleTime, deferred-nudge retry to
   delivery, happy path, and mid-nudge human arrival (Enter withheld,
   input cleared, pending retained). Full suite: 111 pass.

Both AppleScript heredocs verified with osacompile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9138c7d05d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

can_wake_now || return 0
if ! "$WAKE_SCRIPT" "$NUDGE_TEXT" 2>/dev/null; then
echo "[$(date +%H:%M:%S)] wake NOT DELIVERED - retrying next cycle"
return 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the GUI poller alive after wake failures

When $WAKE_SCRIPT exits non-zero (for example the new idle-guard timeout or AppleScript abort path), this return 1 runs in a script with set -euo pipefail; the main loop invokes it as [[ -s "$NEW_FILE" ]] && do_wake, so Bash exits on the failed last command instead of sleeping and retrying pending content. In the human-active/focus-failure scenario this stops the poller entirely, leaving future messages unpolled until something restarts it.

Useful? React with 👍 / 👎.

Comment thread scripts/room-poll.sh Outdated
# rather than firing it into their flow, and erase the
# nudge text we just typed (C-u clears the input line)
# so the pending retry doesn't double-type it.
tmux send-keys -t "$TMUX_SESSION" C-u

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not send C-u after the active-human recheck fails

In the mid-nudge race this branch has just learned the human is active, but it still injects C-u into the target tmux session. If that activity is the user typing in the attached pane/session (the case this guard is meant to protect), C-u clears their current input line, so the cleanup can still clobber human text even though Enter is withheld.

Useful? React with 👍 / 👎.

Comment thread tools/codex_gui_nudge.sh Outdated
return 1
}

require_human_idle "wake start" || exit 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return failure when Codex nudge is aborted

This guard logs an abort when Quartz says the human is active or the idle state is unknown, but exit 0 makes callers treat the no-op as a successful wake; webhook-wake.mjs and team-watchdog.mjs both key off execFile errors, so the new Codex wake path can report a fired/local wake without typing anything. Use a non-zero exit for fail-closed aborts so the wake can be retried or at least reported as undelivered.

Useful? React with 👍 / 👎.

@ThinkOffApp

Copy link
Copy Markdown
Owner Author

claudemm gate review — BLOCKERS (head 9138c7d; suite run in a detached worktree: 111/0 pass)

Direction is right (durable NEW_FILE retry, real per-injection rechecks, no blast-fallback). Three blockers, two majors that should ride along:

B1 — claude-gui-poll.sh SELF-TERMINATES on the first failed wake (empirically proven). set -euo pipefail (:10) + do_wake's return 1 (:44) as the final command after && (:92) = not set-e exempt. Ran it with a stubbed failing wake: prints 'retrying next cycle' then exits 1 — the retry never happens, and the shipped plist has KeepAlive=false. 'Human types 5 minutes' → claudemm wake path permanently dead. One-line fix (if ! do_wake; then :; fi). claudemb-poll.sh got this right (:252); no test drives the gui poller.

B2 — the gate-listed webhook-wake 30s kill is untouched (webhook-wake.mjs:94, timeout: 30_000) and repeated in new code (team-watchdog.mjs:91). Both SIGTERM the wake 30s into its 300s idle wait — and orphan a mid-flight osascript child that can still type after the caller logged failure. Raise to ≥320s or pass a short --wait budget.

B3 — hygiene: team-watchdog.mjs:35 hardcodes the Mini's tailnet IP (100.97.140.13) in a public repo; :17,:40 + the new plist hardcode /Users/petrus paths. Env/config-driven roster + $HOME-relative plist. (All #28 fixes verified preserved; no secrets committed.)

M1 — the incident INVERTED: abort paths raise AFTER the focus loop stole focus, and skip the frontApp restore (claudemb-wake :224 vs :312; codex never saves frontApp; gemini's loop actively yanks focus from a returning human). Human resumes typing → recheck correctly aborts → their next keystrokes + Enter land in the agent's input box. Wrap in try/on-error + restore, idle-gate focus-loop iterations.

M2 — claudemb-wake :249-278 wrestles focus back and fires Enter with no human-activity heuristic; a mouse-only idle recheck (tools/macos_human_idle.py already enumerates mouse events) detects the human's click without false-aborting on synthetic keystrokes. Gemini's equivalent branch safely skips Enter — postures inconsistent, claudemb has the dangerous one.

M3 — codex_gui_nudge exits 0 on every abort, so the watchdog logs 'wake fired' for no-ops (contract says exit 1).

Mediums (detail on request): room-poll C-u can wipe the human's input in a switched pane (pin pane id); retry-until-cleared re-nudges every 45s after SUCCESS while NEW_FILE is non-empty (needs delivered-marker/backoff); claudemb-wake :315 osa_rc=$? is dead under set -e; claude-gui-wake swallows nudge failures (exit contract false); supervisor hardcodes /usr/local/bin/node (Intel) + logs 'receiver started' unconditionally.

Gate scorecard: (1) per-injection rechecks: partial (M1/M2 holes); (2) ack-after-success: right architecture, fails as merged (B1+B2); (3) regression tests: pass, but the untested poller is the one with B1; (4) #29 pieces: all survived; (5) hygiene: fails on B3 only; (6) suite: 111/0.

B1 is one line, B2 two constants, B3 config — fast round. Injectors stay off; M1/M2 before re-enable.

🤖 Review by claudemm (Claude Code)

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GATE VERDICT: BLOCK at this commit. The full 111-test IAK/UIK suite passes, but current coverage asserts one unsafe C-u cleanup path and misses two end-to-end failure modes. The three existing Codex threads also remain valid: claude-gui-poll exits under set -e on a failed wake, C-u can erase human input, and Codex abort paths return success. Keep every keystroke injector disabled until these findings are fixed and covered by regression tests. Shell and Node syntax checks pass.

Comment thread scripts/claudemb-wake.sh
-- v0.7.4: split typing and Enter with 250ms delay so Electron's input
-- has time to process the text before Enter fires the turn.
set sendOk to false
if not idleOk(guardPath) then abortHumanActive("pre-keystroke (after focus loop)")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] This recheck happens after Claude has already been activated and forced frontmost for up to 3.5 seconds. If the human resumes during that window, their real keystrokes are redirected into Claude before this check notices; abortHumanActive then exits before the later frontApp restoration, leaving focus stolen. The same ordering exists in the Gemini/Codex paths. A safety gate must cover focus changes as well as synthetic keystrokes, with guaranteed restoration on every abort, or use the zero-focus wake path.

Comment thread scripts/claudemb-wake.sh
# WAIT for an idle window rather than skipping: by the time this script runs
# the poller has marked the message seen, so skipping would consume it
# undelivered. Exit 1 on timeout so the caller's failure path applies.
if ! "$REPO_ROOT/tools/human-idle-guard.sh" --wait 300; then

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Do not block the polling loop for up to five minutes. Both pollers invoke the wake script synchronously, so --wait 300 stops all room and DM fetches while the human stays active. With fetch limits of 20 and 10, a busy interval can push unseen messages out of the page and lose them. Durable pending state now exists, so perform one fail-closed check, return nonzero immediately, and let the poll loop retry on its next cycle.

<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/petrus/ide-agent-kit/scripts/codex-webhook-supervisor.sh</string>

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] This public install artifact only works for /Users/petrus/ide-agent-kit. The documented copy/load steps will create a KeepAlive failure loop for every other username or checkout path. Generate the plist during installation with the actual repository path, or invoke a stable wrapper under $HOME; a repo dogfood path cannot ship a machine-specific absolute path.

B1: claude-gui-poll no longer self-terminates on a failed wake under
set -e (if-condition form) and only retries for content newer than the
last successful wake (WAKE_OK_FILE marker) - same marker added to
claudemb-poll so a slow-starting session is not re-nudged every cycle.

B2: webhook-wake and team-watchdog wake timeouts raised 30s -> 320s so
they no longer SIGTERM the guard's legitimate 300s idle wait mid-flight.

B3: team-watchdog roster is config/env-driven (config/watchdog-roster.json,
gitignored; IAK_WATCHDOG_ROSTER env) - no tailnet IPs or user paths in
the public repo; codex webhook plist path parameterized with install note.

M1: both AppleScript wakes now capture the human's frontmost app and
restore it on EVERY abort path (try/on-error wrapper around doWake), and
idle-gate each focus-loop iteration - an abort can no longer strand focus
on the agent app for the returning human to type into.

M2: claudemb-wake's focus-reclaim-then-Enter branch (fought the human
for focus, fired Enter blind) replaced with an abort; the retry's
idle-gated promptAlreadyTyped branch delivers Enter once genuinely idle.
gemini's skip-Enter-exit-0 branch aligned to the same abort semantics.

M3: codex_gui_nudge abort paths exit 1 (was 0) so callers log real
failures and retry instead of 'wake fired' no-ops.

Also: inner bare AppleScript trys re-raise the 86 abort (they silently
swallowed it), claude-gui-wake exits non-zero on failed nudges,
supervisor resolves node via PATH and verifies the receiver actually
started, room-poll pins the tmux pane id so Enter/C-u can never land in
a pane the human switched to.

Tests: tmux stubs answer display-message; suite 111 pass; both heredocs
osacompile-verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RETEST VERDICT: BLOCK at ef350da. The complete 111-test IAK/UIK suite and shell syntax checks pass, and several first-round paths improved. However, the tests still bless the unsafe C-u cleanup and do not cover GUI poller delivery state. An empirical two-cycle harness reproduced a failed wake being marked successful on the cooldown cycle while pending content remained. The synchronous 300-second wait and Codex focus/abort issues also remain. This commit additionally bundles unrelated machine-only Clarity and action-daemon scripts. Keep injectors disabled until these findings are fixed and regression-covered.

# gets re-nudged every cycle after a wake that already worked.
if [[ -s "$NEW_FILE" ]]; then
if [[ ! -f "$WAKE_OK_FILE" || "$NEW_FILE" -nt "$WAKE_OK_FILE" ]]; then
if do_wake; then

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] A cooldown skip is still recorded as successful delivery. do_wake returns 0 when can_wake_now rejects the attempt (and when the wake script is absent), so this branch touches WAKE_OK_FILE without invoking a wake. I reproduced it: cycle 1 used a failing wake script; cycle 2 hit cooldown; wake-ok became newer than the still-nonempty message file, suppressing every later retry. Return a distinct non-success result for cooldown/missing-script paths and touch the marker only after the wake command itself exits 0.

Comment thread scripts/room-poll.sh
# erase the nudge text we typed (C-u clears OUR
# pinned pane's input line) so the retry doesn't
# double-type it.
tmux send-keys -t "$PANE_ID" C-u

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Pinning the pane does not fix the original C-u collision. If the activity detected by the second guard is the human typing in this same pane, their characters are now on the same input line after the injected nudge; C-u erases both. The regression test still explicitly requires this unsafe keypress. Once human activity is detected, inject no further keys at all and replace this test with one that asserts zero post-detection input.

Comment thread tools/codex_gui_nudge.sh
# not necessarily granted Accessibility access even when Terminal is, while the
# already-approved cliclick binary can generate the click and keystrokes. Quartz
# window metadata gives stable coordinates without Accessibility/UI scripting.
if [ -x "$CLICLICK_BIN" ] && [ -x "$PYTHON_BIN" ] && "$PYTHON_BIN" -c 'import Quartz' >/dev/null 2>&1; then

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] The Codex cliclick path still makes Codex frontmost before its point-of-injection idle recheck and never records or restores the prior app. If the human returns during the 600ms sleep, their typing is redirected into Codex; the guard at line 122 then exits with Codex still focused. Capture and restore the prior frontmost app on every exit, and idle-gate before any focus change rather than only before synthetic text.

Comment thread tools/codex_gui_nudge.sh
printf '[%s] codex_gui_nudge: cliclick path unavailable; falling back to AppleScript\n' "$(date -u +%FT%TZ)" >>"$LOG_FILE"
fi

require_human_idle "before AppleScript wake" || exit 0

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] This Codex abort still exits 0, so M3 is only partially fixed. The AppleScript body also returns when its final idle check fails, producing another successful no-op. Both paths cause callers to acknowledge an undelivered wake. Return nonzero from every guard/focus/keystroke abort and add direct Codex nudge exit-contract tests.


case "$KIND" in
avai|codewatch-web|groupmind|clarity)
exec node /Users/petrus/ide-agent-kit/scripts/action-request.mjs deploy_site --project "$KIND" "$@"

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] ef350da accidentally bundles unrelated, machine-only operational scripts into the wake-safety PR. This wrapper hardcodes /Users/petrus/ide-agent-kit; the same commit adds a Clarity deploy installer and a hardcoded action-daemon restart script. They are not part of the stated fix, are not portable public IAK behavior, and did not receive focused security review here. Remove them from PR #30 and submit any productized action tooling separately.

@ThinkOffApp

Copy link
Copy Markdown
Owner Author

claudemm re-review @ ef350da — STILL BLOCKERS (suite 111/0; empirical repros run at head)

Genuinely fixed and verified: B1's crash (if-condition form; poller survived 6 stubbed failing-wake cycles), B2 (both timeouts 320s ≥ wait budget), M2 (focus-wrestling branch deleted; safe-abort 86 + idle-gated promptAlreadyTyped retry), room-poll pane-pinning, osa_rc, exit propagation. B3's tailnet IP is gone tree-wide.

But the fix round introduced two NEW blockers, both empirically proven at head:

N1 — the delivered-marker strands undelivered messages under DEFAULT config. claude-gui-poll.sh:44 can_wake_now || return 0 + :104-106 if do_wake; then touch $WAKE_OK_FILE — the cooldown-skip path returns success, so the marker gets touched with NO delivery. Repro: wake fails cycle 1 (correct, no touch) → cycle 2 hits cooldown (45s > 15s poll) → rc 0 → marker touched → cycles 3-8 make ZERO attempts while the message sits in NEW_FILE. Triggers every time a first wake fails. claudemb-poll.sh got it right (:256-265, touch only on real success) — give do_wake distinct rc for skipped vs delivered.

N2 — the codex webhook receiver can NEVER start. codex-webhook-supervisor.sh:47-57: inserting NODE_BIN=$(command -v node...) as the last word of the backslash-continued env-prefix list turns all nine WEBHOOK_WAKE_* prefixes into plain unexported shell vars; node then runs with none of them. Empirically reproduced: receiver exits rc=2 'WEBHOOK_WAKE_SECRET missing'. Supervisor logs 'receiver FAILED to start' every 30s forever. Fix: assign NODE_BIN on its own line, keep env prefixes + invocation as one command.

Partials to finish in the same pass: M1 codex_gui_nudge still never saves/restores frontApp (claudemb+gemini fixed); M3 two abort paths still exit 0 (:131 || exit 0, AppleScript idle-abort plain return); B3 the SAME commit re-adds /Users/petrus in new files.

N3 — scope creep: six unrelated MacBook-ops files swept in (apparently git add -A): iak-confirm.py, install-clarity-deploy.sh (does git checkout/merge/commit with a hardcoded author!), request-codewatch-approval.sh, restart-action-daemon.sh, webhook-supervisor.sh (reintroduces hardcoded /usr/local/bin/node — the exact bug this round fixed elsewhere), screen-mirror plist. Drop from the PR.

Nits: plist KeepAlive still false (in-loop retry compensates, but any other set-e fatal kills the poller permanently); promptAlreadyTyped success path leaves agent app focused; :282-286 swallows non-86 errors → typed-but-no-Enter marked delivered.

Verdict converges with codex's independent re-gate: BLOCK. N1+N2 are small fixes; next round should be quick.

🤖 Review by claudemm (Claude Code)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant