Skip to content

feat(agent-isolation): name the blocked command and its directory on the touch overlay - #1318

Merged
potiuk merged 1 commit into
apache:mainfrom
potiuk:feat/overlay-touch-context
Sep 21, 2026
Merged

potiuk merged 1 commit into
apache:mainfrom
potiuk:feat/overlay-touch-context

Conversation

@potiuk

@potiuk potiuk commented Sep 21, 2026

Copy link
Copy Markdown
Member

Summary

  • The touch overlay said a key was waiting and nothing about what for. The window now also names the blocked command and the directory it runs in.
  • That matters because more than one thing can be waiting on the same key — an agent session and a terminal, or two worktrees of one repository that look alike from the outside — and a touch given to the wrong one is not recoverable: the key fires its OTP slot into whatever has focus.
  • A password in a URL (git push https://user:token@host/repo) is masked before it is recorded; nothing else is, and the docs say so, because this is a full-screen window raised at an unpredictable moment.

Type of change

  • Skill change (.claude/skills/<name>/) — eval fixtures updated below
  • Tool / bridge contract (tools/<system>/*.md)
  • Python package (tools/*/ with pyproject.toml) — tools/agent-isolation
  • Groovy reference impl
  • Cross-cutting (RFC, AGENTS.md, sandbox, privacy-LLM)
  • Documentation (docs/, README.md, CONTRIBUTING.md)
  • Project template (projects/_template/)
  • CI / dev loop (prek, workflows, validators)

How it works

arm takes the two values from the hook payload (.cwd, plus the command it already parses); wrap takes them from its own $PWD and argv, since the wrapper is the command's process while the hook only runs beside it.

They go into a per-owner file under context/, keyed like the registration and swept with it — not into the watcher's environment. That is what makes the ride-along case correct: a second command arming into an already-watched session returns before it would spawn anything, so the text has to reach the running watcher through something it can re-read, and the window should name the command actually blocked rather than the one that started the watcher. context/ is a separate directory from owners/ because _sweep_owners reads every entry there as a registration.

The command is flattened to one line and capped at 400 characters on disk (an agent's git commit -F - heredoc carries newlines, and a two-line file cannot hold those). The windows elide again for their own width, keeping the head of a command and the tail of a path.

Test plan

  • prek run --all-files passes (32 hooks, exit 0).
  • uv run --directory tools/agent-isolation --project . python -m pytest — 90 passed, 7 skipped (all pre-existing environmental skips: no gi, no Tk 8.6, sandboxed pgrep/unix-socket).
  • 17 new tests: what arm and wrap record, the ride-along refresh under one watcher, cleanup on disarm and on sweep of a dead owner, one-line flattening, the length cap, and the URL-password masking.
  • End-to-end by hand: armed a session, read back the context file, re-armed the same session with a different command and confirmed the text moved while the watcher pid did not, then disarmed and confirmed nothing was left behind.
  • Not visually verified on screen. The window needs a WindowServer connection, which the sandbox denies (Tk() aborts, _gui_available returns 1), so the rendered layout is unconfirmed — the logic feeding it is covered by the tests above. Worth a reviewer with a hardware key eyeballing one real commit.

RFC-AI-0004 compliance

  • HITL — no new mutation; the change is what an existing window displays.
  • Sandbox — no new host access, no new paths beyond context/ inside the existing magpie-gpg-touch runtime directory.
  • Privacy LLM — nothing reaches an LLM; the new data is local and on-screen only. The URL-password mask is the one deliberate redaction.

Linked issues

None.

Notes for reviewers (optional)

  • elide and context are duplicated across the GTK and Aqua windows. Those are standalone scripts run by whichever interpreter happens to have the toolkit, and they import nothing — the package ships tests only, by design. A test compares the two copies with ast so they cannot drift; extracting them to a shared module would mean a sys.path hack in both, which seemed the worse trade.
  • The masking is deliberately narrow. //user:secret@ in a URL is the one secret shape that routinely rides in an argv this script matches. I did not try to write a general redactor — a partial one invites trust it cannot earn, so the docs state the limit instead.
  • tools/spec-loop/.last-sync is 6 commits behind main; I updated the affected spec by hand rather than bumping the marker, since I have not verified the other five commits' specs.

🤖 Generated with Claude Code

…the touch overlay

The overlay said a key was waiting and nothing about what for. That is
enough when one thing is signing, and not enough the rest of the time:
an agent session and a terminal can both be blocked on the same key,
several worktrees of one repository look alike from the outside, and a
touch given to the wrong one is not recoverable — the key fires its OTP
slot into whatever has focus.

The window now carries two more lines, the command and the directory it
runs in. `arm` takes them from the hook payload (`.cwd`, the command it
already parses), `wrap` from its own `$PWD` and argv, since the wrapper
is the command's process while the hook only runs beside it.

They go in a per-owner file under `context/`, keyed like the
registration and swept with it, rather than into the watcher's
environment. That is what makes the ride-along case right: a second
command arming into a session that is already watched returns before it
would spawn anything, so the text has to reach the running watcher
through something it can re-read — and the window should name the
command actually blocked, not the one that started the watcher. A
separate directory from `owners/` because `_sweep_owners` reads every
entry there as a registration.

A password in a URL is masked before it is recorded: `git push
https://user:token@host/repo` is a documented git spelling, and this is
a full-screen window raised at an unpredictable moment. Nothing else is
scrubbed and the docs say so.

The command is flattened to one line and capped at 400 characters on
disk — an agent's `git commit -F -` heredoc carries newlines, and a
two-line file cannot hold those. The windows elide again for their own
width, keeping the head of a command and the tail of a path.

`elide` and `context` are duplicated across the GTK and Aqua windows,
which are standalone scripts run by whichever interpreter has the
toolkit and import nothing. A test compares the two copies so they
cannot drift.

Generated-by: Claude Opus 5
@potiuk
potiuk merged commit dd40ecd into apache:main Sep 21, 2026
9 checks passed
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