Skip to content

fix(probe): name the probe by a space-free link so Codex rules match the macOS app - #2024

Merged
kjgbot merged 3 commits into
mainfrom
fix/probe-space-free-alias
Oct 30, 2026
Merged

kjgbot merged 3 commits into
mainfrom
fix/probe-space-free-alias

Conversation

@kjgbot

@kjgbot kjgbot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

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_hook or claude_session_hook is on and the probe's path needs shell quoting, the core keeps a symlink at ~/.agentworkforce/desktop/bin/agent-relay-probe (agent-relay-probe-dev for Agent Relay Dev) and names it in the Codex rules, the Claude permissions.allow rules, the relay read pointers and every reply/agents/attachment hint (Codex, Claude Code, OpenCode, Grok — all go through outbound::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.json in its app-data dir), and read_link is 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 created 0700 for 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 ~/.codex exactly 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 ~/.claude and 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 Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

… and hints

Codex's execpolicy matches a command only when its program is a plain,
unquoted word (shell-command/src/bash.rs, Codex 0.163: the command_name
must be a `word` node; quoted arguments are read as one word and match).
The macOS app's probe, /Applications/Agent Relay.app/Contents/Helpers/
Agent Relay Helper, must be quoted in every hint, so the rules from #2019
never matched and Codex asked for every read and reply.

While the Codex hook or the Claude Code hook is on and the probe's path
needs quoting, the core keeps a link at
~/.agentworkforce/desktop/bin/agent-relay-probe (-dev for Agent Relay Dev)
and names it in the Codex rules, the Claude allow rules, the pointers and
every reply hint. The socket stays a quoted argument, which Codex matches.

- The link is pointed at the running probe again whenever a hook turns on
  and at each core start; a probe moved to a plain path drops it.
- It is used only when its folder (made 0700) and that folder's parent are
  real folders owned by this user and not group/world-writable, the core
  recorded making it (probe-alias.json in its data), and it points at
  exactly the running probe. Anything else there is never replaced or
  used; the hints and rules then name the real path as before.
- Turning both hooks off (or relay init --uninstall) removes the link and
  its folder once empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 748d260f-322e-41ee-bca0-fff5926f755a
Commits

Reviewing files that changed from the base of the PR and between b0e02a2 and 44ebdc6.

Files selected for processing: 12
  • CHANGELOG.md
  • crates/probe/src/relay.rs
  • crates/probe/src/relay/claude/session_hook.rs
  • crates/probe/src/relay/claude/session_hook/permission_tests.rs
  • crates/probe/src/relay/codex/stop_hook.rs
  • crates/probe/src/relay/local/plugins.rs
  • crates/probe/src/relay/local/terminal.rs
  • crates/probe/src/relay/message_pointer.rs
  • crates/probe/src/relay/outbound.rs
  • crates/probe/src/relay/outbound/fetch.rs
  • crates/probe/src/relay/probe_alias.rs
  • docs/probe-move.md

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

Walkthrough

The core now manages a space-free probe alias for Codex and Claude hook rules when needed. Hook settings acquire and release the alias as hooks change state. Probe command construction uses the active alias when available.

Changes: Probe alias and hook integration

Layer / File(s) Summary
Probe alias lifecycle
crates/probe/src/relay/probe_alias.rs, crates/probe/src/relay.rs
A new ProbeAlias module manages alias eligibility, ownership and permission checks, link creation, active-path publication, and cleanup.
Hook rule integration
crates/probe/src/relay/local/plugins.rs, crates/probe/src/relay/codex/stop_hook.rs, crates/probe/src/relay/claude/session_hook.rs, crates/probe/src/relay/codex/stop_hook.rs, crates/probe/src/relay/claude/session_hook/permission_tests.rs, docs/probe-move.md, CHANGELOG.md
Local plugin settings pass a shared alias to the Codex and Claude hooks. The hooks use it in rules and manage it during enablement, refresh, and removal. Unix tests cover rule generation and cleanup; the docs describe the alias behavior.
Active probe command construction
crates/probe/src/relay/outbound/fetch.rs, crates/probe/src/relay/outbound.rs, crates/probe/src/relay/message_pointer.rs, crates/probe/src/relay/local/terminal.rs
Probe executable lookup prefers the active alias. Socket-bound command construction resolves the executable when needed, and terminal setup borrows the configured reader value.

