Skip to content

feat(opencode): write guard via project plugin (opencode-plugin engine) #155

Description

@slowdini

Part of the OpenCode enhancement-parity track (see #113 for the baseline + progressive-enhancement model). This is the last missing enhancement column for OpenCode and the largest of the track.

OpenCode cannot arm the write guard today: run_capabilities().supports_guard is false, auto-arm stays off, and an explicit --guard warns and continues unguarded.

Verified hook surface (opencode plugins doc, v1.18.3)

  • Project plugins at .opencode/plugins/*.js (and global at ~/.config/opencode/plugins/) are loaded automatically at startup — no trust prompt is documented.
  • The tool.execute.before hook receives (input, output) with input.tool (tool id) and output.args (tool parameters), and throwing an Error blocks the tool call (the documented .env-protection example throws to deny reads).
  • Tool ids verified from source: bash (input {command}), edit/write (input {filePath, ...}), apply_patch (input {patchText}), read/glob/grep, skill (input {name}).

Proposed design

A new guard engine variant alongside the existing JSON-hooks engine (which stays the claude/codex path), selected by [guard] engine = "opencode-plugin":

  • Install: stage an embedded JS plugin template at .opencode/plugins/slow-powers-eval-guard.js with {exe}/{marker} substituted; marker + manifest via the shared src/sandbox/ machinery; teardown removes the plugin file (extend the guard_hook_cleanup_dir path).
  • Plugin: forward every tool call as {"tool_name": input.tool, "tool_input": output.args} on stdin to eval-magic guard-hook --harness opencode <marker>; empty stdout → allow; non-empty stdout → parse the verdict JSON and throw new Error(reason). Keep the JS dumb — the shared arbiter (src/sandbox/decide.rs) stays the single classification point.
  • Verdict: unchanged — the generic guard-hook entry point and guard_verdict render the descriptor's verdict_template (codex-style {"decision":"block","reason":"{reason}"} works); only the install side is new.
  • Descriptor/validation: [guard] gains an engine discriminator (default "json-hooks"); hook_entry/matcher are required only for the JSON-hooks engine, plugin-file fields for the plugin engine; [run] supports_guard = true lockstep as today; user-supplied descriptors remain barred from declaring [guard].
  • Arbiter coverage (already landed with transcript ingest): [tools] vocabulary for opencode, path_arg covers filePath, apply_patch_paths covers patchText.

Verification checklist (don't-guess guardrail — record evidence in docs/opencode-notes.md)

  1. A staged project plugin auto-loads under opencode run headless with no trust prompt.
  2. tool.execute.before fires under --auto (i.e., after permission auto-approval).
  3. A thrown Error aborts the tool call and surfaces in the events stream as a tool error.
  4. Armed guard blocks an out-of-bounds write/bash and allows in-bounds writes; teardown-guard removes the plugin cleanly.

Rejected alternative

OPENCODE_PERMISSION env JSON with external_directory/edit/bash patterns: glob-matching is weaker than the arbiter's lexical normalization, bash path parsing is heuristic-only, and an env var is easy to drop from a hand-driven runbook — the plugin auto-loads by directory convention, exactly like the claude/codex hook files.

Acceptance

  • run --harness opencode auto-arms the guard on staged runs (with the armed banner); --no-guard opts out; explicit --guard works
  • Guard blocks out-of-bounds writes/mutations in a live opencode run; allows in-bounds
  • Unit + integration tests (install/teardown, verdict round-trip through guard-hook stdin); golden blessing for the armed banner
  • docs/opencode-notes.md + README support table (Write guard ✅)
  • cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check green

Depends on the [tools] vocabulary declared with transcript ingest (arbiter classification + descriptor validation).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions