Skip to content

Commit 717fa62

Browse files
bgagentclaude
andcommitted
fix(review_prs): attribute the headless Workflow block to disallowed_tools, not the allow-list (review: isadeks)
isadeks' review (PR #621, 4716887845) caught the Execution-context section crediting the wrong mechanism. Verified against current runner.py on origin/main: 1. The allow-list is NOT the lock. Under permission_mode="bypassPermissions", a tool absent from allowed_tools is auto-ALLOWED, not blocked. The hard lock is disallowed_tools=_DISALLOWED_TOOLS (Workflow/Task/Agent/Monitor/ SendMessage/Cron*), and Cedar PreToolUse default-permits on no-match. The doc now attributes the guarantee to disallowed_tools and drops the "not in the allowlist" framing. 2. setting_sources is conditional: _resolve_setting_sources returns ["project"] only when config.repo_url is set, [] for repo-less tasks. Reworded from the unconditional form; the nuance strengthens the point (stub reachable only on the repo path, tool off-surface regardless). Doc-only; drift-prevention + docs build green. Refs #620 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 69ba7c4 commit 717fa62

1 file changed

Lines changed: 19 additions & 14 deletions

File tree

.abca/commands/review_prs.md

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,27 @@ bodies and agent transcripts. (Caveat: per-PR *summaries* still accrete linearly
2929
cannot be — executed by ABCA's headless agent runtime.** The distinction matters because Stage 2's
3030
core tool, `Workflow`, is unavailable to headless tasks by construction:
3131

32-
- The runtime resolves its SDK tool surface from an **allowlist**, not the on-disk config:
33-
`agent/src/runner.py` `_resolve_allowed_tools()` returns the workflow's `agent_config.allowed_tools`
34-
or falls back to `_FULL_TOOL_SURFACE = ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch"]`
35-
(`read_only` lanes drop `Write`/`Edit` on top). `Workflow`, `Task`, and `Agent` are **not in that
36-
allowlist**, so a headless task physically cannot spawn a `Workflow` — the incident-driven
37-
hardening against a repo-less task launching a detached background `Workflow` and finalizing on a
38-
placeholder. Interactive Claude Code / Cursor sessions are not tool-gated this way, so `Workflow`
39-
is available there — which is where this command runs.
40-
- The runtime does load project `.claude/` config (`ClaudeAgentOptions(setting_sources=["project"])`
41-
in `runner.py`), so this command's stub **is** discoverable to a task that clones this repo. That
42-
is precisely why the allowlist — not mere obscurity — is the guarantee: even though the file is on
43-
disk, the tool it calls is off-surface.
32+
- The runtime hard-blocks `Workflow` via a **deny-list**, which is the load-bearing mechanism.
33+
`agent/src/runner.py` passes `disallowed_tools=_DISALLOWED_TOOLS` (`= ["Workflow", "Task", "Agent",
34+
"Monitor", "SendMessage", "CronCreate", "CronDelete", "CronList"]`) to `ClaudeAgentOptions` — the
35+
incident-driven hardening after a repo-less task launched a detached background `Workflow` and
36+
finalized on a placeholder. This is the *only* thing that removes the tool from the surface: the
37+
runtime runs under `permission_mode="bypassPermissions"`, so a tool that is merely **absent from
38+
the `allowed_tools` auto-approve list is auto-*allowed*, not blocked** (runner.py is emphatic:
39+
"`disallowed_tools` is the only hard lock … do not rely on this allow-list, nor on Cedar, to remove
40+
a tool from the surface" — Cedar PreToolUse default-permits on no-match). So the guarantee rests on
41+
`disallowed_tools`, not on `Workflow` being off the allow-list. Interactive Claude Code / Cursor
42+
sessions are not gated this way, so `Workflow` is available there — which is where this command runs.
43+
- The runtime loads project `.claude/` config only when a repo is cloned:
44+
`_resolve_setting_sources(config)` returns `["project"]` when `config.repo_url` is set and `[]`
45+
for a repo-less task (defense-in-depth that also stops a stray on-disk skill from being reachable).
46+
So this command's stub **is** discoverable to a repo-cloning task — which is exactly why the
47+
`disallowed_tools` block, not mere obscurity, is the guarantee: even though the file is on disk and
48+
reachable on that path, the tool it calls is off the surface.
4449
- The read-only review workflow (`coding/pr-review-v1`) cannot be steered into `/review_prs`: it is
4550
bound to the `pr_review` system prompt (`agent/src/workflow/loader.py`) and drives the agent
46-
through that fixed prompt over the allowlisted tool surface — there is no "invoke a slash command"
47-
path, and `Workflow` is off-surface regardless.
51+
through that fixed prompt — there is no "invoke a slash command" path, and `Workflow` is
52+
deny-listed regardless.
4853

4954
In short: operators run `/review_prs`; the ABCA runtime never does, and structurally cannot.
5055

0 commit comments

Comments
 (0)