Priority: Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant plugins_with_options
  participant ProbeAlias
  participant CodexStopHookSettings
  participant CodexSandboxRules
  plugins_with_options->>ProbeAlias: Create shared alias
  plugins_with_options->>CodexStopHookSettings: Configure alias
  CodexStopHookSettings->>ProbeAlias: Acquire Codex alias
  CodexStopHookSettings->>CodexSandboxRules: Write rules with alias probe path
Loading

Possibly related PRs

  • AgentWorkforce/relay#2021: Adds Claude permission rules and probe-based reply commands, which this change updates to use the alias when required.
  • AgentWorkforce/relay#2019: Adds Codex sandbox rules and command hints, which this change updates to use the alias when required.
  • AgentWorkforce/relay#2023: Tests Claude and Codex init and uninstall flows that relate to the hook path and cleanup behavior changed here.

Suggested reviewers: khaliqgant, willwashburn

Merge Risk: Low · up to 44ebd

Disabling the last hook while an agent is still running can break that agent’s report command. The PR remains mergeable with awareness of this narrow lifecycle limitation.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 73.08% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check Warning The description explains the problem, fix, tests, safety behavior, cleanup behavior, and end-to-end results. However, it omits the required ## Summary, ## Test Plan, and ## RelayFlow Proof struc… Add the required template sections. Preserve the existing content under ## Summary and ## Test Plan, mark the completed tests, and add ## RelayFlow Proof with one valid change type (feature or bugfix) and exactly one case path und…
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly identifies the primary change: using a space-free probe link so Codex rules match macOS app paths.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 73.08% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 26 functions across 10 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Full details: Description check

Explanation

The description explains the problem, fix, tests, safety behavior, cleanup behavior, and end-to-end results. However, it omits the required ## Summary, ## Test Plan, and ## RelayFlow Proof structure. The RelayFlow Proof is incomplete because it does not provide a change type or exactly one RelayFlow case under tests/relayflows/cases/&lt;case-id&gt;/.

Resolution

Add the required template sections. Preserve the existing content under ## Summary and ## Test Plan, mark the completed tests, and add ## RelayFlow Proof with one valid change type (feature or bugfix) and exactly one case path under tests/relayflows/cases/&lt;case-id&gt;/. Add ## Screenshots or state that screenshots are not applicable.

Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks the probe-path trail,
A tidy link avoids the quote,
The hooks now share a careful route,
They leave no link when turned away,
And socket-bound commands find their way,
The rabbit hops, then guards the gate.

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Devin Review

Comment thread crates/probe/src/relay/probe_alias.rs Resolved
Comment thread crates/probe/src/relay/probe_alias.rs Resolved
@greptile-apps

greptile-apps Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with no outstanding findings.

Summary

The PR gives probes whose paths contain spaces a plain link that Codex rules can match. Rules and hints share that link while either hook needs it.

  • Relayed commands use a space-free probe name when hooks need it.

Reviews (3) · Last reviewed commit: "fix(probe): finish an interrupted link m..." · Reviewed by Greptile

Comment thread crates/probe/src/relay/probe_alias.rs Resolved
Comment thread crates/probe/src/relay/probe_alias.rs Resolved
… pointers

Review on #2024:
- probe-alias.json now records the link's target and inode, so a link put
  in its place (even one to the same probe) is never overwritten on refresh
  or removed on turn-off.
- Hints check the link again on every use (`active` goes through
  `usable`), so one deleted or replaced since is never handed out.
- A pointer handed out through the link that has not been read yet keeps
  the link after both hooks go, until it is read or expires; hints name the
  probe's own path again at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread crates/probe/src/relay/probe_alias.rs Resolved
Comment thread crates/probe/src/relay/probe_alias.rs Resolved
Comment thread crates/probe/src/relay/probe_alias.rs Resolved
@kjgbot

