fix(grok): allow external-directory reads so the pr-reviewer can work

Live NAS run showed the Grok pr-reviewer claim the review and fetch the diff,
then write it to /tmp and FAIL to read it back: opencode auto-denied
"external_directory (/tmp/*)" — its file tools refuse paths outside the project
cwd, and in headless serve/run mode an "ask" permission auto-rejects (no human).

Add permission.external_directory (default "allow", env
ROBOCO_GROK_EXTERNAL_DIR_PERMISSION) to the generated opencode.json. The
container is the sandbox and secret-scrub still blocks credential-file reads, so
allowing in-container external-dir reads is safe and unblocks legitimate scratch
use (e.g. the pr-reviewer grepping a large diff in /tmp).

Verified live against grok-build-0.1: with external_directory:"allow" the Read
tool reads a file outside cwd and returns its contents (no auto-reject); the
plain-string form is accepted by opencode 1.17.8.

Needs a rebuild of roboco-agent-grok + a pr-reviewer re-run on the NAS to confirm.
This commit is contained in:
Renn F
2026-06-18 18:52:07 +02:00
parent 12f11a4996
commit 9ed84f051c
2 changed files with 32 additions and 3 deletions
+15 -3
View File
@@ -94,13 +94,17 @@ class XaiTarget:
class OpencodeGuards: class OpencodeGuards:
"""Tunable runtime guards baked into a Grok ``opencode.json``. """Tunable runtime guards baked into a Grok ``opencode.json``.
``bash``/``edit`` gate the command/file tools; the timeouts bound a single ``bash``/``edit`` gate the command/file tools; ``external_directory`` gates
model call and abort an idle stream; ``disable_subagents`` removes the reading paths outside the project cwd (opencode auto-DENIES an ``ask`` in
subagent ``task`` tool entirely. headless mode, which blocked the pr-reviewer from reading a diff it wrote to
/tmp — so default ``allow``: the container is the sandbox and secret-scrub
still blocks credential files); the timeouts bound a single model call and
abort an idle stream; ``disable_subagents`` removes the subagent ``task`` tool.
""" """
bash_permission: str = "allow" bash_permission: str = "allow"
edit_permission: str = "allow" edit_permission: str = "allow"
external_directory_permission: str = "allow"
request_timeout_ms: int = _DEFAULT_REQUEST_TIMEOUT_MS request_timeout_ms: int = _DEFAULT_REQUEST_TIMEOUT_MS
chunk_timeout_ms: int = _DEFAULT_CHUNK_TIMEOUT_MS chunk_timeout_ms: int = _DEFAULT_CHUNK_TIMEOUT_MS
disable_subagents: bool = True disable_subagents: bool = True
@@ -160,6 +164,11 @@ def build_opencode_config(
"permission": { "permission": {
"bash": guards.bash_permission, "bash": guards.bash_permission,
"edit": guards.edit_permission, "edit": guards.edit_permission,
# Reading paths outside the project cwd (e.g. /tmp scratch). opencode
# auto-denies an "ask" in headless mode, which blocked the pr-reviewer
# from reading a diff it wrote to /tmp; "allow" since the container is
# the sandbox and secret-scrub still blocks credential files.
"external_directory": guards.external_directory_permission,
}, },
"instructions": instruction_paths, "instructions": instruction_paths,
# Command guard / secret-scrub (bash-guard parity). Baked into the image. # Command guard / secret-scrub (bash-guard parity). Baked into the image.
@@ -198,6 +207,9 @@ def main() -> int:
) )
guards = OpencodeGuards( guards = OpencodeGuards(
bash_permission=os.environ.get("ROBOCO_GROK_BASH_PERMISSION", "allow"), bash_permission=os.environ.get("ROBOCO_GROK_BASH_PERMISSION", "allow"),
external_directory_permission=os.environ.get(
"ROBOCO_GROK_EXTERNAL_DIR_PERMISSION", "allow"
),
request_timeout_ms=_env_int( request_timeout_ms=_env_int(
"ROBOCO_GROK_REQUEST_TIMEOUT_MS", _DEFAULT_REQUEST_TIMEOUT_MS "ROBOCO_GROK_REQUEST_TIMEOUT_MS", _DEFAULT_REQUEST_TIMEOUT_MS
), ),
+17
View File
@@ -103,6 +103,23 @@ def test_build_opencode_config_bash_permission_is_tunable() -> None:
assert cfg["permission"]["edit"] == "allow" assert cfg["permission"]["edit"] == "allow"
def test_build_opencode_config_allows_external_directory_by_default() -> None:
# opencode auto-denies an "ask" external-dir read in headless mode (the
# pr-reviewer couldn't read a diff it wrote to /tmp); default "allow".
cfg = build_opencode_config(_MCP, _TARGET, instruction_paths=[])
assert cfg["permission"]["external_directory"] == "allow"
def test_build_opencode_config_external_directory_is_tunable() -> None:
cfg = build_opencode_config(
{},
_TARGET,
instruction_paths=[],
guards=OpencodeGuards(external_directory_permission="ask"),
)
assert cfg["permission"]["external_directory"] == "ask"
def test_build_opencode_config_disables_subagent_task_tool_by_default() -> None: def test_build_opencode_config_disables_subagent_task_tool_by_default() -> None:
# The subagent `task` tool must be hard-disabled: a RoboCo role never uses # The subagent `task` tool must be hard-disabled: a RoboCo role never uses
# opencode-internal subagents, and one spawned on grok-build-0.1 hung the run. # opencode-internal subagents, and one spawned on grok-build-0.1 hung the run.