diff --git a/roboco/llm/providers/opencode_config.py b/roboco/llm/providers/opencode_config.py index 76f3d517..7530d2c2 100644 --- a/roboco/llm/providers/opencode_config.py +++ b/roboco/llm/providers/opencode_config.py @@ -94,13 +94,17 @@ class XaiTarget: class OpencodeGuards: """Tunable runtime guards baked into a Grok ``opencode.json``. - ``bash``/``edit`` gate the command/file tools; the timeouts bound a single - model call and abort an idle stream; ``disable_subagents`` removes the - subagent ``task`` tool entirely. + ``bash``/``edit`` gate the command/file tools; ``external_directory`` gates + reading paths outside the project cwd (opencode auto-DENIES an ``ask`` in + 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" edit_permission: str = "allow" + external_directory_permission: str = "allow" request_timeout_ms: int = _DEFAULT_REQUEST_TIMEOUT_MS chunk_timeout_ms: int = _DEFAULT_CHUNK_TIMEOUT_MS disable_subagents: bool = True @@ -160,6 +164,11 @@ def build_opencode_config( "permission": { "bash": guards.bash_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, # Command guard / secret-scrub (bash-guard parity). Baked into the image. @@ -198,6 +207,9 @@ def main() -> int: ) guards = OpencodeGuards( 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( "ROBOCO_GROK_REQUEST_TIMEOUT_MS", _DEFAULT_REQUEST_TIMEOUT_MS ), diff --git a/tests/unit/llm/test_opencode_config.py b/tests/unit/llm/test_opencode_config.py index 74906f62..9f5f823a 100644 --- a/tests/unit/llm/test_opencode_config.py +++ b/tests/unit/llm/test_opencode_config.py @@ -103,6 +103,23 @@ def test_build_opencode_config_bash_permission_is_tunable() -> None: 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: # 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.