fix(probe): name the probe by a space-free link so Codex rules match the macOS app
Problem
Codex sessions still asked to approve every relayed read and reply whenever the Agent Relay core runs from a path with spaces — always the case for the macOS desktop app (/Applications/Agent Relay.app/Contents/Helpers/Agent Relay Helper).
Codex's execpolicy only reads a shell command whose program is a plain, unquoted word (codex-rs/shell-command/src/bash.rs at rust-v0.163.0, parse_plain_command_from_node: command_name must be a word node; otherwise commands_for_exec_policy falls back to the whole bash -lc … and no prefix rule matches). The hints must quote a spaced probe path, so the #2019 rules never matched.
Quoted arguments are fine: the same parser accepts string / raw_string argument nodes as one word each, so --socket '/…/Application Support/…/relay.sock' matches the rule as written. The socket therefore needs no alias.
Fix
New relay::probe_alias::ProbeAlias:
- While
codex_stop_hookorclaude_session_hookis on and the probe's path needs shell quoting, the core keeps a symlink at~/.agentworkforce/desktop/bin/agent-relay-probe(agent-relay-probe-devfor Agent Relay Dev) and names it in the Codex rules, the Claudepermissions.allowrules, therelay readpointers and every reply/agents/attachment hint (Codex, Claude Code, OpenCode, Grok — all go throughoutbound::fetch::probe_executable). - The hook commands themselves keep the real path (Codex/Claude run hooks directly, not through rules).
- Refresh: the link is retargeted whenever a hook turns on and at each core start, so a moved/updated app never leaves it at an old binary; a probe moved to a plain path drops the old link.
- Safety: the link is created/used only when its folder (created
0700) and that folder's parent are real directories (not symlinks) owned by this user and not group/world-writable, the core recorded creating it (probe-alias.jsonin its app-data dir), andread_linkis exactly the running probe. Anything else at the name — a link someone else made (even to this probe), a file, a symlinked folder, a writable folder — is never replaced or used, and everything falls back to today's quoted real path. Rules still allow only the same two (Codex) / five (Claude) command shapes. - Cleanup: when neither hook is on (turned off, or
relay init --uninstall), the link goes, with its folder once empty, and the record.
Claude Code already matched the quoted path literally (#2021); its rules use the link too so they keep matching the hints, which now name the link.
Tests
probe_alias::tests: link created0700for a spaced path; no link for a plain path; moved probe retargets the link and a plain-path move removes it; refuses others' links (to elsewhere and to this probe), a plain file, a writable folder, a writable parent, a symlinked folder, and a link changed behind its back; link stays while one user remains and goes (with its empty folder and record) after the last.stop_hook: rules and hints name the link, socket stays quoted, refresh recreates a missing link, turning off restores~/.codexexactly and removes the link.permission_tests: Claude rules name the link, the pointer/reply/agents hints through the link are covered by the rules, turning off restores~/.claudeand removes the link.
End to end
Isolated HOME/AGENT_RELAY_HOME/INSTALL_DIR/XDG/CODEX_HOME under /tmp/arc7, locally built probe copied to /tmp/arc7/Agent Relay Test.app/Contents/Helpers/Agent Relay Helper (AGENT_RELAY_PROBE_PATH), real Codex 0.163.0 in its default permissions ("Workspace (granular)"): relay init wrote the link and the rules naming it; a DM from alice was answered by Codex via /tmp/arc7/.agentworkforce/desktop/bin/agent-relay-probe relay socket-request --socket '…/Application Support/…/relay.sock' … with no approval prompt; relay message inbox check --as alice showed the reply. relay init --uninstall + --stop removed the link, its folder, the rules, hooks.json and ~/.claude. Details (and why the live Claude Code run is still pending: the 1Password read of the setup token timed out) are in the e2e comment below. Review follow-up: the record also keeps the link's target and inode, so a link put in its place is never overwritten or removed; hints re-check the link on every use; and an interrupted move is finished at the next start. Hints handed out before both hooks were turned off that name the link stop working once it is removed (they would have needed approval without the rules anyway).
🤖 Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Agent Relay sessions
claudesession8ab900cc· opened viagh pr create· last active 2026-10-30