This document analyzes the feasibility of making Claude Code plugins compatible with OpenCode, enabling plugins to work in both environments.
Compatibility Level: High
OpenCode and Claude Code share remarkably similar architectures for commands and agents (both use markdown files with YAML frontmatter). OpenCode also has native support for CLAUDE.md files, making cross-compatibility very achievable with minor adaptations.
| Aspect | Claude Code | OpenCode |
|---|---|---|
| Manifest | .claude-plugin/plugin.json |
opencode.json |
| Plugin Location | plugins/ directory |
.opencode/ or ~/.config/opencode/ |
| NPM Support | No | Yes ("plugin": ["package-name"]) |
| Aspect | Claude Code | OpenCode |
|---|---|---|
| Location | commands/*.md |
.opencode/commands/*.md |
| Format | Markdown + YAML frontmatter | Markdown + YAML frontmatter |
| Invocation | /command-name |
/command-name |
| Arguments | argument-hint field |
$ARGUMENTS, $1, $2 in template |
| Tool Restriction | allowed-tools: Read, Write |
Not in frontmatter (uses agent config) |
| Model Override | model: sonnet |
model: provider/model-id |
| Agent Assignment | N/A | agent: agent-name |
Key Differences:
- OpenCode supports shell injection with
!`command`syntax - OpenCode has
subtaskoption for specialized execution contexts - Claude Code restricts tools per-command; OpenCode restricts per-agent
| Aspect | Claude Code | OpenCode |
|---|---|---|
| Location | agents/*.md |
.opencode/agents/*.md |
| Format | Markdown + YAML frontmatter | Markdown + YAML frontmatter |
| Name Field | name: agent-name |
Filename becomes name |
| Description | description: |
description: (required) |
| Tools | tools: Read, Glob, Grep |
tools: { read: true, glob: true } |
| Model | model: sonnet |
model: provider/model-id |
| Mode | Implicit (all are subagents) | mode: primary|subagent |
| Temperature | Not supported | temperature: 0.0-1.0 |
| Max Steps | Not supported | maxSteps: N |
OpenCode-Only Features:
temperaturecontrolmaxStepslimit for agentic iterationstaskPermissionsto control subagent invocation- Primary vs subagent distinction
| Aspect | Claude Code | OpenCode |
|---|---|---|
| Project File | CLAUDE.md |
AGENTS.md (falls back to CLAUDE.md) |
| Global File | ~/.claude/CLAUDE.md |
~/.config/opencode/AGENTS.md |
| Skills Directory | ~/.claude/skills/ |
Supported via compatibility layer |
Native Compatibility: OpenCode reads CLAUDE.md files if no AGENTS.md exists.
| Aspect | Claude Code | OpenCode |
|---|---|---|
| Definition | hooks/hooks.json + shell scripts |
JavaScript/TypeScript modules |
| Events | SessionStart, PostToolUse, Stop, etc. |
session.created, tool.execute.before, etc. |
| Execution | Shell commands | Async JavaScript functions |
Event Mapping:
| Claude Code Event | OpenCode Equivalent |
|---|---|
SessionStart |
session.created |
PostToolUse |
tool.execute.after |
PreToolUse |
tool.execute.before |
Stop |
session.idle or custom |
PreCompact |
experimental.session.compacting |
| Aspect | Claude Code | OpenCode |
|---|---|---|
| Built-in Tools | ~15 tools | 13 tools |
| Custom Tools | Not directly supported | TypeScript/Zod definitions |
| Permissions | Per-command frontmatter | Global config (allow/deny/ask) |
| MCP Support | Yes | Yes |
Common Built-in Tools: bash, edit, write, read, grep, glob, todowrite, webfetch
OpenCode Additions: list, lsp, patch, skill, question, todoread
| Component | Compatibility | Effort Required |
|---|---|---|
| CLAUDE.md / AGENTS.md | Native | None |
| Commands | High | Frontmatter translation |
| Agents | High | Frontmatter translation |
| Bash Scripts | Native | None |
| Hooks | Low | Rewrite in JavaScript |
| Custom Tools | Low | Rewrite in TypeScript |
| Skills | Medium | Path/structure adaptation |
plugins/{plugin-name}/
├── .claude-plugin/
│ └── plugin.json # Claude Code manifest
├── .opencode/
│ ├── commands/ # OpenCode-format commands
│ │ └── {command}.md
│ └── agents/ # OpenCode-format agents
│ └── {agent}.md
├── commands/ # Claude Code commands (source of truth)
│ └── {command}.md
├── agents/ # Claude Code agents (source of truth)
│ └── {agent}.md
├── hooks/
│ ├── hooks.json # Claude Code hooks
│ └── *.sh # Shared shell scripts
├── opencode-hooks/ # OpenCode JavaScript hooks
│ └── index.ts
├── scripts/ # Shared utilities (both platforms)
│ └── *.sh
└── build/
└── translate.ts # Build script for translation
Claude Code (commands/backlog.md):
---
description: Display task status summary from tasks/INDEX.md
argument-hint: [--graph] [--critical-path] [--order]
allowed-tools: Read, Write, Edit, Grep, Glob, AskUserQuestion, Bash, TodoWrite
model: sonnet
---OpenCode (.opencode/commands/backlog.md):
---
description: Display task status summary from tasks/INDEX.md
agent: backlog-agent
model: anthropic/claude-sonnet
---Note: OpenCode tool restrictions go on the agent, not the command.
Claude Code (agents/code-auditor.md):
---
name: code-auditor
description: Comprehensive automated code review that populates REVIEW.md
tools: Read, Glob, Grep, Bash
model: sonnet
---OpenCode (.opencode/agents/code-auditor.md):
---
description: Comprehensive automated code review that populates REVIEW.md
mode: subagent
model: anthropic/claude-sonnet
tools:
read: true
glob: true
grep: true
bash: true
write: false
edit: false
---Claude Code (hooks/hooks.json):
{
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
}]
}]
}
}OpenCode (opencode-hooks/index.ts):
import { execSync } from 'child_process'
export const BacklogPlugin = async ({ directory }) => {
return {
'session.created': async () => {
execSync('./hooks/session-start.sh', { cwd: directory })
}
}
}- Keep
CLAUDE.mdas the single source - OpenCode reads it natively - Share all bash scripts in
scripts/- work identically in both - Document command/agent equivalents
- Create a translation script that:
- Reads Claude Code command/agent markdown
- Transforms frontmatter fields
- Outputs to
.opencode/directories
- Add to CI/CD or pre-commit hooks
- Create JavaScript wrappers that call existing shell scripts
- Map Claude Code events to OpenCode events
- Package as OpenCode plugin module
- Package adapted plugins for npm
- Users install via
opencode.json:"plugin": ["@cyotee/backlog-plugin"]
Features available in OpenCode that could enhance plugins:
- LSP Integration - Code intelligence (go-to-definition, references)
- Temperature Control - Fine-tune agent creativity
- Max Steps - Limit agentic iterations
- Patch Tool - Apply unified diffs
- Question Tool - Structured user prompts
- Session Compaction Hooks - Inject context during summarization
OpenCode respects these for Claude Code compatibility:
# Disable CLAUDE.md reading
OPENCODE_DISABLE_CLAUDE_MD=1
# Disable global Claude config
OPENCODE_DISABLE_CLAUDE_GLOBAL=1
# Disable Claude skills directory
OPENCODE_DISABLE_CLAUDE_SKILLS=1- OpenCode Plugins Documentation
- OpenCode Tools Documentation
- OpenCode Commands Documentation
- OpenCode Rules Documentation
- OpenCode Agents Documentation
- OpenCode Custom Tools Documentation
The following Claude Code plugin features could not be directly converted to OpenCode:
Claude Code:
{
"hooks": {
"SessionStart": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh" }],
"PostToolUse": [{ "matcher": "Write|Edit", "command": "..." }],
"Stop": [{ "command": "..." }]
}
}Why not convertible:
- OpenCode uses JavaScript event handlers, not shell commands
- Different event model (
session.createdvsSessionStart) - Requires rewriting hooks as TypeScript modules
Workaround: Create opencode-hooks/index.ts that wraps existing shell scripts:
import { execSync } from 'child_process'
export const Plugin = async ({ directory }) => ({
'session.created': () => execSync('./hooks/session-start.sh', { cwd: directory })
})Claude Code: Commands reference scripts via ${CLAUDE_PLUGIN_ROOT}/scripts/deps.sh
Why not convertible:
- OpenCode has no equivalent plugin root variable
- Scripts need to be either:
- Installed to a known location
- Referenced by absolute path
- Bundled with npm package
Workaround: Use relative paths or install scripts globally.
Claude Code: /backlog:launch, /design:init (plugin:command format)
OpenCode: /backlog-launch, /design-init (flat namespace by filename)
Why not convertible:
- OpenCode commands are identified by filename
- No built-in plugin namespace mechanism
- Cross-references need different syntax
Workaround: Use hyphenated names (/backlog-launch instead of /backlog:launch).
Claude Code:
---
allowed-tools: Read, Write, Edit, Grep, Glob
---OpenCode: Tool restrictions are per-agent, not per-command.
Workaround: Create dedicated agents with restricted tool access for sensitive commands.
Claude Code:
skills/
code-reviewer/
SKILL.md
checklist.md
patterns.md
OpenCode: Skills are loaded via the skill tool with different structure.
Workaround: Adapt SKILL.md files to OpenCode's expected format.
Claude Code: PreToolUse and PostToolUse hooks with matcher patterns and prompt-based responses.
OpenCode: Uses tool.execute.before and tool.execute.after with JavaScript handlers.
Why not convertible:
- Claude Code hooks can return natural language prompts
- OpenCode hooks return programmatic results
Claude Code: Stop hook checks for <promise>TASK_COMPLETE</promise> tags.
OpenCode: No equivalent mechanism for preventing session exit based on content.
Workaround: Use session management differently or implement custom validation.
When migrating a Claude Code plugin to OpenCode:
- Commands (
.mdfiles) - Translate frontmatter, update cross-references - Agents (
.mdfiles) - Translate frontmatter, addmodefield -
opencode.jsonmanifest - Create fromplugin.json - Hooks - Rewrite as TypeScript event handlers
- Skills - Adapt to OpenCode skill format
- Scripts - Update paths, remove
${CLAUDE_PLUGIN_ROOT} - Cross-references - Update
/plugin:commandto/plugin-command