Skip to content

hooks: fix OpenCode guard-path baking and Windows fallback, add plugin tests - #300

Merged
codeyevsky merged 1 commit into
solongate:mainfrom
j-ai-cmd:fix/opencode-plugin-guard-path
Nov 3, 2026
Merged

codeyevsky merged 1 commit into
solongate:mainfrom
j-ai-cmd:fix/opencode-plugin-guard-path

Conversation

@j-ai-cmd

@j-ai-cmd j-ai-cmd commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #207, fixes #191, fixes #192.

All three issues sit in the path from install to opencode-plugin.mjs spawning the guard, so they go in one PR. Happy to split them if you'd rather review them separately.

Cause

Fix

  • bakeGuardBinPath now uses one strings.NewReplacer that escapes \, ', \n and \r. A single pass means a quote after a backslash still comes out right.
  • The fallback adds .exe when process.platform === 'win32', which matches goBinaryNames().
  • New packages/hooks/opencode-plugin.test.mjs (node:test, no dependencies). It doesn't mock child_process. Each test points the plugin at a small shell script that stands in for the guard: the script records its argv and stdin, then replies with a canned JSON body and exit code. So the tests run the real spawn/stdin/stdout path. 19 cases cover:
    • guard path resolution: baked path, unbaked fallback, stale baked path, and the Windows .exe fallback (simulated by setting process.platform before the module loads)
    • tool.execute.before: payload and argv sent, exit 2 blocks with the guard's reason or the default reason, updatedInput rewrite applies only on allow, non-JSON output fails open, missing binary fails open
    • tool.execute.after: audit payload, and each redaction merge branch (string, stdout, content, metadata merge, nothing redacted)
    • event: token report for a completed assistant message, and no report for user messages, unfinished messages or part updates
  • The test file sits next to the plugin, not in a test/ folder, because .gitignore ignores **/test/.
  • scripts/sg-unit-check.sh runs the suite, and ci.yaml gets a small hooks job that runs it with the runner's preinstalled Node. No setup-node action and no new dependencies. CONTRIBUTING mentions the suite.
  • No change to the decision path or to what any policy means.

How I tested

  • node --test packages/hooks/opencode-plugin.test.mjs: 19/19 pass on Node 24, macOS. The Windows-fallback test fails if the .mjs fix is reverted.
  • go test ./install/ in packages/core, including 3 new bakeGuardBinPath cases: single quote, quote after a backslash, line breaks. go vet is clean, and the changed files pass the CI-style gofmt check.
  • To check hooks: baked guard-binary path isn't quote-escaped, can produce invalid JS for the OpenCode plugin #207 end to end, I baked the real opencode-plugin.mjs with C:\Users\o'brien\nx\solongate-guard.exe, then ran node --check and import() on the output. Both succeed. Before the fix the output was a syntax error.
  • I ran all modules' go test. guard, policy, shared, tui, app and crypto pass. On macOS, sandbox, core and cli show the same file-lock/protect failures on an untouched origin/main checkout, so they come from the environment and not this change.
  • Not tested against a live OpenCode session.

🤖 Generated with Claude Code

…n tests

bakeGuardBinPath only doubled backslashes before splicing the guard path
into a single-quoted JS literal, so an install path with an apostrophe
(or a line break) produced an opencode-plugin.mjs that OpenCode could not
parse, which silently turned enforcement off. It now escapes quotes and
line breaks too (solongate#207).

The plugin's fallback guard path had no .exe suffix, so on Windows the
fallback spawn always hit ENOENT and the plugin failed open (solongate#191).

opencode-plugin.mjs had no tests (solongate#192). Add a node:test suite that runs
the plugin against a fake guard script, covering path resolution, deny,
rewrite, redaction merge, token reporting and the fail-open paths, and
run it from sg-unit-check.sh and CI.

Fixes solongate#207, fixes solongate#191, fixes solongate#192.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jai Dhingra <297267163+j-ai-cmd@users.noreply.github.com>
@codeyevsky
codeyevsky merged commit e4b222a into solongate:main Nov 3, 2026
34 of 35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants