anomalyco / anomalyco/opencode

plugin: permission.evaluate ask verdicts are ignored on beta-19151 (deny still honored)

Open
#47,495 2 comments 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Sep 5, 2026.

2.0
Dominant language
TypeScript
Stars
209k
Forks
27.5k
Avg merge
7h 2m
Merged PRs (30d)
384

Description

Summary

On 0.0.0-beta-19151, an ask verdict set by a permission.evaluate plugin hook is ignored: the tool executes as if allowed and no prompt ever surfaces. A deny verdict from the same hook is still honored (permission.rejected). Any plugin implementing approval policies through this hook (ask-tier) is therefore silently bypassed on this beta, while deny-tier keeps working.

Environment

  • opencode version: opencode2 v0.0.0-beta-19151
  • OS: macOS, Darwin 25.5.0 (darwin arm64)
  • Terminal: VS Code integrated terminal (TERM_PROGRAM=vscode, TERM=xterm-256color)
  • Shell: /bin/zsh
  • Install/channel: beta
  • Active plugins: one local file plugin (~/.config/opencode/plugins/security-guard.js, V2 object form) plus, during the investigation only, a minimal tap plugin (removed afterwards; production state restored and verified healthy via scripts/doctor.mjs --live)

Reproduction

  1. Install a minimal tap plugin (V2 object form) at ~/.config/opencode/plugins/zzz-eval-tap.js:

    import { appendFileSync } from "node:fs"
    export default {
      id: "zzz-eval-tap",
      async setup(ctx) {
        await ctx.permission.hook("evaluate", (event) => {
          try {
            const res = Array.isArray(event?.resources) ? event.resources : []
            if (res.some((r) => String(r).includes("CANARY-ASK-TEST")) && event) {
              event.effect = "ask"
              event.message = "tap canary: direct ask, no plugin logic involved"
            }
            if (res.some((r) => String(r).includes("CANARY-DENY-TEST")) && event) {
              event.effect = "deny"
              event.message = "tap canary: direct deny, no plugin logic involved"
            }
            appendFileSync("/tmp/eval-tap.log", JSON.stringify({
              action: event?.action, effect: event?.effect, n: res.length,
            }) + "\n")
          } catch {}
        })
      },
    }
    
  2. Restart the service (opencode2 service restart).

  3. In an agent session, run echo CANARY-ASK-TEST.

  4. Observe: the command executes (CANARY-ASK-TEST is printed, no prompt), while /tmp/eval-tap.log contains {"action":"shell","effect":"ask","n":…} — proving the hook fired and the ask verdict was recorded, then ignored.

  5. Run echo CANARY-DENY-TEST. Observe: the command is blocked with permission.rejected carrying the tap message — deny is honored.

Expected Behavior

An ask verdict set by a permission.evaluate hook produces an approval prompt (the behavior on 0.0.0-beta-19086 for non-cd commands, per #47356), blocking execution until the user responds.

Actual Behavior

The ask verdict is silently dropped: execution proceeds as if the effect were allow. Consistently reproducible across many commands and several service restarts. deny from the same hook works correctly, and tool.execute.before throws still block — the gap is specific to the ask effect of the permission pipeline.

Impact: every ask-tier policy implemented via permission.evaluate (approval prompts for ambiguous operations) silently becomes allow on this beta. Only deny-tier enforcement can be relied on.

Additional Context

  • Related: #47356 (permission.evaluate not firing for cd-led commands on beta-19086 — same channel family, one beta earlier).
  • Secondary observation: on beta-19151, shell resources arrive pre-segmented — one resource per ;-segment (echo a; echo b yields n:2; a 4-statement command yielded n:4). The deny canary confirms a deny on the whole event still blocks the full command. Plugin authors matching whole-command patterns against resources should be aware the unit changed from whole-command (19086) to per-segment (19151).
  • Service log note: per-command evaluated permission=… INFO lines present on older betas no longer appear at all on 19151 (only spawning process lines), consistent with an evaluation-pipeline change.
  • Workaround used by the affected plugin: enforce ask-tier verdicts for shell commands as hard blocks inside tool.execute.before (which still throws reliably), at the cost of losing the approve-and-continue workflow for legitimate ambiguous operations.
  • Evidence method: tap log lines plus observed command outcomes; the tap plugin was removed and the service restarted afterwards (only the production plugin remains; doctor --live healthy).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.