Skip to content

[luv-340] fix: regenerate OpenCode dev shim + handler-side canonicalization for OpenCode/Pi#340

Merged
NiveditJain merged 2 commits intomainfrom
luv-340
May 10, 2026
Merged

[luv-340] fix: regenerate OpenCode dev shim + handler-side canonicalization for OpenCode/Pi#340
NiveditJain merged 2 commits intomainfrom
luv-340

Conversation

@NiveditJain
Copy link
Copy Markdown
Member

@NiveditJain NiveditJain commented May 10, 2026

Summary

Two-layer fix for block-read-outside-cwd silently no-op'ing on OpenCode read calls inside this repo. Surfaced when an opencode session running from this repo could read paths outside the repo cwd (e.g. /home/<user>/<other-folder>) without any deny.

  • Root cause: PR [luv-338] fix: canonicalize OpenCode + Pi tool-input arg keys #337 added OPENCODE_TOOL_INPUT_MAP to the production shim template at src/hooks/integrations.ts:buildOpenCodePluginShim, but the dev shim at .opencode/plugins/failproofai.mjs is hand-maintained and was never regenerated. With camelCase filePath reaching the binary unchanged, getFilePath() returned "", the target-empty short-circuit at src/hooks/builtin-policies.ts:799 returned allow, and the policy silently no-op'd.
  • Layer 1 — dev shim: regenerated .opencode/plugins/failproofai.mjs to mirror the production template (TOOL_NAME_MAP + TOOL_INPUT_MAP + idempotent canonicalizeTool / canonicalizeToolInput). Also picked up the applyDecision async-await change from [luv-319] fix: enforce Stop hook on Cursor Agent CLI + cut 0.0.10-beta.6 #318 the dev shim had been missing, so Stop / SubagentStop force-retry actually awaits the SDK call.
  • Layer 2 — handler defense-in-depth: added per-CLI canonicalization for OpenCode + Pi in src/hooks/handler.ts so user-scope shims that pre-date [luv-338] fix: canonicalize OpenCode + Pi tool-input arg keys #337 automatically start enforcing the moment failproofai upgrades — no failproofai policies --install --cli opencode re-run required. New canonicalizeToolInput helper gated by cli === "opencode" / "pi"; idempotent because the per-CLI maps key on camelCase / Pi-shape and don't match already-canonical snake_case input. Claude / Codex / Cursor / Copilot / Gemini sessions are unaffected.

Smoke-tested against the exact bug-report shape: an opencode read with a camelCase filePath pointing outside the repo cwd is now blocked with Access outside project directory blocked. In-cwd reads still pass; idempotent for already-canonical input; Pi path shape also blocked.

