@khalilgharbaoui/opencode-claude-code-plugin 0.18.3 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -63
- package/dist/index.d.ts +100 -22
- package/dist/index.js +2383 -1470
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +60 -15
package/README.md
CHANGED
|
@@ -2,49 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@khalilgharbaoui/opencode-claude-code-plugin)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use Claude models inside [opencode](https://opencode.ai) by driving the official **Claude Code CLI** (`claude`) as a subprocess. opencode therefore inherits whatever authentication that CLI already holds: a Claude subscription login, an API key, Bedrock, or Vertex. This plugin never reads, stores, or replays an OAuth token of its own.
|
|
6
|
+
|
|
7
|
+
- **Your CLI's auth, untouched.** Because `claude` does the authenticating, there is no subscription token here to lift and replay against the Anthropic API. That replay is what proxy-style opencode plugins do, it is a practice Anthropic has disallowed for third-party tools in 2026, and it is structurally not something this plugin can do.
|
|
8
|
+
- **opencode stays in charge of your machine.** Bash, Edit, Write, WebFetch and subagent dispatch are executed by opencode, behind its permission prompts and audit log, rather than by Claude Code. See [Selective tool proxy](#selective-tool-proxy).
|
|
9
|
+
- **Headless by default, which has a billing consequence.** `claude --print` usage on a subscription plan draws from the separate Agent SDK / extra-usage allowance rather than from normal plan usage; API-key authentication is unaffected. See [Billing](#billing).
|
|
6
10
|
|
|
7
11
|
> Maintained fork of [`unixfox/opencode-claude-code-plugin`](https://github.com/unixfox/opencode-claude-code-plugin). Published as `@khalilgharbaoui/opencode-claude-code-plugin` on npm.
|
|
8
12
|
|
|
9
13
|
---
|
|
10
14
|
|
|
11
|
-
##
|
|
15
|
+
## Quickstart
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
# 1. Make sure `claude` is installed and logged in
|
|
15
|
-
claude --version
|
|
17
|
+
### 1. Install and log in the Claude Code CLI
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
The plugin drives an existing [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code); it does not bundle one. Check that `claude` is on your `$PATH` and authenticated:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
claude --version # e.g. 2.1.263 (Claude Code)
|
|
23
|
+
claude auth status # which account you are signed in as
|
|
24
|
+
claude auth login # run this if you are not signed in yet
|
|
18
25
|
```
|
|
19
26
|
|
|
27
|
+
`login`, `status` and `logout` are the `claude auth` subcommands as of 2.1.263. Run `claude auth --help` if your install differs.
|
|
28
|
+
|
|
29
|
+
### 2. Add the plugin to your opencode config
|
|
30
|
+
|
|
31
|
+
opencode reads a global config at `~/.config/opencode/opencode.json` (or `$XDG_CONFIG_HOME/opencode/` when that is set). A project-level `opencode.json` in your repo overrides the global one, and `OPENCODE_CONFIG=/path/to/config.json` points opencode at one specific file instead. Put the plugin in the global config so every project gets it:
|
|
32
|
+
|
|
20
33
|
```json
|
|
21
34
|
{
|
|
22
35
|
"plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"]
|
|
23
36
|
}
|
|
24
37
|
```
|
|
25
38
|
|
|
26
|
-
That
|
|
39
|
+
That package spec is the whole install. Do **not** `npm install` the package yourself: opencode resolves and caches plugin packages on its own. You do not need a `provider` block either, unless you want to change one of the [options](#options-reference).
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
### 3. Restart opencode and verify
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
Quit opencode fully and relaunch it: plugins are loaded once, at process start, so a reload is not enough.
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
In the model picker you should now see a provider called **Claude Code (Default)** holding entries such as `Claude Haiku 4.5 (1×)`, `Claude Sonnet 5 (3×)` and `Claude Opus 5 (5×)`. The `(N×)` suffix is each model's list price relative to Haiku; see [Models](#models). Pick one and send a message.
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude` on your `$PATH`)
|
|
36
|
-
- Node 18+ / Bun
|
|
47
|
+
If the provider does not appear, turn on the plugin's log file and look for its one startup line:
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
```bash
|
|
50
|
+
OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode
|
|
51
|
+
grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
|
|
52
|
+
```
|
|
39
53
|
|
|
40
|
-
|
|
54
|
+
That single `NOTICE: claude-code plugin ready` entry reports the plugin version, the `claude` binary and version it found, the directory it will spawn in, and which providers registered. [Startup diagnostics](#startup-diagnostics) explains every field.
|
|
55
|
+
|
|
56
|
+
### Not seeing a version you just upgraded to?
|
|
57
|
+
|
|
58
|
+
opencode resolves the `@latest` plugin spec once and freezes the concrete version into its own package cache, so restarting never re-resolves the tag. Delete the cache entry and relaunch:
|
|
41
59
|
|
|
42
60
|
```bash
|
|
43
|
-
|
|
61
|
+
rm -rf ~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest
|
|
44
62
|
```
|
|
45
63
|
|
|
46
|
-
Then add it to `opencode.json` as shown in the TL;DR.
|
|
47
|
-
|
|
48
64
|
### Local development
|
|
49
65
|
|
|
50
66
|
```bash
|
|
@@ -62,11 +78,13 @@ In your `opencode.json`, point at the local build with a `file://` URL:
|
|
|
62
78
|
}
|
|
63
79
|
```
|
|
64
80
|
|
|
81
|
+
CI installs and builds on **Node 24** (`.github/workflows/publish.yml`), which is the only version this package is built against. `package.json` declares no `engines` range, so older Node versions are untested rather than deliberately unsupported. opencode itself may run under Bun; the [interactive transport](#interactive-transport-experimental) requires that.
|
|
82
|
+
|
|
65
83
|
---
|
|
66
84
|
|
|
67
85
|
## Models
|
|
68
86
|
|
|
69
|
-
The plugin auto-registers the following
|
|
87
|
+
The plugin auto-registers the following, and they appear in the model picker with no extra config: Haiku 4.5, Sonnet 4.5/4.6/5, Opus 4.5/4.6/4.7/4.8/5 (plus two fast-mode Opus entries), Fable 5/5.1 and Mythos 5/5.1, each except Haiku carrying `low` / `medium` / `high` / `xhigh` / `max` reasoning variants.
|
|
70
88
|
|
|
71
89
|
| ID | Display name | Context | Output | Reasoning variants | Price × |
|
|
72
90
|
|---|---|---|---|---|---|
|
|
@@ -120,8 +138,11 @@ Variants set the underlying reasoning effort. They're regular opencode model var
|
|
|
120
138
|
|
|
121
139
|
## Billing
|
|
122
140
|
|
|
123
|
-
|
|
124
|
-
|
|
141
|
+
By default this plugin drives Claude Code headlessly (the Agent SDK path, `claude --print`). Since June 2026, headless usage on a Claude subscription plan draws from a separate Agent SDK credit / extra usage rather than from normal plan usage. Authenticating the CLI with an API key is unaffected by that policy and bills as ordinary API usage.
|
|
142
|
+
|
|
143
|
+
Anthropic's own page is the authoritative and current source, including the amounts, which change: <https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan>
|
|
144
|
+
|
|
145
|
+
Two things in this plugin interact with the above. [`ignoreAnthropicApiKey`](#options-reference) stops a stray `ANTHROPIC_API_KEY` in your environment from silently redirecting the CLI onto pay-as-you-go API billing. The experimental [interactive transport](#interactive-transport-experimental) drives the real `claude` TUI instead of `--print`, which bills as normal plan usage.
|
|
125
146
|
|
|
126
147
|
---
|
|
127
148
|
|
|
@@ -230,10 +251,10 @@ That beats whatever effort the call arrived with. It has to, because opencode re
|
|
|
230
251
|
|
|
231
252
|
An agent that declares nothing keeps the inherited effort, so this changes nothing until a file asks for it. An unrecognised level is refused and the inherited one kept, since the CLI rejects a level it does not know. Compaction is exempt: its summary always gets the full budget.
|
|
232
253
|
|
|
233
|
-
To force an **account** rather than a model, pin the full string. Both halves are needed, because the provider selects the account's config dir and the `@account` marker is what the model was registered under for that provider:
|
|
254
|
+
To force an **account** rather than a model, pin the full string. This only applies if you declared [`accounts`](#multiple-claude-code-accounts) in the first place; with the default single-account setup there is nothing to pin. Both halves are needed, because the provider selects the account's config dir and the `@account` marker is what the model was registered under for that provider:
|
|
234
255
|
|
|
235
256
|
```yaml
|
|
236
|
-
model: claude-code-
|
|
257
|
+
model: claude-code-work/claude-opus-5@work
|
|
237
258
|
```
|
|
238
259
|
|
|
239
260
|
### Options reference
|
|
@@ -259,33 +280,61 @@ model: claude-code-appical/claude-opus-5@appical
|
|
|
259
280
|
|
|
260
281
|
| Option | Type | Default | Description |
|
|
261
282
|
|---|---|---|---|
|
|
262
|
-
| `cliPath` | string | `
|
|
263
|
-
| `accounts` | string[] | – | Optional
|
|
264
|
-
| `cwd` | string |
|
|
265
|
-
| `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`.
|
|
266
|
-
| `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to `claude --permission-mode`. |
|
|
283
|
+
| `cliPath` | string | `"claude"` | Path to the `claude` executable (a binary, not a shell command with flags). opencode's config hook seeds this with `"claude"`, so under opencode this default always applies; `CLAUDE_CLI_PATH` is only consulted when `createClaudeCode()` is called directly and the option is absent. Account providers wrap it with a generated script; never point it at one of those yourself. |
|
|
284
|
+
| `accounts` | string[] | – | **Optional.** Most setups need no accounts at all: with this unset you get a single `Claude Code (Default)` provider on your normal `~/.claude` login. Supply names only to run several Claude logins side by side; `default` stays implicit, so `["work", "personal"]` gives you `Claude Code (Default)`, `Claude Code (Work)` and `Claude Code (Personal)`. See [Multiple Claude Code accounts](#multiple-claude-code-accounts). |
|
|
285
|
+
| `cwd` | string | see description | Working directory for the spawned CLI. Resolved **lazily per request**, first match winning: this explicit value, then the opencode session's own `directory` (so `opencode serve` and the web UI spawn in the right project even though one server handles many), then `process.cwd()` when it is a real directory, then the project directory captured at plugin init (this rescues macOS GUI launches, where `process.cwd()` is `/`), and finally `process.cwd()` regardless. [Startup diagnostics](#startup-diagnostics) reports which tier won. Session tier contributed by [@galvani](https://github.com/galvani). |
|
|
286
|
+
| `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. It is still passed when `proxyTools` is set: proxied calls go through opencode's permission system regardless, but unproxied CLI built-ins do not. The one case where the flag is dropped is `permissionMode: "plan"`, because the CLI lets the skip flag override plan mode outright. See [Plan mode](#plan-mode). |
|
|
287
|
+
| `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to headless `claude --permission-mode`. `"plan"` also suppresses `--dangerously-skip-permissions` (see the row above). Not version-gated, so check that your installed CLI accepts the value. The [interactive transport](#interactive-transport-experimental) does not forward it. |
|
|
288
|
+
| `defaultSubagentModel` | string | – | Model that plugin-discovered `mode: subagent` agents run on when their own definition pins nothing. The caller's account is kept; only the model name changes. An agent's own `forceModel` wins over it, and an unknown id is refused rather than spawned. Unset means no implicit override at all. See [Subagents: your account, their model](#subagents-your-account-their-model). |
|
|
267
289
|
| `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Claude built-in tools to route through opencode's executor + permission UI. Opt-in extras: `"Question"`, `"Compress"`. See [Selective tool proxy](#selective-tool-proxy). |
|
|
268
290
|
| `extraDisallowedTools` | string[] | – | Extra Claude built-ins to switch off with `--disallowedTools`, on top of what `proxyTools` implies. Claude's names, e.g. `["NotebookEdit"]`. See [Closing a tool with no proxy](#closing-a-tool-with-no-proxy). |
|
|
269
|
-
| `proxyToolTimeoutMs` | `Record<string, number>` | – |
|
|
270
|
-
| `planModeQuestion` | boolean | `false` | Route `ExitPlanMode` approval through opencode's native `question` tool instead of a text "(yes/no)" prompt. Opt-in
|
|
291
|
+
| `proxyToolTimeoutMs` | `Record<string, number>` | – | Optional wall-clock backstop per proxy tool, in ms, keyed by proxy tool name (`bash`, `task`, …). A call normally ends on an event the plugin listens for (result, abort, next message, process exit, chat deletion), not on a timer; see [How a proxied call ends](#how-a-proxied-call-ends). Defaults: 10 min flat, `task` / `task_batch` → none, `question` → 30 min. `0` disables a tool's deadline; negative or non-numeric values are ignored. For `bash`, the call's own `input.timeout` is honoured on top (`max(resolved, input.timeout)`). See [Per-tool proxy timeouts](#per-tool-proxy-timeouts). |
|
|
292
|
+
| `planModeQuestion` | boolean | `false` | Route `ExitPlanMode` approval through opencode's native `question` tool instead of a text "(yes/no)" prompt. Opt-in, and currently unreachable on the default headless transport, which is not offered an `ExitPlanMode` tool at all. See [Plan mode](#plan-mode). |
|
|
271
293
|
| `controlRequestBehavior` | `allow` \| `deny` | `allow` | Default response when `skipPermissions: false` and Claude sends a `can_use_tool` control request. |
|
|
272
294
|
| `controlRequestToolBehaviors` | `Record<string, "allow" \| "deny">` | – | Per-tool override for `can_use_tool`. Example: `{ "Bash": "deny", "Read": "allow" }`. |
|
|
273
295
|
| `controlRequestDenyMessage` | string | built-in message | Message returned to Claude on a deny. |
|
|
274
296
|
| `bridgeOpencodeMcp` | boolean | `true` | Auto-translate your opencode `mcp` block into Claude's `--mcp-config`. See [MCP bridge](#mcp-bridge). |
|
|
275
297
|
| `mcpConfig` | string \| string[] | – | Extra `--mcp-config` paths/JSON passed alongside the bridged config. |
|
|
276
298
|
| `strictMcpConfig` | boolean | `false` | Pass `--strict-mcp-config` so Claude loads **only** the configured servers and ignores `~/.claude/settings.json`. |
|
|
299
|
+
| `hotReloadMcp` | boolean | `true` | With MCP bridging on, compare the merged MCP config and runtime status at the start of each turn and respawn the `claude` process when they drifted, so a server you just enabled or disabled becomes visible without restarting opencode or opening a new chat. Eviction waits for pending proxy calls, never happening mid tool-call, and the session id is preserved for `--resume`. Set `false` to keep a cached subprocess until the chat is reset. It does not reload other provider options and does not watch the contents of files named in `mcpConfig`. |
|
|
300
|
+
| `proxyOpencodeMcpTools` | boolean | `true` | Route the MCP tools discovered from opencode through the in-process `opencode_proxy` server instead of bridging them straight into Claude's `--mcp-config`. With both layers pointed at the same server, direct bridging executes every call twice, once in Claude's own MCP child process and once in opencode; proxying keeps opencode as the single execution site while preserving its permission prompts and tool rows. Falls back to direct bridging when discovery is unavailable, so do not treat it as an exactly-once guarantee for write-capable tools. |
|
|
277
301
|
| `webSearch` | `"claude"` \| `"disabled"` \| `<tool>` | `"claude"` | Routing for Claude's built-in `WebSearch`. See [WebSearch routing](#websearch-routing). |
|
|
278
302
|
| `multiStepContinuation` | boolean | `true` | Append a system-prompt hint nudging Claude to chain tool calls within one turn instead of pausing between subtasks. Each opencode turn boundary requires the user to manually press "continue", so for multi-step tasks this reduces friction. Set `false` to disable. |
|
|
279
303
|
| `autoContinueIncompleteTurns` | boolean \| `"smart"` | `"smart"` | Smartly continue incomplete Claude CLI results inside the same opencode turn. Reduces manual "continue" presses when Claude ends after reasoning/tool activity without a useful final answer. Set `false` to disable. |
|
|
280
304
|
| `compactionModel` | string | `"claude-haiku-4-5"` | Model used when opencode invokes `/compact`. Override per-process via the `CLAUDE_CODE_COMPACTION_MODEL` env var (env wins over config). See [Compaction](#compaction). |
|
|
281
|
-
| `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from every spawned `claude` process so it authenticates with your logged-in subscription instead of pay-as-you-go API billing. The plugin warns once at startup whenever an API key is detected, regardless of this setting. See [Billing](#billing
|
|
282
|
-
| `idleProcessTimeoutMs` | number | – | Kill a retained headless Claude worker after this many idle milliseconds following a completed turn. The
|
|
283
|
-
| `bridgeOpencodeSkills` | boolean | `false` | Expose your opencode skills to Claude's native `Skill` tool. See [Skill bridge](#skill-bridge). Written by [@broskees](https://github.com/broskees). |
|
|
284
|
-
| `
|
|
305
|
+
| `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from every spawned `claude` process so it authenticates with your logged-in subscription instead of pay-as-you-go API billing. The plugin warns once at startup whenever an API key is detected, regardless of this setting. See [Billing](#billing). |
|
|
306
|
+
| `idleProcessTimeoutMs` | number | – | Kill a retained headless Claude worker after this many idle milliseconds following a completed turn. The timer starts when a turn finishes, a new turn cancels it, a worker that is mid-turn when it fires is left alone and re-timed, and the session id is preserved for `--resume`. Values above Node's maximum timer delay (`2147483647`) are ignored. Omit or set `0` to retain workers until LRU eviction (16 processes). Interactive transport is excluded. Contributed by [@bernardofortes](https://github.com/bernardofortes). |
|
|
307
|
+
| `bridgeOpencodeSkills` | boolean | `false` | Expose your opencode skills to Claude's native `Skill` tool. Off by default because every bridged skill is also in the system prompt opencode forwards, so a large set is paid for twice per turn; the bundled configuration skill is staged either way. See [Skill bridge](#skill-bridge). Written by [@broskees](https://github.com/broskees). |
|
|
308
|
+
| `logging` | object | all defaults | The plugin's own logger, four independent fields: `file` (boolean, default `false`), `dir` (string, default `~/.local/share/opencode-claude-code/`), `mode` (`"silent"` \| `"debug"`, default `"silent"`) and `level` (`"debug"` \| `"info"` \| `"notice"` \| `"warn"` \| `"error"`, default `"info"`). Goes under `provider.claude-code.options` like every other row here. See [Logging](#logging). |
|
|
309
|
+
| `turnStats` | boolean | `false` | Append a one-line cost / duration / cache footer to each finished turn. See [Per-turn stats](#per-turn-stats). |
|
|
310
|
+
| `interactive` | boolean | `false` | **Experimental.** Drive the interactive `claude` TUI (subscription billing) instead of headless `--print`. Requires opencode running under Bun with PTY support; silently falls back to headless otherwise. The tool proxy, `permissionMode` and `/btw` are all unavailable on it, so read [What it does not support](#what-it-does-not-support) before enabling. Env: `CLAUDE_CODE_INTERACTIVE_TRANSPORT=1`. |
|
|
285
311
|
| `interactiveBypass` | boolean | `false` | Deprecated/no-op with `interactive`: Claude Code's TUI shows a manual safety confirmation for `bypassPermissions`, so the plugin intentionally does not pass it. |
|
|
286
312
|
| `interactiveAllowTools` | string[] | `["Bash", "Edit", "Write", "Read", "WebFetch"]` | With `interactive`: built-in tools pre-allowed without prompting (replaces the default list). MCP server wildcards (`mcp__<server>__*`) are always added from the bridged config. |
|
|
287
313
|
| `interactiveSystemPrompt` | boolean | `true` | With `interactive`: append this plugin's CLI/AGENTS/continuation prompt via `--append-system-prompt-file`. The transport intentionally does not forward opencode's own system prompt, because it can trigger Claude Code's third-party-app usage gate on subscription accounts. Set `false` only for diagnostics. |
|
|
288
314
|
|
|
315
|
+
### Environment variables
|
|
316
|
+
|
|
317
|
+
Every variable the plugin itself reads, in one place. Config is read once at opencode startup, so these are the way to change behaviour for a single run without editing `opencode.json`. Claude Code's own variables (`CLAUDE_CODE_DISABLE_THINKING` and friends) are passed through untouched and are listed under [Extended thinking](#extended-thinking).
|
|
318
|
+
|
|
319
|
+
| Variable | Read by | Effect |
|
|
320
|
+
|---|---|---|
|
|
321
|
+
| `CLAUDE_CLI_PATH` | provider factory | Fallback `claude` path when `cliPath` is absent. Under opencode the config hook always supplies `cliPath`, so this only applies to direct `createClaudeCode()` use. |
|
|
322
|
+
| `CLAUDE_CODE_COMPACTION_MODEL` | compaction spawn | Model for `/compact`. Wins over the `compactionModel` option. See [Compaction](#compaction). |
|
|
323
|
+
| `CLAUDE_CODE_INTERACTIVE_TRANSPORT` | transport selection | `1` turns on the experimental [interactive transport](#interactive-transport-experimental) for one process, same as `interactive: true`. |
|
|
324
|
+
| `CLAUDE_CODE_INTERACTIVE_BYPASS` | transport selection | Requests `bypassPermissions` in interactive mode. Deliberately ignored, with a warning, for the reason in the `interactiveBypass` row above. |
|
|
325
|
+
| `CLAUDE_CODE_START_WATCHDOG_MS` | start watchdog | Milliseconds a `claude` process may stay completely silent on stdout after a turn is written, or after a proxy tool result should have resumed it, before the plugin acts. First expiry respawns the process and resumes the session; a second ends the turn with an error rather than hanging. Default `90000`; a positive integer is required and anything else falls back to that. Mainly a knob for reproducing the hang. |
|
|
326
|
+
| `OPENCODE_CLAUDE_CODE_LOG_FILE` | logger | `1` writes the log file, `0` forces it off even when `logging.file` is `true`. See [Logging](#logging). |
|
|
327
|
+
| `OPENCODE_CLAUDE_CODE_LOG_DIR` | logger | Directory for the log file, overriding `logging.dir`. |
|
|
328
|
+
| `OPENCODE_CLAUDE_CODE_LOG_LEVEL` | logger | Minimum level to emit, overriding `logging.level`. An unrecognised value falls through to config. |
|
|
329
|
+
| `DEBUG` | logger | `DEBUG=opencode-claude-code` promotes the logger to `mode: "debug"`, echoing every emitted level to opencode's TUI. |
|
|
330
|
+
| `OPENCODE_CLAUDE_CODE_PLUGIN_NO_CLEANUP` | startup cleanup | `1` skips the one-time removal of a stale **unscoped** `opencode-claude-code-plugin` install from opencode's plugin cache. That old package is a different artifact that shadows this scoped one when both are present; set this if you are deliberately keeping it. |
|
|
331
|
+
| `OPENCODE_WORKTREE` | MCP bridge | Overrides worktree-root detection, which otherwise walks up from the working directory looking for a `.git` entry. |
|
|
332
|
+
| `OPENCODE_CONFIG` / `OPENCODE_CONFIG_DIR` | config discovery | Where the plugin looks for your opencode config when bridging MCP and skills. See [Discovery order](#discovery-order-highest-to-lowest-priority). |
|
|
333
|
+
| `OPENCODE_VERSION` | startup diagnostics | Reported as the opencode version when set, sparing the plugin a `--version` spawn. Diagnostics only. |
|
|
334
|
+
| `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` | spawn environment | Not set by the plugin: these are yours, and Claude Code authenticates with them in preference to your subscription login when present. `ignoreAnthropicApiKey: true` strips them from the spawn. See [Billing](#billing). |
|
|
335
|
+
|
|
336
|
+
The plugin also honours the usual path conventions rather than defining its own: `XDG_CONFIG_HOME` and `XDG_CACHE_HOME` (falling back to `~/.config` and `~/.cache`), `HOME` / `USERPROFILE`, and Claude Code's `CLAUDE_CONFIG_DIR` when the interactive transport needs to find the session transcript. Account providers set `CLAUDE_CONFIG_DIR` themselves for the process they spawn.
|
|
337
|
+
|
|
289
338
|
### Overriding model metadata
|
|
290
339
|
|
|
291
340
|
To rename a model, change a limit, or add a custom one:
|
|
@@ -312,7 +361,7 @@ Anything you supply is merged on top of the defaults; you don't need to redeclar
|
|
|
312
361
|
|
|
313
362
|
## Interactive transport (experimental)
|
|
314
363
|
|
|
315
|
-
By default the plugin spawns `claude --print` (headless). From **June 15, 2026** that usage bills against the separate [Agent SDK credit](#billing
|
|
364
|
+
By default the plugin spawns `claude --print` (headless). From **June 15, 2026** that usage bills against the separate [Agent SDK credit](#billing) on subscription plans. The interactive transport instead drives the real interactive `claude` TUI — which bills as **normal plan usage** — under a native PTY inside opencode's Bun runtime, types your prompt into it, and streams the session transcript (`~/.claude/projects/<cwd>/<session-id>.jsonl`) back through the same pipeline the headless transport uses.
|
|
316
365
|
|
|
317
366
|
```json
|
|
318
367
|
"options": { "interactive": true }
|
|
@@ -329,16 +378,26 @@ Or per-process: `CLAUDE_CODE_INTERACTIVE_TRANSPORT=1`.
|
|
|
329
378
|
|
|
330
379
|
- The plugin's appended prompt (Claude CLI context, AGENTS.md guidance, continuation rules). The interactive transport intentionally does not forward opencode's own system prompt, because live testing showed that payload can trigger Claude Code's third-party-app usage gate on subscription accounts.
|
|
331
380
|
- The MCP bridge: bridged servers are passed via `--mcp-config` + `--strict-mcp-config`, and every bridged server is pre-allowed as `mcp__<server>__*`.
|
|
381
|
+
- The [skill bridge](#skill-bridge): the same `--plugin-dir` staging the headless spawn uses, so the TUI's native `Skill` tool can load your opencode skills too.
|
|
332
382
|
- Model selection, session reuse, and the whole streaming/usage pipeline.
|
|
333
383
|
|
|
334
384
|
Set `interactiveSystemPrompt: false` only for diagnostics. While disabled, the interactive session will not receive the plugin's CLI context, AGENTS.md guidance, or continuation hints.
|
|
335
385
|
|
|
336
|
-
### What
|
|
386
|
+
### What it does not support
|
|
387
|
+
|
|
388
|
+
This is the part to read before turning it on. Three whole features of this plugin are simply absent on the interactive transport:
|
|
389
|
+
|
|
390
|
+
- **No tool proxy.** The interactive spawn starts no proxy MCP server at all, so `mcp__opencode_proxy__bash`, `edit`, `write`, `webfetch`, `task`, `task_batch`, `question` and `compress` do not exist for that session. Claude uses its own built-in tools directly, which means opencode does not execute them, does not prompt for them, and does not log them. Everything in [Selective tool proxy](#selective-tool-proxy) applies to the headless transport only.
|
|
391
|
+
- **No `permissionMode`.** The interactive spawn never passes your `permissionMode` to the CLI, so `"plan"` and the rest have no effect there. Permission handling is the pre-allow list described below and nothing else.
|
|
392
|
+
- **No [`/btw`](#side-questions-with-btw).** Side questions ride Claude Code's `side_question` control protocol over the headless process's stdio. Asking one in an interactive session returns an error telling you so.
|
|
393
|
+
|
|
394
|
+
### What else is different
|
|
337
395
|
|
|
338
396
|
- **Permissions:** the interactive TUI has no `can_use_tool` control channel, so tools can't be approved per-call through opencode. Built-in tools are pre-allowed via a settings allow list (default `Bash, Edit, Write, Read, WebFetch`; override with `interactiveAllowTools`). `bypassPermissions` is intentionally not used here because Claude Code shows a manual safety confirmation in the TUI and defaults to exit.
|
|
339
397
|
- **Input is text-only:** images and other non-text blocks are dropped (with a logged warning); tool results are rendered as labeled text.
|
|
340
398
|
- **Output granularity:** text arrives per transcript record, not token-by-token, so it can feel chunkier than headless streaming.
|
|
341
399
|
- **Turn timeout:** a turn that produces no terminal stop within 30 minutes is reported honestly as an error result (visible truncation), not silently ended.
|
|
400
|
+
- **No idle eviction:** `idleProcessTimeoutMs` does not apply to interactive sessions.
|
|
342
401
|
- `/compact` always uses the headless transport regardless of this setting.
|
|
343
402
|
|
|
344
403
|
---
|
|
@@ -369,7 +428,7 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
|
|
|
369
428
|
- **Resume:** pass the child session ID back as `task_id` to continue that subagent session. Omit it to create a fresh child.
|
|
370
429
|
- **Nested tasks:** current opencode defaults `subagent_depth` to `1`, so a first-level child cannot launch another child. Increase top-level `subagent_depth` to permit deeper nesting, and explicitly grant `permission.task` on every subagent that should delegate; opencode otherwise adds a task deny to spawned subagent sessions.
|
|
371
430
|
- **Background:** `background: true` returns after starting the child and lets opencode notify the parent when it finishes. Current opencode requires `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` in the environment of the opencode process. Foreground is the default.
|
|
372
|
-
- **Several at once:** `mcp__opencode_proxy__task_batch` takes a `tasks` array of ordinary task inputs and runs them concurrently. It exists because Claude Code sends MCP requests one at a time: when the model emits two `task` calls in one response, the second only leaves the CLI after the first has returned (measured live, 2026-09-06), so "launch two subagents" was always serial. The plugin turns one `task_batch` call into N opencode `task` calls inside a single tool boundary, which opencode executes in parallel, then hands the model every result together, labelled in task order. Same permissions, same
|
|
431
|
+
- **Several at once:** `mcp__opencode_proxy__task_batch` takes a `tasks` array of ordinary task inputs and runs them concurrently. It exists because Claude Code sends MCP requests one at a time: when the model emits two `task` calls in one response, the second only leaves the CLI after the first has returned (measured live, 2026-09-06), so "launch two subagents" was always serial. The plugin turns one `task_batch` call into N opencode `task` calls inside a single tool boundary, which opencode executes in parallel, then hands the model every result together, labelled in task order. Same permissions, same no-deadline default, same `subagent_type` list. Enabled whenever `Task` is proxied. Designed and first implemented by [@broskees](https://github.com/broskees) on his fork.
|
|
373
432
|
|
|
374
433
|
**Steering models to it.** Headless Claude Code CLIs expose no `Agent`/`Task`
|
|
375
434
|
dispatch tool of their own (verified on 2.1.211), while they *do* expose
|
|
@@ -431,6 +490,8 @@ It is the one proxy tool opencode never sees. The call is answered inside the pl
|
|
|
431
490
|
|
|
432
491
|
Without it, the appended system prompt tells the model that `compress` is unavailable and to ignore instructions that ask for it, which is the right answer when nothing implements it.
|
|
433
492
|
|
|
493
|
+
The store, the interceptor and the two prompt variants are covered by tests, but the full "model calls compress, the next turn really is a fresh process carrying only the summary" round-trip has not been verified against a live CLI. Treat it as working-but-unproven and check the plugin log the first time you rely on it.
|
|
494
|
+
|
|
434
495
|
Only those seven values are actually proxied; anything else you put in `proxyTools` is ignored. Proxying `Edit` also disables `MultiEdit` — opencode has no batched-edit equivalent, so Claude is forced to fan out into single `Edit` calls that each flow through the permission UI. The `"Question"` proxy is version-gated on opencode's built-in `question` tool: on builds that lack the registry entry the def is silently dropped (a forwarded call would otherwise render as `⚙ invalid`), so add it only on opencode versions that ship the `question` tool.
|
|
435
496
|
|
|
436
497
|
Without `"Task"` in `proxyTools`, Claude's built-in `Agent` tool stays enabled and Claude orchestrates subagents internally with no opencode child-session visibility. To opt out of all proxying, including Task, use an explicit empty list:
|
|
@@ -483,7 +544,7 @@ sqlite3 ~/.local/share/opencode/opencode.db \
|
|
|
483
544
|
|
|
484
545
|
### What you get with proxying on
|
|
485
546
|
|
|
486
|
-
- opencode's **permission prompts** for every Bash/Edit/Write/WebFetch call
|
|
547
|
+
- opencode's **permission prompts** for every Bash/Edit/Write/WebFetch call. The default `--dangerously-skip-permissions` is still passed to `claude`, but it only governs Claude's own built-in tools; a proxied call is executed by opencode and answers to opencode's rules instead. Built-ins that are neither proxied nor listed in `extraDisallowedTools` do run under that flag.
|
|
487
548
|
- opencode's **audit log** captures the calls.
|
|
488
549
|
- Per-tool **policy rules** in opencode apply.
|
|
489
550
|
|
|
@@ -492,18 +553,35 @@ sqlite3 ~/.local/share/opencode/opencode.db \
|
|
|
492
553
|
- A small per-call latency hop through `127.0.0.1:<random>/mcp`.
|
|
493
554
|
- Batched-edit ergonomics: with `Edit` proxied, Claude can no longer use `MultiEdit`, so a refactor that would have been one tool call becomes N single `Edit` calls.
|
|
494
555
|
|
|
556
|
+
### How a proxied call ends
|
|
557
|
+
|
|
558
|
+
A proxied call ends when something happens to it, not when a clock runs out. The plugin holds the CLI's request open and listens to the process, the stream and the protocol for the events that actually decide the call's fate; each one releases the call on the spot, and tells the CLI where there is still a CLI to tell:
|
|
559
|
+
|
|
560
|
+
| What happens | What the plugin does |
|
|
561
|
+
|---|---|
|
|
562
|
+
| opencode returns the tool's result | resolves the call; the CLI gets the result and carries on |
|
|
563
|
+
| you abort the turn (Esc / Ctrl+C) | sends the CLI an `interrupt`, which answers with its own result, and rejects every call the turn had pending, whether the abort lands before content, mid-turn, or while opencode is running the tool between two stream boundaries |
|
|
564
|
+
| you send the next message in that chat | rejects every call the previous turn left pending as orphaned, so the CLI gets an error result and the new turn starts clean |
|
|
565
|
+
| the `claude` process closes its output or exits, mid-turn or between turns | rejects its pending calls; a mid-turn death also ends the turn as a visible error |
|
|
566
|
+
| you delete the chat in opencode, or opencode exits | kills the worker and rejects its pending calls |
|
|
567
|
+
| the CLI hangs up on its own request | keeps the call so a late result can still be delivered as a plain-text continuation (see below) |
|
|
568
|
+
|
|
569
|
+
Because every ending is observed rather than inferred from elapsed time, a `task` can run until it is finished: **`task` and `task_batch` have no deadline by default**. Earlier flat ceilings fired mid-subagent, Claude believed its dispatch had failed, and the eventual result was dropped because the parent turn had already ended on the timeout error; a 60-minute one did the same to anything longer. What the default gives up is only that nothing fires on the clock alone, so a chat parked in a `task` holds its `claude` worker until one of the events above happens. That is the operator's decision to make, so no timer makes it for them.
|
|
570
|
+
|
|
571
|
+
The same events are also what let a legitimately long call complete, which is the second half of the story: the CLI's own HTTP client used to give up on a silent reply at about five minutes whatever the tool deadline said. Every held call therefore keeps its connection visibly alive. A client that advertises SSE gets immediate headers and a keepalive comment every 15 seconds (since 0.15.0); a client that only accepts JSON gets its headers immediately as well, as a chunked body carrying keepalive whitespace on the same cadence, which is still one valid JSON-RPC response when the result lands, on success and on error. Keepalives are about the connection, not the tool: they never extend or replace a deadline. Claude's MCP client timeout for the proxy server, written into the generated `--mcp-config`, is set to the largest effective deadline, and to the largest value the CLI accepts (Node's timer maximum, about 24.8 days) while any tool has no deadline, because the CLI rejects a `timeout` of `0` outright.
|
|
572
|
+
|
|
495
573
|
### Per-tool proxy timeouts
|
|
496
574
|
|
|
497
|
-
|
|
575
|
+
Deadlines still exist, as an explicit backstop rather than the mechanism that decides when a call is over. If a tool with one has not been resolved within that many milliseconds, the call is rejected and Claude receives a timeout error. Resolved per tool, most-specific layer winning:
|
|
498
576
|
|
|
499
577
|
1. flat default — 10 min (matches Claude CLI's own Bash ceiling)
|
|
500
|
-
2. per-tool default
|
|
501
|
-
3. your `proxyToolTimeoutMs` override (case-insensitive key)
|
|
502
|
-
4. for `bash` only, the call's own `input.timeout
|
|
578
|
+
2. per-tool default: **`task` / `task_batch`: none**, **`question`: 30 min**, everything else: 10 min
|
|
579
|
+
3. your `proxyToolTimeoutMs` override (case-insensitive key; a positive value replaces the default, `0` removes the deadline, anything else is ignored)
|
|
580
|
+
4. for `bash` only, the call's own `input.timeout`: the proxy never undercuts a build the caller explicitly asked to run long (`max(resolved, input.timeout)`), and a positive `input.timeout` restores a deadline that `bash: 0` removed
|
|
503
581
|
|
|
504
|
-
|
|
582
|
+
`question` keeps 30 minutes because it blocks on a human reading a form, and a form nobody answers is not an event. A positive `task` override restores a wall-clock backstop for operators who want one; if it fires, the error tells Claude not to "schedule a wake-up": that is a Claude Code affordance which cannot fire in this headless/proxy context, so deferring silently loses the work.
|
|
505
583
|
|
|
506
|
-
|
|
584
|
+
Two watchdogs are a different thing again and are unchanged: the start watchdog (90 s of complete silence after a turn is written, respawn then error, see `CLAUDE_CODE_START_WATCHDOG_MS`) and the wire-inactivity watchdog (60 s of silence after content). Those exist because a process that is alive but wedged emits no event to listen to, and a proxy call is never what they are waiting on: a CLI parked inside a proxied tool is producing nothing on purpose, and both watchdogs know that.
|
|
507
585
|
|
|
508
586
|
If Claude nevertheless abandons the HTTP call, the plugin preserves narration emitted while opencode was running the tool, renders it on return, and delivers the late completion as a plain-text continuation naming the original call. It tells Claude not to run the tool again. A silent post-tool continuation gets one resumed-process retry, preserving the original model, account, effort, and proxy configuration; a second failure ends with an error rather than an indefinite hang. Buffered narration is capped at 500 lines and 2 MiB, with a warning if output was dropped.
|
|
509
587
|
|
|
@@ -553,6 +631,50 @@ Notes:
|
|
|
553
631
|
|
|
554
632
|
Fully restart opencode after upgrading to load the command and runtime changes. Other providers do not gain Claude's native side-question behavior from this command.
|
|
555
633
|
|
|
634
|
+
## Plugin health with /claude-code-doctor
|
|
635
|
+
|
|
636
|
+
```text
|
|
637
|
+
/claude-code-doctor
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Prints, in the chat, what the plugin currently thinks is happening. The plugin answers it itself: no model is called, nothing is billed, and the reply reports 0 tokens. It is the thing to paste into a bug report.
|
|
641
|
+
|
|
642
|
+
It carries the startup-diagnostics fields (plugin version, opencode version, `claude` path and version, the working directory and which resolution tier picked it, providers, accounts, `proxyTools`, the on-disk MCP servers, transport, whether an `ANTHROPIC_API_KEY` is present) plus the live runtime state the startup block cannot know:
|
|
643
|
+
|
|
644
|
+
- every live `claude` child, by opencode session id and model, with its pid, whether a turn is in flight, how long it has been up, and the effort it was spawned at,
|
|
645
|
+
- every pending proxy call, with the tool, the call id, how long it has waited, and its deadline,
|
|
646
|
+
- each proxy server's URL with one unauthenticated `initialize` posted to it: `401, good` is the patched behaviour, and anything else is flagged unsafe with the fix (restart every opencode window, since a window opened before 0.13.2 keeps serving an open port). See [Proxy endpoint security](#proxy-endpoint-security).
|
|
647
|
+
|
|
648
|
+
Nothing secret goes in it: not the proxy bearer token, not the value of `ANTHROPIC_API_KEY`, not the system prompt, not a pending call's arguments. A `claude-code-doctor` command you defined yourself is never overwritten. The name has no space in it because opencode reads everything after the first space as the command's arguments. The whole exchange is kept out of any transcript replayed to the CLI, like a `/btw` pair.
|
|
649
|
+
|
|
650
|
+
## Per-turn stats
|
|
651
|
+
|
|
652
|
+
Off by default. With `turnStats: true`:
|
|
653
|
+
|
|
654
|
+
```text
|
|
655
|
+
▌ **stats:** $0.0123 · 4.2 s · 2 CLI turns · in 1.2k · out 812 · cache read 45.1k · cache write 2.0k
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
One line at the end of a finished turn, from the numbers the CLI already reports on its `result`. Notes:
|
|
659
|
+
|
|
660
|
+
- Never on a `/compact` turn (the footer would be appended to what opencode stores as the summary) and never on a turn that ended in error, where the error is the thing to read.
|
|
661
|
+
- It is its own text part led by `▌ **stats:**`, and the plugin strips it again if the conversation is ever replayed into a fresh Claude Code process. The model never reads its own accounting.
|
|
662
|
+
- Token counts are the turn's totals, which is what matches the cost. They are deliberately not the same numbers opencode's context gauge shows, which use the last tool-use iteration so the window is not inflated.
|
|
663
|
+
- The cost is what the CLI reported for the turn, not a billing guarantee.
|
|
664
|
+
|
|
665
|
+
The same numbers are logged at INFO whatever this option is set to, and `total_cost_usd`, `duration_ms`, `duration_api_ms`, `num_turns`, `usage`, `modelUsage` and `permission_denials` always reach `providerMetadata` (denials by tool name and id only, never their inputs).
|
|
666
|
+
|
|
667
|
+
## Things the CLI says that are no longer silent
|
|
668
|
+
|
|
669
|
+
Four Claude Code stream events used to reach nothing but a debug log:
|
|
670
|
+
|
|
671
|
+
- **A rate-limit rejection.** When the CLI reports `status: "rejected"` (or a rejected extra-usage state), the turn now carries a `▌ **rate limit:**` line naming the window, the reason extra usage is unavailable, when it resets, and the four things that can be done about it. Warned once per identity per process. See [Billing](#billing-change-june-15-2026-agent-sdk-credit).
|
|
672
|
+
- **A context compaction Claude Code did on its own.** A `▌ **context compacted:**` note says so, with the before and after token counts, so an answer that suddenly forgets the start of the conversation has a visible cause.
|
|
673
|
+
- **A `result` whose subtype is not `success`** (`error_max_turns`, `error_during_execution`, …). The subtype is named in the transcript and the turn finishes as an error instead of an ordinary reply.
|
|
674
|
+
- **A CLI-executed tool that failed.** Its result is forwarded with the AI SDK's error flag, so opencode renders the row as failed rather than as a success whose output happens to be an error message.
|
|
675
|
+
|
|
676
|
+
At session start the plugin also warns once per process for each MCP server Claude Code could not connect (its tools are simply absent otherwise) and once when the CLI's own `apiKeySource` says an API key is in effect, which is the field that tells you pay-as-you-go billing is happening. See [`ignoreAnthropicApiKey`](#options-reference).
|
|
677
|
+
|
|
556
678
|
## Configuration skill
|
|
557
679
|
|
|
558
680
|
The package includes a `claude-code-plugin` skill so your agent can configure it without asking you to navigate all its options. Ask, for example:
|
|
@@ -563,7 +685,7 @@ Use the claude-code-plugin skill to configure a work account and idle worker cle
|
|
|
563
685
|
|
|
564
686
|
It covers accounts, models and agent effort, proxy tools, permissions, MCP/skill bridging, timeouts, logging, upgrades and troubleshooting. It directs the agent to preserve JSONC comments, change only requested settings, validate the result, protect credentials and ask before paid probes or broader permissions.
|
|
565
687
|
|
|
566
|
-
The plugin registers the bundled directory with opencode's `skills.paths`, making it available to other providers too on supporting opencode versions. For
|
|
688
|
+
The plugin registers the bundled directory with opencode's `skills.paths`, making it available to other providers too on supporting opencode versions. For Claude turns it also loads through Claude's native Skill tool as `opencode-skills:claude-code-plugin`, even when `bridgeOpencodeSkills` is `false`. This requires CLI `--plugin-dir` support and applies to the headless, interactive and direct `doGenerate` spawns; compaction never loads the native bridge.
|
|
567
689
|
|
|
568
690
|
No separate skill installation or copying is needed. It ships with each package version, so upgrading updates the reference. Fully restart opencode to load it. `test-configure-skill.ts` checks coverage of provider/logging options, model ids, proxy tools and environment variables; maintainers must update behavior and default guidance in the same change as the implementation.
|
|
569
691
|
|
|
@@ -571,7 +693,7 @@ No separate skill installation or copying is needed. It ships with each package
|
|
|
571
693
|
|
|
572
694
|
opencode and Claude Code use the same on-disk skill format, a `<name>/SKILL.md` whose frontmatter carries `name` and `description`, but they read from different directories. opencode looks in `.opencode/skills/` and `~/.config/opencode/skills/`; the Claude CLI looks in `~/.claude/skills/` and its own plugins. So opencode advertises your skills in the system prompt it forwards, the model calls `Skill("browser-automation")`, and Claude answers `Unknown skill`.
|
|
573
695
|
|
|
574
|
-
|
|
696
|
+
By default the plugin discovers your opencode skills, stages a throwaway Claude Code plugin directory that links them, and passes it as `claude --plugin-dir`. They register natively, prefixed with the plugin name:
|
|
575
697
|
|
|
576
698
|
```text
|
|
577
699
|
opencode-skills:browser-automation
|
|
@@ -582,7 +704,7 @@ Claude can invoke them with the Skill tool or as `/opencode-skills:<name>`. `--p
|
|
|
582
704
|
|
|
583
705
|
Discovery order, first match wins: `.opencode/skills/` walking up from the working directory, then `~/.opencode/skills/`, then `$OPENCODE_CONFIG_DIR/skills/`, then `~/.config/opencode/skills/`. A project skill shadows a global one of the same name. If the skill set is unchanged the staged directory is reused between spawns.
|
|
584
706
|
|
|
585
|
-
|
|
707
|
+
The bridge is **off by default**: every bridged skill's name and description is also in the system prompt opencode already forwards, so a large skill set is paid for twice on every turn. Set `bridgeOpencodeSkills: true` when the model tries `Skill("<name>")` for a skill opencode advertises and gets `Unknown skill`; the bundled configuration skill is staged either way. When on, the bridge applies to the headless, interactive and direct `doGenerate` spawns alike, never to compaction, and it is skipped on a Claude CLI without `--plugin-dir` (the plugin probes `claude --help` and logs a notice).
|
|
586
708
|
|
|
587
709
|
This bridge was written by [@broskees](https://github.com/broskees) (Joseph Roberts) on his fork and absorbed here with credit; see [Credits](#credits).
|
|
588
710
|
|
|
@@ -650,9 +772,12 @@ Each chat keeps a long-lived `claude` subprocess so the model retains its native
|
|
|
650
772
|
- **Same chat, multiple turns** → process reused, full Claude context retained.
|
|
651
773
|
- **New chat** → fresh process under the new session key.
|
|
652
774
|
- **Resumed chat after restart** → in-memory state is gone; a new process spawns and the conversation history is summarized and prepended.
|
|
653
|
-
- **Abort (Esc / Ctrl+C)** → the plugin sends the Claude CLI a stream-json `interrupt` control request, so the CLI actually stops generating and running tools instead of finishing the abandoned turn on your bill. The process stays alive for the next message in that chat. If a turn is somehow still running when the next one starts, it is interrupted first (5 s cap). Contributed by [@broskees](https://github.com/broskees).
|
|
654
|
-
- **Idle timeout** → when `idleProcessTimeoutMs` is
|
|
655
|
-
- **Cap**: 16 active processes, LRU eviction.
|
|
775
|
+
- **Abort (Esc / Ctrl+C)** → the plugin sends the Claude CLI a stream-json `interrupt` control request, so the CLI actually stops generating and running tools instead of finishing the abandoned turn on your bill. The process stays alive for the next message in that chat, and any proxied call the aborted turn left behind is released when that message arrives (see [How a proxied call ends](#how-a-proxied-call-ends)). If a turn is somehow still running when the next one starts, it is interrupted first (5 s cap). Contributed by [@broskees](https://github.com/broskees).
|
|
776
|
+
- **Idle timeout** → when `idleProcessTimeoutMs` is set, a completed headless turn arms an eviction timer (unset or `0` keeps workers until LRU eviction). Reuse cancels it, a worker found mid-turn when it fires is left alone and re-timed, and eviction preserves the session id, so the next message resumes the same conversation with `--resume`. An idle `claude --print` holds around 250 MB, which is the reason to set it if you keep many chats open.
|
|
777
|
+
- **Cap**: 16 active processes, LRU eviction. A process that is mid-turn is never the victim: eviction takes the oldest **idle** one, and when every process is busy it evicts nothing and warns instead, so a running answer is never truncated to make room.
|
|
778
|
+
- **Deleted chat** → deleting a session in opencode kills its `claude` workers at once and forgets their session ids and per-chat state; there is nothing left to resume. Other chats, and the shared fallback bucket used when no session id is known, are untouched.
|
|
779
|
+
- **opencode exits** → every retained worker is killed on the way out, so a hard shutdown does not leave `claude` processes reparented to init.
|
|
780
|
+
- **Crash** → if the CLI dies mid-turn (no terminal `result` line), the turn ends with a visible error naming the exit code or signal and the last stderr the CLI wrote, not a silent `stop` that reads as a short but finished answer. An abort you asked for is not reported this way.
|
|
656
781
|
|
|
657
782
|
---
|
|
658
783
|
|
|
@@ -769,7 +894,7 @@ What you see is a **summary** of the model's thinking, not the raw chain-of-thou
|
|
|
769
894
|
|
|
770
895
|
### Reasoning effort
|
|
771
896
|
|
|
772
|
-
Each model exposes `low` / `medium` / `high` / `xhigh` / `max
|
|
897
|
+
Each model exposes five picker variants, `low` / `medium` / `high` / `xhigh` / `max`. An agent's own `reasoningEffort` frontmatter accepts six values: those five plus `minimal`, which maps to the CLI's `low`. The plugin hands the level to the CLI as `CLAUDE_CODE_EFFORT_LEVEL` at spawn, which Claude Code treats as the session-wide override: it beats the `effortLevel` in that account's `settings.json` and a shell export of the same variable. Effort is fixed for the life of a `claude` process, so it is part of the session key. Changing effort retires the previous effort's process and remembered transcript ID before replaying the conversation into a fresh process. Switching back cannot resume stale context; same-effort streaming turns still reuse their process. This reset is scoped to the same directory, model, provider/account, agent, and conversation. If the previous effort still has pending work (including tool results, plan approval, recovery, or `/btw`), the switch is rejected: finish that work at its original effort first. Title, compaction, and `/btw` calls do not trigger effort resets.
|
|
773
898
|
|
|
774
899
|
Earlier versions injected a thinking keyword such as `(ultrathink)` into the user message instead. Claude Code stopped recognising every keyword except `ultrathink`, so that path is gone and nothing is appended to your messages any more. Compaction skips request and agent effort overrides, but still inherits a shell-level `CLAUDE_CODE_EFFORT_LEVEL` when set.
|
|
775
900
|
|
|
@@ -815,14 +940,24 @@ to file only and lets WARN/ERROR bubble in the TUI (they always do).
|
|
|
815
940
|
`mode: "debug"` additionally echoes every emitted level to the TUI (which
|
|
816
941
|
opencode surfaces as warning bubbles).
|
|
817
942
|
|
|
943
|
+
`logging` is an ordinary provider option, so it goes under `provider.claude-code.options` like every other one. Keying it on the package name instead is the common mistake: opencode accepts that config without complaint and the plugin never reads it, so you get no log and no error.
|
|
944
|
+
|
|
818
945
|
**Recommended dev setup** — capture audit trail to disk, keep TUI quiet:
|
|
819
946
|
|
|
820
947
|
```jsonc
|
|
821
|
-
|
|
822
|
-
"
|
|
948
|
+
{
|
|
949
|
+
"provider": {
|
|
950
|
+
"claude-code": {
|
|
951
|
+
"options": {
|
|
952
|
+
"logging": { "file": true }
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
823
956
|
}
|
|
824
957
|
```
|
|
825
958
|
|
|
959
|
+
The snippets below abbreviate to the `logging` value alone; each one belongs at that same path.
|
|
960
|
+
|
|
826
961
|
**Full firehose for deep debugging** (every DEBUG stream event captured):
|
|
827
962
|
|
|
828
963
|
```jsonc
|
|
@@ -881,9 +1016,11 @@ grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
|
|
|
881
1016
|
Reading it:
|
|
882
1017
|
|
|
883
1018
|
- **`cwd.source`** is which rule picked the working directory Claude will be
|
|
884
|
-
spawned in
|
|
1019
|
+
spawned in: `configured` (you pinned `options.cwd`), `process` (normal),
|
|
885
1020
|
`captured` (`process.cwd()` was unusable and opencode's project directory
|
|
886
1021
|
rescued it, the macOS GUI-launch case), or `unresolved` (neither worked).
|
|
1022
|
+
The per-session tier that `opencode serve` uses is resolved per call and so
|
|
1023
|
+
cannot appear here; this line mirrors the synchronous order only.
|
|
887
1024
|
- **`claudeCli.version`** reading `not detected` means the `claude` binary at
|
|
888
1025
|
that path didn't answer `--version`, which also disables version-gated
|
|
889
1026
|
flags like `--thinking-display`.
|
|
@@ -893,6 +1030,10 @@ Reading it:
|
|
|
893
1030
|
opencode still does not hand its version to plugins. It reads `unknown` when
|
|
894
1031
|
opencode is run from source rather than as the packaged binary.
|
|
895
1032
|
|
|
1033
|
+
This block is logged once, to a file that is off by default. For the same
|
|
1034
|
+
fields plus live process and proxy state, without enabling logging, run
|
|
1035
|
+
[`/claude-code-doctor`](#plugin-health-with-claude-code-doctor) in the session.
|
|
1036
|
+
|
|
896
1037
|
### Default behavior (no config, no env)
|
|
897
1038
|
|
|
898
1039
|
Nothing persists; only WARN and ERROR bubble in the TUI. The plugin
|
|
@@ -904,7 +1045,7 @@ plugin internals.
|
|
|
904
1045
|
|
|
905
1046
|
### [opencode-dcp](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning) (Dynamic Context Pruning)
|
|
906
1047
|
|
|
907
|
-
Partial support since v0.5.1. DCP runs in a useful degraded mode: automatic strategies and slash commands work,
|
|
1048
|
+
Partial support since v0.5.1. DCP runs in a useful degraded mode: its automatic strategies and slash commands work, while its own model-facing tools do not reach the model. Model-driven compression is still available, through this plugin's opt-in [`compress` proxy](#context-compression) rather than DCP's tool.
|
|
908
1049
|
|
|
909
1050
|
| DCP feature | Status | Notes |
|
|
910
1051
|
|---|---|---|
|
|
@@ -912,18 +1053,19 @@ Partial support since v0.5.1. DCP runs in a useful degraded mode: automatic stra
|
|
|
912
1053
|
| `experimental.chat.system.transform` (context-limit nudges, iteration reminders) | ✅ Works in headless | Headless spawns forward system-role content via `--append-system-prompt-file`. Interactive mode intentionally omits opencode's forwarded system prompt and keeps only this plugin's CLI/AGENTS/continuation prompt. |
|
|
913
1054
|
| `/dcp compress`, `/dcp sweep`, `/dcp manual`, `/dcp context`, `/dcp stats` slash commands | ✅ Works | Handled by opencode's `command.execute.before` hook, not the model. |
|
|
914
1055
|
| Automatic `deduplication` + `purgeErrors` strategies | ✅ Works | Message-transform only, no model tool calls. |
|
|
915
|
-
|
|
|
1056
|
+
| DCP's own autonomous `compress` / `distill` / `prune` tool calls | ❌ Not supported | DCP registers those as opencode-native tools. Claude CLI only ever sees its own built-ins and MCP-bridged servers, so the model never sees them. |
|
|
1057
|
+
| Model-driven compression through this plugin's `compress` proxy | ⚠️ Opt-in | Add `"Compress"` to `proxyTools` and the plugin exposes `mcp__opencode_proxy__compress`, which gives the model a working way to compress its own context. It is not DCP's tool and does not use DCP's strategies. See [Context compression](#context-compression). |
|
|
916
1058
|
|
|
917
|
-
|
|
1059
|
+
So autonomous compression is available, just not DCP's implementation of it. Two routes: add `"Compress"` to `proxyTools` so the model can compress its own context through this plugin, or leave it off and trigger DCP manually with `/dcp compress` whenever you would have wanted the model to call it. With `"Compress"` absent, the plugin's appended system prompt tells Claude that no such tool exists and to ignore instructions asking for it, which is the correct answer in that case.
|
|
918
1060
|
|
|
919
1061
|
---
|
|
920
1062
|
|
|
921
1063
|
## Known limitations
|
|
922
1064
|
|
|
923
|
-
-
|
|
1065
|
+
- Tool inputs stream as they are constructed (Anthropic's `input_json_delta` is forwarded as `tool-input-delta`), but only for tool calls opencode actually sees. Calls the plugin deliberately does not forward, meaning proxy tools, CLI-internal `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, the todo-ledger `Task*` family and Claude's other internal tools, have their deltas suppressed, because a delta for a tool opencode never saw start renders as a permanently pending `⚙ unknown` row.
|
|
924
1066
|
- Raw chain-of-thought is not available. Claude 4 family models ship summarized thinking only. See [Extended thinking](#extended-thinking) for the full picture.
|
|
925
1067
|
- Recommended Claude Code CLI: **2.1.142+**. Older CLIs work for everything else but skip the `--thinking-display` flag, so Claude Opus 4.7 turns may render empty Thinking rows. If something breaks after a Claude Code update, the CLI version is the first thing to check.
|
|
926
|
-
- **Foreground Task calls have
|
|
1068
|
+
- **Foreground Task calls have no proxy deadline by default.** The plugin listens for the events that end a call instead of timing it (see [How a proxied call ends](#how-a-proxied-call-ends)), so a subagent runs to completion and a chat parked in one holds its `claude` worker until you abort, send another message, delete the chat, or the process goes away. Add a wall-clock backstop via [`proxyToolTimeoutMs`](#per-tool-proxy-timeouts) if you want one. For independent work that should not block the turn at all, use `background: true` after enabling opencode's experimental background-subagent flag.
|
|
927
1069
|
- **Subagent todos require explicit permission.** See [Subagent todos](#subagent-todos) for the rule and a working config.
|
|
928
1070
|
|
|
929
1071
|
---
|
|
@@ -960,7 +1102,7 @@ src/
|
|
|
960
1102
|
opencode-types.ts # mirrored opencode types
|
|
961
1103
|
```
|
|
962
1104
|
|
|
963
|
-
For runtime gotchas, the
|
|
1105
|
+
For runtime gotchas, the release flow, and the compatibility audit (last taken against **opencode 1.18.29**), see [`AGENTS.md`](./AGENTS.md).
|
|
964
1106
|
|
|
965
1107
|
## Publishing (maintainers)
|
|
966
1108
|
|
|
@@ -969,7 +1111,7 @@ npm version patch # or minor/major — bumps package.json + creates the tag
|
|
|
969
1111
|
git push origin master --follow-tags
|
|
970
1112
|
```
|
|
971
1113
|
|
|
972
|
-
The GitHub Actions workflow at `.github/workflows/publish.yml` runs `npm publish --access public` on tag push (
|
|
1114
|
+
The GitHub Actions workflow at `.github/workflows/publish.yml` runs `npm publish --access public` on tag push. Since v0.6.2 it authenticates with **npm trusted publishing (OIDC)**, not a token: the job holds `id-token: write`, upgrades npm first because OIDC needs npm 11.5.1 or newer, and passes no `NODE_AUTH_TOKEN`. The trusted publisher is configured on npmjs.com against this repository and the `publish.yml` workflow filename, so a publish that fails on auth means that configuration, not an expired secret. There is no `NPM_TOKEN` in the workflow.
|
|
973
1115
|
|
|
974
1116
|
## Star History
|
|
975
1117
|
|
|
@@ -990,13 +1132,13 @@ This plugin absorbs work from its forks directly, cherry-picked with the origina
|
|
|
990
1132
|
| [@galvani](https://github.com/galvani) (Jan Kozak) | Per-session working directory for `opencode serve`, so one server spawns each project's `claude` in the right place. Also found the stale `toolCallMap` re-emission three months before it was fixed here. | `9e02ce4`, `2238ed0` |
|
|
991
1133
|
| [@HeikoAtGitHub](https://github.com/HeikoAtGitHub) | Stopped sending `AGENTS.md` to the model twice (opencode already forwards it). Independently diagnosed the 5-minute proxy wall. | `25260a4`, `42f426d` |
|
|
992
1134
|
| [@bernardofortes](https://github.com/bernardofortes) (Bernardo Fortes) | `idleProcessTimeoutMs`, idle eviction of retained `claude` workers. | `a5f723a` |
|
|
993
|
-
| [@broskees](https://github.com/broskees) (Joseph Roberts) | Task proxy default-on (PR #18), the abort `interrupt` so Esc really stops the CLI, the skill bridge, `task_batch` for concurrent subagents (and the measurement that the CLI serialises MCP calls),
|
|
1135
|
+
| [@broskees](https://github.com/broskees) (Joseph Roberts) | Task proxy default-on (PR #18), the abort `interrupt` so Esc really stops the CLI, the skill bridge, `task_batch` for concurrent subagents (and the measurement that the CLI serialises MCP calls), the undici 300 s diagnosis of the proxy wall, and the lifecycle release of proxied calls that made the `task` deadline unnecessary (PR #36). | PR #18, `68ed142`, PR #36 |
|
|
994
1136
|
| [@jknlsn](https://github.com/jknlsn) (Jake Nelson) | Per-tool proxy timeouts, subagent dispatch steering, the question proxy, the start watchdog respawn. | `84f3db9`, `94980a6`, `47501d0`, `ffefc24` |
|
|
995
1137
|
| [@CollieIsCute](https://github.com/CollieIsCute) (Collie Tsai) | The plan-mode approval bridge. | `8c5b583` |
|
|
996
1138
|
| [@flupkede](https://github.com/flupkede) | The compress proxy tool design and the AI-SDK v4 image-part fix. | `4ac319f`, `60a6e9a` |
|
|
997
1139
|
| [@CNQQC](https://github.com/CNQQC) | Cost units corrected to dollars per million tokens (PR #25). | PR #25 |
|
|
998
1140
|
| [@willmcginnis](https://github.com/willmcginnis) | The proxy endpoint authentication (PR #28, GHSA-3mxm-w7gf-3c5x). | PR #28 |
|
|
999
|
-
| [@nic-lan](https://github.com/nic-lan) | The issue #29 diagnosis of subagent output lost across the CLI resume boundary. | #29 |
|
|
1141
|
+
| [@nic-lan](https://github.com/nic-lan) | The issue #29 diagnosis of subagent output lost across the CLI resume boundary, and the fix for unattended output replaying as one text block per delta (PR #35). | #29, PR #35 |
|
|
1000
1142
|
| [@JWebCoder](https://github.com/JWebCoder) (joao moura) | Diagnosed that auto-continue never fires on current CLIs (PR #15). | PR #15 |
|
|
1001
1143
|
|
|
1002
1144
|
Commit hashes are on the contributors' forks where the work was cherry-picked; `git log --author` on this repo shows the preserved authorship.
|