Files

12 KiB

OpenCode Compatibility

This extension's flat permission format and evaluation semantics were directly inspired by OpenCode's permission model (v1.1.x permission rework). If you are familiar with OpenCode's permission system, most concepts transfer directly — the same mental model applies.

Point-in-time reference. This comparison reflects OpenCode as of May 2026. See the official OpenCode permissions docs for the latest upstream behavior.

What Transfers Directly

The following concepts are shared between OpenCode and this extension:

Concept Description
Three actions allow / ask / deny — identical semantics
Flat permission object Top-level key in config; surface names as keys
"*" universal fallback Sets the default action when no surface-specific rule matches
Granular object syntax Surface key → string (catch-all) or { pattern: action } map
Last-match-wins When multiple patterns match, the last one in config order wins
* wildcard Matches zero or more of any character (including path separators)
? wildcard Matches exactly one character
Home directory expansion ~/, $HOME/, and ${HOME}/ expand to the OS home directory in patterns
external_directory surface Gates access to paths outside the working directory
bash surface Command patterns matched against shell commands
skill surface Skill name patterns matched against skill invocations
task surface Gates subagent/delegation tool calls
Session-scoped approvals once / always / reject from the ask dialog; always adds a session rule
Per-agent overrides Override global permissions for specific agents
Tool hiding Denied tools are removed before the agent starts (no wasted turns probing)
Bash path extraction Tree-sitter AST parsing to detect external paths in shell commands (see details below)
Bash arity table Generates smart approval pattern suggestions (e.g., git checkout * not git *)
Trailing wildcard optionality "ls *" matches bare "ls" — the trailing * is optional

If your OpenCode config uses these features, the equivalent works in this extension with minimal translation (see Porting Guide below).

Where They Diverge

Summary Table

Area OpenCode This extension
Default fallback "*": "allow" (permissive) "*": "ask" (least privilege)
.env file protection Built-in read rules deny/ask .env files No built-in rules; user configures with the cross-cutting path surface or per-tool path patterns (see porting guide)
Cross-cutting path gate No equivalent — .env protection is per-tool only path surface denies/asks across all tools and bash at once; a path deny cannot be overridden by a per-tool allow
OpenCode-only surfaces lsp, question, webfetch, websearch, todowrite, doom_loop Not applicable — Pi does not expose these tools or events
File mutation surfaces edit covers edit, write, apply_patch Separate write and edit surfaces
Search/discovery surfaces glob, grep, list find, grep, ls (Pi tool names)
mcp surface Not a documented permission surface First-class with server/tool-level granularity
Top-level string shorthand "permission": "allow" sets all surfaces Not supported; must use an object
Per-agent config location agent key in config JSON or YAML frontmatter YAML frontmatter in agent .md files only
Config file paths ~/.config/opencode/opencode.json ~/.pi/agent/extensions/pi-permission-system/config.json
Subagent prompt forwarding Not documented ask policies work in non-UI subagent contexts
Infrastructure auto-allow N/A Read-only tools to Pi infra dirs bypass the gate
Permission review log No equivalent documented Writes decisions to a JSONL audit log

Notable Differences Explained

Default Fallback: allow vs ask

OpenCode defaults to permissive — most tools work without configuration. This extension defaults to least privilege — omitting "*" gives you "ask" for everything.

If you want OpenCode-like permissiveness:

{
  "permission": {
    "*": "allow",
    "external_directory": "ask"
  }
}

File Mutation Surfaces

OpenCode unifies all file writes under a single edit permission. This extension exposes Pi's actual tool names: write (create/overwrite) and edit (targeted replacement).

To replicate OpenCode's unified behavior, set both to the same action:

{
  "permission": {
    "write": "ask",
    "edit": "ask"
  }
}

MCP Surface (Pi-Only)

This extension provides a first-class mcp permission surface with granular server and tool-level control:

{
  "permission": {
    "mcp": {
      "*": "ask",
      "mcp_status": "allow",
      "myServer:*": "ask",
      "dangerousServer": "deny"
    }
  }
}

OpenCode does not expose MCP as a configurable permission surface.

Bash Path Extraction

Both systems use web-tree-sitter + tree-sitter-bash to parse shell commands into an AST for external_directory path detection, but the extraction strategies differ significantly:

OpenCode only extracts paths from a hardcoded allowlist of file-manipulating commands (rm, cp, mv, mkdir, touch, chmod, chown, cat, plus PowerShell equivalents). Commands not in the list — including sed, awk, grep — get no path extraction at all. For allowlisted commands, all non-flag positional arguments are assumed to be paths.

This extension extracts path candidates from all commands generically, then applies additional intelligence:

  • A PATTERN_FIRST_COMMANDS map understands flag arity for sed, awk, grep, rg, and similar tools, distinguishing inline patterns/scripts from file arguments to avoid false positives.
  • Redirect destinations (> /path/to/file) are extracted.
  • Heredoc bodies, comments, and variable assignments are skipped.

The result is broader coverage (paths detected in any command, not just a curated list) with fewer false positives on pattern-first commands (no spurious prompts for sed regexes or grep patterns that happen to contain /).

Porting an OpenCode Config

Before (OpenCode)

{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "*": "allow",
    "bash": {
      "*": "ask",
      "git *": "allow",
      "npm *": "allow",
      "rm *": "deny"
    },
    "edit": {
      "*": "ask",
      "src/*.ts": "allow"
    },
    "external_directory": {
      "~/projects/*": "allow"
    }
  }
}

After (this extension)

{
  "$schema": "https://raw.githubusercontent.com/gotgenes/pi-packages/main/packages/pi-permission-system/schemas/permissions.schema.json",
  "permission": {
    "*": "allow",
    "bash": {
      "*": "ask",
      "git *": "allow",
      "npm *": "allow",
      "rm *": "deny"
    },
    "write": "ask",
    "edit": "ask",
    "external_directory": {
      "*": "ask",
      "~/projects/*": "allow"
    }
  }
}

Key Translation Steps

  1. Replace "permission": "allow" (top-level string) with "permission": { "*": "allow" }.

  2. Split edit into separate write and edit entries if you need different policies for create vs. modify. If not, set both to the same action.

  3. Rename search surfaces: globfind, listls.

  4. Add .env rules manually if you relied on OpenCode's built-in protection. The path surface is the recommended approach — it covers all tools and bash in one rule:

    {
      "permission": {
        "path": {
          "*": "allow",
          "*.env": "deny",
          "*.env.*": "deny",
          "*.env.example": "allow"
        }
      }
    }
    

    Alternatively, use per-tool patterns if you only need to protect specific tools (e.g., read):

    {
      "permission": {
        "read": {
          "*": "allow",
          "*.env": "deny",
          "*.env.*": "deny",
          "*.env.example": "allow"
        }
      }
    }
    
  5. Remove OpenCode-only surfaces (lsp, question, webfetch, websearch, todowrite, doom_loop) — they have no effect in this extension.

  6. Add mcp rules if you use MCP servers — OpenCode has no equivalent, so this is new configuration.