kjgbot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

End-to-end evidence (head bd55e97)

Isolated env: HOME, AGENT_RELAY_HOME, AGENT_RELAY_INSTALL_DIR, XDG_* and CODEX_HOME all under /tmp/arc7; AGENT_RELAY_CORE_SOCKET=$HOME/.agentworkforce/desktop/core.sock; the probe built from this branch, copied to /tmp/arc7/Agent Relay Test.app/Contents/Helpers/Agent Relay Helper and passed in with AGENT_RELAY_PROBE_PATH; project at ~/arc7-project; only auth.json copied into CODEX_HOME.

relay init --yes --tools claude,codex --scope global

  • Wrote ~/.agentworkforce/desktop/bin/ (drwx------) with agent-relay-probe -> /tmp/arc7/Agent Relay Test.app/Contents/Helpers/Agent Relay Helper, and recorded it in probe-alias.json as {link, target, inode}.
  • ~/.codex/rules/agent-relay.rules has two prefix_rules: ["/tmp/arc7/.agentworkforce/desktop/bin/agent-relay-probe", "relay", "socket-request"|"read", "--socket", "/tmp/arc7/Library/Application Support/com.agentrelay.desktop/run/relay.sock"].
  • ~/.claude/settings.json has the 5 Bash(...) rules, each naming the link and the single-quoted socket.

Codex 0.163.0, default permissions (/status: Permissions "Workspace (granular)")

  • alice → codex-arc7-project-say-hello DM 241854835246493696. Codex ran /tmp/arc7/.agentworkforce/desktop/bin/agent-relay-probe relay socket-request --socket '/tmp/arc7/Library/Application Support/…/relay.sock' --method POST --path '/send?address=alice@direct&…' with no approval prompt. The reply is message 241854855106523136 ("MANGO"), and relay message inbox check --as alice shows it. A first run at 44ebdc6 gave the same result (reply 241849858642837504, "PINEAPPLE").
  • relay read shape: <link> relay read --socket '<socket>' 0123456789ab ran outside the sandbox with no prompt and reached the socket (No message 0123456789ab was kept for this session, which is the expected answer for a made-up ref).
  • Counterfactual: the same read through the quoted real path '/tmp/arc7/Agent Relay Test.app/…/Agent Relay Helper' relay read … matched no rule, ran inside the sandbox and failed with Agent Relay's session socket is unavailable. The quoted program is what kept the rules from matching; the quoted socket argument is fine.

relay init --uninstall then relay init --stop

  • The link, its bin/ folder and probe-alias.json are gone.
  • rules/ and hooks.json are gone, as are the codex-config-created marker and claude-permission-rules.json.
  • ~/.claude is gone (it did not exist before init), and auth.json and .claude.json are byte-identical to before.
  • The only thing left in CODEX_HOME beyond Codex's own runtime state is config.toml with [tui] screen_reader_detection_done = true. Codex's TUI writes that itself; it is not Agent Relay's.
  • Every process I started (the core, relaycast-engine, Codex and its app-server daemon, the tmux server) has been stopped, and /tmp/arc7 and ~/arc7-project have been removed.

Claude Code live run: blocked. op read 'op://AI Agents/Claude Setup Token/TOKEN' failed with authorization timeout four times across two runs, so Claude Code 2.1.314 could not be signed in. Claude coverage comes from the unit test permission_tests::a_probe_path_with_a_space_is_named_by_its_link: the rules name the link, and covers() (Claude's literal-prefix matching, verified live in #2021) accepts the pointer, reply and agents hints built through the link.

Review on #2024:
- The record keeps the link being replaced while the app moves, so a core
  stopped between recording the new link and renaming it into place still
  knows the old one as its own and finishes the move at the next start.
- Keeping the link for unread pointers is reverted: reading the pointer
  hands out reply commands naming the link too, and pointers for one
  message in two sessions shared an entry, so any read-based retention
  still broke commands while leaving the link after the person turned
  Agent Relay off. Turning both hooks off removes the link as asked; hints
  name the probe's own path again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants