fix(grok): auto-approve tool execution (--always-approve) so headless agents can call tools

Live smoke caught every grok agent (Main PM, pr-reviewer, dev, …) ending its run
with stopReason=Cancelled and empty output the instant it reached for a tool. Root
cause: headless `grok -p` cannot approve a tool call without `--always-approve`
(grok's docs: required for unattended automation), and the per-role args didn't
pass it — so no agent could call a gateway verb, an edit, or an MCP tool, and the
run was cancelled.

Add `--always-approve` to grok_cli_args_for_role (one place → every role, one-shot
and interactive). Safety is unaffected: `--disallowed-tools` still removes tools
and `--deny` still hard-blocks command patterns regardless of approval (a denied
command returns a permission error and the agent recovers — verified live).

Proven in the rebuilt image side-by-side: without the flag a tool call yields
Cancelled/not-called; with the real rendered args it returns EndTurn and the MCP
tool actually runs. (My earlier in-image tool-calling check passed `--always-approve`
manually, which masked that the production args omitted it — fixed.)
This commit is contained in:
Renn F
2026-06-19 07:02:01 +02:00
parent 9a4af47b9a
commit 58fd78830c
2 changed files with 18 additions and 3 deletions
+11 -3
View File
@@ -184,10 +184,18 @@ def grok_cli_args_for_role(
) -> list[str]:
"""The per-role ``grok -p`` flag tokens (excludes ``-p``/model/cwd).
Order: tool removal, web off, turn cap, deny rules, then effort. Each token is
a separate list element so callers can splice them without shell quoting.
Order: auto-approve, tool removal, web off, turn cap, deny rules, then effort.
Each token is a separate list element so callers can splice them without shell
quoting.
"""
args: list[str] = ["--disallowed-tools", _disallowed_tools(role)]
# Auto-approve tool execution — REQUIRED for headless/unattended runs. Without
# it, grok cannot approve any tool call in `grok -p` (no human to confirm) and
# the run ends `Cancelled` the instant the agent reaches for a tool — every
# gateway verb, edit, and MCP call. Safety still holds: `--disallowed-tools`
# removes tools entirely and `--deny` hard-blocks command patterns regardless
# of approval (a denied command returns a permission error, verified live).
args: list[str] = ["--always-approve"]
args += ["--disallowed-tools", _disallowed_tools(role)]
# No direct web for any role (parity with the Claude path's tool set); the
# roles that get web reach it through the gated roboco-search MCP server.
args += ["--disable-web-search"]
@@ -113,6 +113,13 @@ def test_web_search_disabled_for_every_role() -> None:
assert "--disable-web-search" in gc.grok_cli_args_for_role(role)
def test_every_role_auto_approves_tools() -> None:
# Headless `grok -p` cannot approve a tool call without this; without it the
# run ends Cancelled the moment the agent calls a gateway verb / MCP tool.
for role in ("developer", "prompter", "secretary", "main_pm", "pr_reviewer", "qa"):
assert "--always-approve" in gc.grok_cli_args_for_role(role)
def test_effort_is_fleet_override_only(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("ROBOCO_GROK_REASONING_EFFORT", "high")
args = gc.grok_cli_args("be-dev-1")