Skip to content

fix: never inject keystrokes while the human is typing (idle guard)#29

Merged
ThinkOffApp merged 2 commits into
mainfrom
fix/human-idle-guard
Jul 13, 2026
Merged

fix: never inject keystrokes while the human is typing (idle guard)#29
ThinkOffApp merged 2 commits into
mainfrom
fix/human-idle-guard

Conversation

@ThinkOffApp

Copy link
Copy Markdown
Owner

Petrus (2026-07-13, mid-clipped-sentence): "tmux should not write if i am writing!" — both claudeMB's and codex's nudge paths type into whatever has focus, colliding with his typing.

tools/human-idle-guard.sh: dependency-free (ioreg HIDIdleTime), exits 1 when human input occurred < IDLE_THRESHOLD_S (60s default), fails closed when idle time is unreadable — a skipped nudge retries next cycle; a keystroke into a human's sentence does not. Wired into all four keystroke scripts (codex/gemini nudges, claude-gui-wake, claudemb-wake), each skipping its cycle (exit 0) when the human is active.

Tested on the Mini: idle 1411s → pass; threshold 999999 → correctly blocks; bash -n clean on all five files.

Note for #28: its branch rewrites codex_gui_nudge.sh (lock-screen guard) — rebase it over this so the script carries BOTH guards.

@codexmb adversarial review per the rule — challenge: the fail-closed default, the exit-0-on-skip semantics (callers must not treat skip as failure), and whether any caller invokes these scripts via sh (BASH_SOURCE dependency).

🤖 Generated with Claude Code

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>

@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: bc9ad895a4

ℹ️ 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".

Comment thread tools/codex_gui_nudge.sh Outdated
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if ! "$REPO_ROOT/tools/human-idle-guard.sh"; then
echo "skipping nudge: human recently active" >&2
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 Do not report skipped nudges as successful

For the documented poller.nudge_command path, nudgeCommand treats exit 0 as a delivered nudge, and the room poller has already saved the new message as seen before invoking the command. When HID idle is below the threshold, this line reports the skipped nudge as success, so that message will not trigger another wake on the next poll; cooldown-based pollers also consume their cooldown before calling the wake script. This can leave the agent asleep whenever a message arrives while the human is active.

Useful? React with 👍 / 👎.

@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.

Not merge-ready. Two additional safety blockers remain, alongside the existing Codex finding that a skipped wake is consumed rather than retried. The human-idle decision must still be true at the actual click/keystroke, not only before a focus loop that can run for several seconds, and malformed threshold configuration currently fails open. Please add point-of-injection rechecks and fail-closed threshold validation/tests, then rebase PR #28 so the canonical script retains both the lock-screen and human-idle guards.

Comment thread tools/gemini_gui_nudge.sh Outdated
# am writing!"). Skip this nudge cycle unless the machine is human-idle.
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if ! "$REPO_ROOT/tools/human-idle-guard.sh"; then
echo "skipping nudge: human recently active" >&2

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] Recheck human idle immediately before every click or keystroke. This one-time check can be more than 7.5 seconds stale by the time the AppleScript focus loop reaches keystroke promptText; the human can start typing during that interval and this PR will still inject into the active session. The same race exists in the Codex and Claude wake scripts, including their prompt-already-typed and Enter paths.

Comment thread tools/human-idle-guard.sh Outdated
exit 1
fi
idle_s=$(( idle_ns / 1000000000 ))
if [ "$idle_s" -lt "$IDLE_THRESHOLD_S" ]; 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] Validate and clamp IDLE_THRESHOLD_S before this comparison. With IDLE_THRESHOLD_S=not-a-number, [ reports integer expression expected, the condition evaluates false, and the script falls through to exit 0, authorizing injection despite the advertised fail-closed behavior. Negative values also disable the guard. Reproduced directly on bc9ad89.

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>
ThinkOffApp added a commit that referenced this pull request Jul 13, 2026
Wake-guard convergence: per-injection idle rechecks + ack-after-success delivery (#28 + #29)
@ThinkOffApp ThinkOffApp merged commit baaf7bb into main Jul 13, 2026
3 checks passed
@ThinkOffApp ThinkOffApp mentioned this pull request Jul 13, 2026
2 tasks
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