Test plan

  • bun run test:run — 1596 / 1596 passing
  • bun run lint — only an unrelated <img> warning in app/components/log-viewer/tool-input-output.tsx
  • bunx tsc --noEmit — clean
  • bun run build — Next.js + dist bundle build cleanly
  • Smoke test 1: stale-shim shape (tool_name: "read", tool_input: {filePath: "<outside-cwd>"}, --cli opencode) → permissionDecision: "deny" with Access outside project directory blocked reason
  • Smoke test 2: fresh-shim shape (tool_name: "Read", tool_input: {file_path: "<outside-cwd>"}) → blocked (idempotent — handler doesn't corrupt fresh-shim shape)
  • Smoke test 3: Pi shape (tool_name: "read", tool_input: {path: "<outside-cwd>"}, --cli pi) → blocked with Pi-flat {permission: "deny", reason} shape
  • Smoke test 4: in-cwd read (tool_input: {filePath: "<cwd>/package.json"}) → exit 0, no stdout (allow)
  • New unit tests in __tests__/hooks/handler.test.ts cover stale-shim shapes, full per-CLI map coverage parity (12 OpenCode + 6 Pi tool names, four Edit keys), idempotency, MCP passthrough, and the per-CLI gate (Claude payloads with literal filePath keys are NOT rewritten)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Canonicalized OpenCode and Pi tool names and input keys so path-checking policies (e.g., block-read-outside-cwd) reliably trigger; handler now performs defense-in-depth, per-CLI canonicalization.
  • Behavior

    • Improved prompt/session handling for certain tool events to ensure correct sequencing of user prompts and tool/permission decisions.
  • Tests

    • Added tests covering canonicalization, idempotency, unknown-tool passthrough, and per-CLI gating.

Review Change Stack

…calization for OpenCode/Pi

Two-layer fix for `block-read-outside-cwd` silently no-op'ing on OpenCode
inside this repo. PR #337 added `OPENCODE_TOOL_INPUT_MAP` to the
production shim template at `src/hooks/integrations.ts:buildOpenCodePluginShim`,
but the dev shim at `.opencode/plugins/failproofai.mjs` is hand-maintained
and was never regenerated, so contributors running `opencode` from inside
this repo could still read paths outside the repo cwd — `getFilePath()`
returned "" (camelCase `filePath` didn't match the policy's snake_case
`file_path` lookup) and the target-empty short-circuit at
`src/hooks/builtin-policies.ts:799` returned allow.

Layer 1 (dev shim): regenerate `.opencode/plugins/failproofai.mjs` to
mirror the production template — `TOOL_NAME_MAP` + `TOOL_INPUT_MAP` +
idempotent `canonicalizeTool` / `canonicalizeToolInput`. Also pick up
the `applyDecision` async-await change from #318 that the dev shim had
been missing, so Stop / SubagentStop force-retry actually awaits the
SDK call before the plugin handler returns.

Layer 2 (handler): add per-CLI canonicalization for OpenCode + Pi in
`src/hooks/handler.ts` as defense-in-depth, so a user-scope shim that
pre-dates #337 automatically starts enforcing the moment failproofai
upgrades — without forcing a `failproofai policies --install --cli opencode`
re-run. The shim canonicalization stays in place; both passes are
idempotent because the per-CLI maps key on camelCase / Pi-shape and
don't match already-canonical snake_case input. New `canonicalizeToolInput`
helper is gated by `cli === "opencode" / "pi"` so Claude / Codex / Cursor /
Copilot / Gemini sessions are unaffected.

New unit tests in `__tests__/hooks/handler.test.ts` cover:
  - Stale-shim shapes (`read` + `filePath` for OpenCode, `read` + `path` for Pi)
  - Per-CLI map coverage parity (12 OpenCode + 6 Pi tool names, four Edit keys)
  - Idempotency (handler doesn't corrupt fresh-shim shapes)
  - Unknown-tool passthrough (MCP `mcp_*` tool names + args)
  - Per-CLI gate (Claude payloads with literal `filePath` keys are not rewritten)

Smoke-tested against the exact pattern from the bug report: an opencode
`read` with a camelCase `filePath` pointing outside the repo cwd is now
blocked with a clear `Access outside project directory blocked` deny
reason. In-cwd reads still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9569a59c-8aad-4eae-9dba-9b960df67da6

📥 Commits

Reviewing files that changed from the base of the PR and between 33ebd51 and 8e88d1f.

📒 Files selected for processing (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

📝 Walkthrough

Walkthrough

This PR implements defense-in-depth tool-name and tool-input canonicalization for OpenCode and Pi CLIs. The handler canonicalizes incoming hook payloads to canonical PascalCase tool IDs and snake_case input keys before policy evaluation. The OpenCode plugin shim includes parallel canonicalization and refactors applyDecision to async, awaiting retry prompts selectively for Stop/SubagentStop events.

Changes

Tool-Name and Tool-Input Canonicalization

Layer / File(s) Summary
Data Mappings & Contracts
.opencode/plugins/failproofai.mjs, src/hooks/handler.ts
Introduces per-CLI tool-name and tool-input key mappings as constants defining which OpenCode/Pi identifiers and fields require canonicalization.
Handler Canonicalization Functions
src/hooks/handler.ts
Implements canonicalizeToolName() and canonicalizeToolInput() helpers that translate incoming OpenCode/Pi tool payloads to canonical PascalCase names and snake_case field keys.
Handler Event Processing
src/hooks/handler.ts
Integrates canonicalization into handleHookEvent() to mutate parsed.tool_name and parsed.tool_input before policy evaluation and downstream activity persistence.
OpenCode Plugin Shim Defense
.opencode/plugins/failproofai.mjs
Refactors applyDecision to async, conditionally awaiting session prompt retry for Stop/SubagentStop events, and integrates canonicalized tool names and inputs into tool pre/post execution hooks.
Tests & Documentation
CHANGELOG.md, __tests__/hooks/handler.test.ts
Adds changelog entry and comprehensive test coverage for handler-side canonicalization, stale shim handling, idempotency, unknown-tool passthrough, and per-CLI gating to verify canonicalization does not affect other CLIs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through tool ID trees,

Converting names with newfound ease,
From camelCase to snake and back,
Two layers guard the policy stack,
So stale shims won't cause a fray!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main changes: regenerating the OpenCode dev shim and implementing handler-side canonicalization for OpenCode/Pi tools, directly matching the PR's core objectives.
Description check ✅ Passed The description comprehensively covers root cause, both layers of the fix, test coverage, and validation results; all required checklist items are marked complete with test outcomes provided.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 3: Update the CHANGELOG.md entry for version "0.0.10-beta.11" so the date
matches today's date (change "2026-05-09" to "2026-05-10") and ensure the
heading remains formatted as "## 0.0.10-beta.11 — 2026-05-10" to comply with the
guideline that changelog entries use the package.json version with today's date.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 71068430-d140-4125-a11b-dee6064b7019

📥 Commits

Reviewing files that changed from the base of the PR and between 3118773 and 33ebd51.

📒 Files selected for processing (4)
  • .opencode/plugins/failproofai.mjs
  • CHANGELOG.md
  • __tests__/hooks/handler.test.ts
  • src/hooks/handler.ts

Comment thread CHANGELOG.md Outdated
…tion

CodeRabbit flagged the freshly-added section heading dated 2026-05-09
while the PR was opened at 2026-05-10T02:26Z UTC. Bump the date so the
heading is internally consistent with the prior `## 0.0.10-beta.10 —
2026-05-10` section and with the GH server clock that downstream
release tooling reads.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@NiveditJain NiveditJain merged commit cc03adf into main May 10, 2026
9 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

Development

Successfully merging this pull request may close these issues.

1 participant