Skip to content

fix(agent-isolation): show the touch overlay for every signature - #1832

Merged
potiuk merged 2 commits into
apache:mainfrom
potiuk:fix/gpg-touch-overlay-dismissal
Oct 26, 2026
Merged

potiuk merged 2 commits into
apache:mainfrom
potiuk:fix/gpg-touch-overlay-dismissal

Conversation

@potiuk

@potiuk potiuk commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Summary

  • A touch window that closed without an answer was counted as a dismissal. That covers the macOS window closing when it loses the keyboard (fix(agent-isolation): close the macOS touch overlay when it loses the keyboard #1325), a missing toolkit, or a crash. The dismissal then lasted until every armed session had disarmed. With a second agent session open, one stray click hid the overlay for every later signature, and git waited on the key with nothing on screen.
  • A dismissal now names the signer processes its window was drawn for, and holds only while every one of them is alive and no signer it does not name is running: an unrelated long-lived gpg cannot keep it, and a signature started since, in this session or another, gets its own window. Only the user's own processes count; another user's gpg cannot be signalled, and kill -0 would read it as ended. No dismissal lasts more than a minute, so a long-lived gpg it happened to name (a gpg --card-edit) cannot keep it; a signature still waiting after that gets its window back. An ssh authentication has no signer to name, and the first gpg or ssh-keygen after it removes its dismissal. A window that closes unanswered is shown again after 3 s. Through that delay the watcher keeps the window lease, so another session's watcher cannot redraw the window early and take the keyboard straight back.
  • The Linux zenity fallback is no longer exec'd, so its exit status can be translated: 0 and 1 (OK, Esc, close) count as answered, anything else as unanswered. Its termination trap is installed before the dialog starts, so no dialog outlives the overlay that drew it.

Type of change

  • Python package (tools/*/ with pyproject.toml)
  • Other: tools/agent-isolation/gpg-touch-overlay.sh and the macOS window

Test plan

  • tools/agent-isolation/tests/test_gpg_touch_overlay.py passes on macOS (serially, and repeatedly under -n auto as CI runs it) and in a Linux container (python:3.12, bash 5.2). Linux runs the zenity-path tests that macOS skips.

  • New watcher tests:

    • an unanswered window is shown again (exit 3 and exit 1)
    • a dismissal answers only its own signature
    • a dismissal does not carry over to an overlapping signature
    • the dismissal names the signers the window was shown for
    • an ssh-authentication dismissal does not cover a later signer (checked to fail without the change)
    • a dismissal ends with its own signer, not a bystander gpg (checked to fail without the change)
    • a dismissal does not cover a signer it does not name (checked to fail without the change)
    • signers are the current user's processes only (checked to fail without the change)
    • no dismissal outlasts its minute, even with a named process alive (checked to fail without the change)
    • a superseded ssh-authentication dismissal does not come back (checked to fail without the change)
    • a second watcher waits out the delay of an unanswered close (checked to fail without the lease change)
    • zenity cancel is a dismissal and a zenity failure is not
    • taking the zenity window down closes the dialog
  • prek run --from-ref origin/main passes, as does tests/test_hook_manager_shim.py after merging main (which carries test(agent-isolation): keep the shim test's commit from signing #1831's fix for the shim test's signed commit).

  • The tests that start a fake signer now take turns across pytest workers (a lock in process_named): every worker sees the same process table, so under -n auto one test's fake ssh-keygen was another test's signature in flight.

RFC-AI-0004 compliance

  • Sandbox: no new host access. The watcher reads the same process and socket state as before.

Linked issues

Refs #1325

Notes for reviewers (optional)

Known follow-ups, not in this PR, both about how a dismissal identifies its signature:

  • With an agent session armed while a terminal signs, the wrapped watcher names its wrapper shell and the hook watcher the real gpg, so each rejects the other's dismissal and the window can come back for the same signature (extra windows only).
  • An ssh authentication has no signer process, so its dismissal is bounded only by its minute: another ssh operation started within it gets no window. Before this PR the same dismissal lasted until every session went idle.

A false positive here costs one extra window; a false negative is a silent block. Where those trade off, the change errs to the window: an unrelated gpg that the dismissal named and that exits brings the window back for a signature already dismissed.

Split from the matcher fix in a separate PR, which makes more commands arm the overlay. The two are independent and either can merge first.

🤖 Generated with Claude Code

@potiuk potiuk added family:tools tools/* capability:fix Implement code change to resolve an issue substrate:sandbox Tool substrate: agent isolation, egress control, settings audit labels Oct 25, 2026
@github-actions github-actions Bot added family:setup setup-* skills capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) labels Oct 25, 2026
@potiuk
potiuk enabled auto-merge (squash) October 25, 2026 23:03
Comment thread tools/agent-isolation/tests/test_gpg_touch_overlay.py Fixed
A window that closed without being answered (the Aqua overlay losing the
keyboard to another application, a missing toolkit, a crash) was taken
for a dismissal, and a dismissal was kept until every armed session had
disarmed. With another agent session open, one stray click silenced the
overlay for every later signature, and git waited on the key with
nothing on screen.

Generated-by: Claude Opus 5
@potiuk
potiuk force-pushed the fix/gpg-touch-overlay-dismissal branch from 1b6d39e to 7f04375 Compare October 26, 2026 00:13
pytest.skip raises, but CodeQL cannot see that, so a loop that returned
the process from inside and fell through to the skip read as mixed
explicit and implicit returns.

Generated-by: Claude Opus 5
@potiuk
potiuk merged commit 0ba1af2 into apache:main Oct 26, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capability:fix Implement code change to resolve an issue capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) family:setup setup-* skills family:tools tools/* substrate:sandbox Tool substrate: agent isolation, egress control, settings audit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants