fix(agent-isolation): keep the touch overlay's state out of /tmp - #1306
Merged
Merged
Conversation
`RUNTIME_DIR` fell back to `/tmp/magpie-gpg-touch` wherever
`XDG_RUNTIME_DIR` is unset, which on macOS is always. That is wrong
twice over.
`/tmp` is world-writable, so another local user can pre-create the
directory and sit on the pid files and the lock the window is leased
through. And it is outside the write set of the sandbox this framework
ships, so a signed commit from a sandboxed agent dies at
gpg-touch-wrap-ssh-keygen: line 340:
/tmp/magpie-gpg-touch/watcher.pid: Operation not permitted
Couldn't sign message (signer): agent refused operation?
before any watcher starts: no window, no touch, and gpg-agent gives up.
The error names the watcher rather than the key, which is what makes it
read like broken signing rather than a denial.
The fallback is now `${XDG_CACHE_HOME:-$HOME/.cache}/magpie-gpg-touch`.
`$TMPDIR` would have been the obvious per-user answer and is the wrong
one: it differs between the signing contexts that have to find each
other — the agent's hooks see the harness's scratch directory, a
terminal `git` sees the login one — and two contexts computing two
runtime directories cannot share an owners registry or a window lease,
which is the whole design. The cache directory is stable for a user
whatever spawned the process, and everything kept there is regenerable.
The hint hook needed a branch of its own, ahead of the ssh-agent one:
this failure also ends in `agent refused operation`, so that branch
claimed it and sent the reader to the entry about an unreachable key
when the key was never the problem.
Adds a `_runtime_dir` test seam in the idiom of the existing ones, so
the three new tests assert the script's own value rather than a copy of
the expression under test.
Generated-by: Claude Opus 5
potiuk
added a commit
to potiuk/magpie
that referenced
this pull request
Sep 21, 2026
The marker sat at 0e7012b, where apache#1306 left it, while apache#1305 and apache#1307-apache#1310 landed on top. Reviewing that delta: - apache#1305 (stats events cache `updatedAt`) and apache#1307 (dev version bump) touch no behaviour any spec describes; no spec references `fetch_issues` or the events cache. - apache#1308, apache#1309 and apache#1310 are the vetted-ops sandbox work, and the `excludedCommands` section this branch already rewrites is where they belong. That rewrite covers all three. So the delta is documented and the marker can move. This is the by-hand route AGENTS.md allows for a small known gap, not a full `loop.sh update` pass — it reconciles these five commits rather than re-auditing every skill, tool and modes.md row. Generated-by: Claude Opus 5
15 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/tmp.RUNTIME_DIRfell back to/tmp/magpie-gpg-touchwhereverXDG_RUNTIME_DIRis unset, which on macOS is always./tmpis world-writable, so another local user can pre-create the directory and sit on the pid files and the lock the window is leased through./tmpis outside the referenceallowWrite, so the wrapper cannot create its pid file, no watcher starts, nothing puts a window on screen, the key is never touched, and gpg-agent gives up withagent refused operation. The error names the watcher, not the key, which is what makes it read like broken signing rather than a denial.${XDG_CACHE_HOME:-$HOME/.cache}/magpie-gpg-touch.XDG_RUNTIME_DIRis still preferred where the platform provides it.Observed while committing #1304: every pre-commit hook passed and the commit then died at the signature.
Type of change
.claude/skills/<name>/) — eval fixtures updated belowtools/<system>/*.md)tools/*/withpyproject.toml)docs/,README.md,CONTRIBUTING.md)projects/_template/)prek, workflows, validators)tools/agent-isolation/*.sh) + its pytest suiteTest plan
prek run --all-filespassesuv run pytest/ruff check/mypypassesXDG_RUNTIME_DIRwins, the fallback is under$HOMEand never bare/tmp,XDG_CACHE_HOMEis honoured) and one pinning the hint-hook routing.RFC-AI-0004 compliance
Nothing widens. The change moves state out of a world-writable directory and into one already in the reference
allowWrite, so a signed commit stops needing a sandbox bypass rather than being granted one.Linked issues
Refs #1304 — found while committing it. Same family as the
TMPDIRandunix://defects fixed there: a path assumed available that the environment does not actually provide.Notes for reviewers
Why not
$TMPDIR. It is per-user and writable, so it looks like the obvious answer, and it is wrong here. It differs between the signing contexts that have to find one another — the agent's hooks see the harness's scratch directory, a terminalgitsees the login one. Two contexts computing two runtime directories cannot share an owners registry or a window lease, and that sharing is the whole design of the ownership model added in #1301. The cache directory is stable for a user whatever spawned the process, and everything kept there is regenerable: pid files swept by the nextarm, a lease reclaimed when its holder dies.XDG semantics. Runtime state in a cache directory is a compromise. On Linux, where
XDG_RUNTIME_DIRexists, nothing changes and the correct directory is still used. The cache fallback only applies to platforms that provide no runtime directory at all, and the contents genuinely are regenerable.The hint hook was mis-routing this failure. Its first branch matches
agent refused operation, so it claimed this error and pointed the reader at "SSH agent / Yubikey appears unreachable" — the wrong entry, since the key is fine and the watcher is what never started. The new branch is deliberately ordered ahead of it, and a test asserts both that the right anchor appears and that the ssh-agent one does not.Test seam.
_runtime_dirfollows the existing_signing_in_flight/_agent_sockets/_lease-probeseams. I tried asserting viaarmfirst; it needs a GUI and a JSON payload, and on a host without Tk it returns early having created nothing, so it cannot observe the path.A probe is deliberately missing. The catalog says a new entry should also grow a doctor probe. I did not add one: a seventh probe cascades into the skill's
## The 6 probesheading, its frontmatter description, the eval output spec, every fixture and the case counts — and the failure is now fixed at source, so the probe would only catch a stale install. Happy to add it if you'd rather the catalog and the doctor stay strictly in lock-step.Your installed copy is behind. This fixes the framework; an operator's
~/.claude/scripts/copy keeps the old fallback until they update it, so signed commits there keep needing a bypass. Nothing in this PR touches an installed copy.🤖 Generated with Claude Code