-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Description
Summary
OpenCode already has excellent Claude Code compatibility for rules (CLAUDE.md), skills (~/.claude/skills/), and can be disabled via OPENCODE_DISABLE_CLAUDE_CODE* env vars. However, Claude Code's hooks system (PreToolUse, PostToolUse, Stop defined in ~/.claude/settings.json) is not supported natively.
Problem
Users who run both Claude Code and OpenCode maintain hooks in ~/.claude/settings.json that enforce guardrails:
- PreToolUse — Block dangerous operations (e.g.,
git pushwithout tests, direct prod DB access, editing governed files without reading the architecture skill first) - PostToolUse — Track state (e.g., mark that tests were run, mark that code files were edited)
- Stop — Verify the dev-loop SOP was followed before the agent stops (tests run, committed, deployed, QA'd)
These hooks are Python/bash scripts that receive JSON on stdin (tool_name, tool_input, transcript_path) and use exit codes to signal behavior (0=allow, 2=block+feedback via stderr).
Without native support, users must either:
- Install
oh-my-opencode(massive opinionated plugin that takes over the entire agent setup just for hook compat) - Write a custom bridge plugin (works for PreToolUse/PostToolUse but Stop hooks lose transcript access and can't re-activate the agent)
Proposed Solution
Add native Claude Code hook execution, similar to how CLAUDE.md and skills are already handled:
- Read hooks from
~/.claude/settings.json(andsettings.local.json) — parse thehooksobject - Map to OpenCode events:
PreToolUse→tool.execute.before— transform tool names (bash→Bash,edit→Edit), pipe JSON stdin, respect exit code 2 as blockPostToolUse→tool.execute.after— same protocol, informationalStop→session.idle— providetranscript_pathwith full session transcript (tool calls + assistant text), and critically: if exit code 2, re-inject stderr as a prompt to continue (this is how Claude Code's Stop hooks enforce SOP completion)
- Disable via env var:
OPENCODE_DISABLE_CLAUDE_CODE_HOOKS=1(consistent with existingOPENCODE_DISABLE_CLAUDE_CODE_*pattern)
Key Gap: Stop Hook Re-activation
The most critical missing piece is Stop hook re-activation. In Claude Code, when a Stop hook exits with code 2, the stderr message is injected back as a prompt and the agent continues working. This is how dev-loop SOPs are enforced — the hook says "you edited code but didn't run tests" and the agent resumes to run tests.
OpenCode's session.idle event is fire-and-forget. The feature request specifically asks for a mechanism where a session.idle handler can signal "don't stop, inject this message instead" — either via return value or a callback.
Protocol Reference
Claude Code hook stdin format:
{
"session_id": "...",
"cwd": "/path/to/project",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "git push origin main" },
"transcript_path": "/tmp/transcript.jsonl"
}Exit codes:
0= allow (stderr is informational, shown to LLM but doesn't block)2= block (PreToolUse) or re-activate (Stop) — stderr is the feedback message
Why Native vs Plugin
A plugin can handle PreToolUse/PostToolUse adequately, but Stop hook re-activation requires OpenCode core support — plugins can't force the agent to resume from session.idle. This is why native support matters.