@khalilgharbaoui/opencode-claude-code-plugin 0.33.1 → 0.33.2
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 +2 -2
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +140 -82
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Three ways to reach Claude from opencode. They differ in who authenticates, who
|
|
|
21
21
|
| **Authentication** | An Anthropic Platform API key, held in opencode's own auth store. | Whatever the official `claude` CLI already holds: a subscription login, an API key, Bedrock, or Vertex. The plugin never reads, stores, or replays a token of its own, and there is no subscription token here to lift. | The Claude OAuth session, used outside the official client. Meridian runs a local proxy that maps Anthropic-style HTTP onto the Claude Agent SDK and your Claude session; `opencode-claude-auth` reads the OAuth tokens out of the macOS Keychain or `~/.claude/.credentials.json` and refreshes them against Anthropic's OAuth endpoint itself. |
|
|
22
22
|
| **What is billed, and to whom** | Pay as you go on the Platform account that owns the key. | Whatever the CLI's own authentication bills. Headless `--print` is the Agent SDK path; an API key found anywhere the CLI looks switches the same turn onto Console pay-as-you-go instead. `apiKeySource` on the CLI's `system` init event is the field that says which, and the plugin warns once per process when a key is in effect. On a subscription, headless and interactive turns both draw from the plan's ordinary usage limits. See [which login bills what](#which-login-bills-what). | The subscription the reused session belongs to. Meridian's own FAQ: "Usage limits follow your Max subscription, not Anthropic API billing tiers." |
|
|
23
23
|
| **Terms-of-service status** | The ordinary API route. Nothing unusual about it. | Sanctioned: the official client does the authenticating, and driving `claude` is what `claude` is for. | Disallowed. Anthropic disallowed reusing subscription authentication for third-party Claude use in February 2026, and each project says so in its own words: Meridian's wrapper "makes no claims regarding compliance with Anthropic's Terms of Service"; `opencode-claude-auth` calls itself "a community workaround" and notes that the terms say subscription tokens "should only be used with official Anthropic clients"; `opencode-claude-plan` quotes Consumer Terms 3.7 and asks you to accept that your account "could be suspended or terminated". |
|
|
24
|
-
| **Model list and fast mode** | Whatever opencode's own provider registers. |
|
|
24
|
+
| **Model list and fast mode** | Whatever opencode's own provider registers. | 18 ids auto-registered, Haiku 4.5 through Opus 5.5 plus Fable and Mythos, each carrying a `(N×)` list-price suffix, and any other id `claude --model` accepts passes straight through. Three `-fast` Opus ids are this plugin's own markers and opt a headless session into fast mode through `--settings` (CLI 2.1.220+). See [Models](#models). | `opencode-claude-auth`'s README lists 14 model ids. Meridian's lists none, because model metadata comes from opencode's own `anthropic` provider. Neither README mentions fast mode. |
|
|
25
25
|
| **Which tools run where, under whose permissions** | All of them are opencode's, behind opencode's permission prompts and audit log. | Your choice, per tool. `Bash`, `Edit`, `Write`, `WebFetch` and `Task` are proxied by default: Claude calls an in-process MCP tool and **opencode** executes it, under its own permissions and audit log. Anything neither proxied nor named in `extraDisallowedTools` runs inside Claude Code under `--dangerously-skip-permissions`. See [Selective tool proxy](#selective-tool-proxy) and [Read-only mode](#read-only-mode). | All of them are opencode's, because the model call is an ordinary provider call. This is the one row where the third column matches the native provider and this plugin has to work for the same result. |
|
|
26
26
|
| **Reasoning and effort** | opencode's own reasoning controls. | Five picker variants per model, `low` through `max`, handed to the CLI as `CLAUDE_CODE_EFFORT_LEVEL` at spawn. Effort is fixed for the life of a `claude` process, so it is part of the session key, and an agent's own `reasoningEffort` beats the effort a call arrived with. Thinking is Anthropic's summarized digest, not raw chain-of-thought. See [Extended thinking](#extended-thinking). | Meridian's SDK-features file exposes a `thinking` key. Neither README documents per-model effort variants. |
|
|
27
27
|
| **Context window** | Whatever the model exposes. | The registered limits: 200k context / 64k output on the 4.5 generation, 1M / 128k on 4.6 and later, all at standard pricing with no above-200K tier. Claude Code may also compact or clear its own context mid-conversation, which the plugin can announce but not prevent. | Not stated in either README. |
|
|
@@ -102,7 +102,7 @@ The same package runs on opencode 1.x and 2.x, and nothing changes for 1.x. The
|
|
|
102
102
|
}
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
Your existing `provider.claude-code.options` block keeps working, because opencode 2 still reads 1.x config files.
|
|
105
|
+
Your existing `provider.claude-code.options` block keeps working, because opencode 2 still reads 1.x config files. opencode 2's own spelling is `providers.claude-code.settings` (note the plural `providers`). All four places the plugin reads its settings from, lowest precedence first: `provider.claude-code.options`, `provider.claude-code.settings`, `providers.claude-code.settings`, and the plugin entry's own `options`, which wins over all of them. Plugin-level settings such as `accounts` usually go in that last one: `{"package": "@khalilgharbaoui/opencode-claude-code-plugin", "options": {"accounts": ["work"]}}`.
|
|
106
106
|
|
|
107
107
|
For a local checkout, point opencode 2 at the **`dist` directory**, not the repository root. It loads `<dir>/server` or `<dir>/index` from a directory and never reads `package.json#main`:
|
|
108
108
|
|
package/package.json
CHANGED
|
@@ -79,7 +79,8 @@ the relevant module. Comments and README can lag the implementation.
|
|
|
79
79
|
tool permissions, or enable experimental flags as a routine verification step.
|
|
80
80
|
Explain consequences first, including `Question`, `planModeQuestion`, `Compress`,
|
|
81
81
|
`interactive`, skill/MCP bridging and fast models. Ask in ordinary text if a decision
|
|
82
|
-
is needed
|
|
82
|
+
is needed. Do not enable the `Question` proxy in order to ask one: it is opt-in
|
|
83
|
+
precisely because it disables Claude's own `AskUserQuestion`.
|
|
83
84
|
|
|
84
85
|
## Procedure
|
|
85
86
|
|
|
@@ -100,13 +101,15 @@ the relevant module. Comments and README can lag the implementation.
|
|
|
100
101
|
## Options reference
|
|
101
102
|
|
|
102
103
|
Use `provider.claude-code.options` unless intentionally overriding an expanded account.
|
|
104
|
+
That key is read by both opencode majors; opencode 2's own spelling is
|
|
105
|
+
`providers.claude-code.settings`, and the full precedence is in the opencode 2 recipe.
|
|
103
106
|
Defaults below describe normal headless opencode use when the key is absent.
|
|
104
107
|
|
|
105
108
|
| Option | Type | Default | What it does |
|
|
106
109
|
|---|---|---|---|
|
|
107
110
|
| `cliPath` | string | `"claude"` | Executable, not a shell command with flags. Use an absolute path for a non-PATH install. The opencode config hook supplies this default; only direct `createClaudeCode()` use falls back to `CLAUDE_CLI_PATH`. Account providers wrap it; never select a generated wrapper yourself. |
|
|
108
111
|
| `accounts` | string[] | unset | Unset keeps provider `claude-code`. Any array, including `[]`, expands to `claude-code-default` plus normalized, deduplicated names. Non-default accounts use `~/.claude-<name>`; default uses the CLI's normal environment/auth. |
|
|
109
|
-
| `accountFailover` | `"ask"` / `"off"` | `"ask"` | When the account a conversation runs on is out of usage, end the turn on opencode's native `question` form listing the other configured accounts, and continue the task on the pick inside the same opencode turn. Only ever fires with more than one account configured, so a single-account install is unaffected by the default. The pick is sticky for the LIMITED account until the limit's reset time (or until opencode restarts when the CLI reported none), so it covers every session on that account and subagents follow their parent; child sessions are never shown the form. Leaving it unanswered waits and costs nothing. `stop`, a dismissal, or text that is not one of the offered accounts ends the turn as the rate-limit error does. Triggered only by a rejected `rate_limit_event
|
|
112
|
+
| `accountFailover` | `"ask"` / `"off"` | `"ask"` | When the account a conversation runs on is out of usage, end the turn on opencode's native `question` form listing the other configured accounts, and continue the task on the pick inside the same opencode turn. Only ever fires with more than one account configured, so a single-account install is unaffected by the default. The pick is sticky for the LIMITED account until the limit's reset time (or until opencode restarts when the CLI reported none), so it covers every session on that account and subagents follow their parent; child sessions are never shown the form. Leaving it unanswered waits and costs nothing. `stop`, a dismissal, or text that is not one of the offered accounts ends the turn as the rate-limit error does. Triggered only by a rejected `rate_limit_event`, one of the two known account-limit error texts, or one of the five account-level failure kinds the CLI names on its own error reply (`authentication_failed`, `oauth_org_not_allowed`, `account_on_hold`, `verification_required`, `billing_error`); never by a generic failure. Never on compaction turns or the interactive transport. A switch cannot resume the Claude session (transcripts live under the account's own config dir), so the conversation is replayed into a fresh one: it costs input tokens on the new account, and MCP servers configured only in the limited account's Claude profile are gone. `"off"` keeps the plain rate-limit error. |
|
|
110
113
|
| `failoverAccounts` | string[] | unset/derived | Account expansion supplies the resolved account list so a limited account can offer the others. Do not hand-wire it; set `accounts` instead. |
|
|
111
114
|
| `baseCliPath` | string | unset/derived | The `cliPath` before the per-account wrapper substitution, so a failover can build another account's wrapper on the same binary. Supplied by the config hook. Do not hand-wire it. |
|
|
112
115
|
| `defaultSubagentModel` | string | unset | Seed-config default for discovered `mode: subagent` agents without a full `provider/model` pin; `forceModel` takes precedence. Keeps the caller's account. Unknown ids warn and keep the inherited model. Not independently read per expanded account. |
|
|
@@ -115,7 +118,7 @@ Defaults below describe normal headless opencode use when the key is absent.
|
|
|
115
118
|
| `cwd` | string | automatic | Pin an absolute existing directory. Otherwise: session directory from SDK, usable `process.cwd()`, captured project directory, final `process.cwd()` fallback. Startup diagnostics cannot show the per-call session tier. |
|
|
116
119
|
| `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to headless Claude, even with proxies enabled. Proxied calls still use opencode permissions, but unproxied CLI tools do not. `false` removes the bypass flag; it does not by itself create human approval prompts. Ignored when `permissionMode` is `"plan"`, which always drops the flag. |
|
|
117
120
|
| `permissionMode` | `acceptEdits` / `auto` / `bypassPermissions` / `default` / `dontAsk` / `plan` | unset | Headless `--permission-mode`, not version-gated: verify the installed CLI supports the value. `plan` is enforced: it overrides `skipPermissions: true` and the plugin drops `--dangerously-skip-permissions` for it, so claude cannot edit or run commands. Every other value governs prompting and still passes the skip flag, so `plan` is the only one that makes a run read-only. Nothing releases plan mode mid-session (no headless `ExitPlanMode`), so leaving it means a config change and an opencode restart; the plugin warns once at startup. Not forwarded by the current interactive spawn path. |
|
|
118
|
-
| `permissionPreset` | `"read-only"` | unset | One named posture instead of hand-combining the
|
|
121
|
+
| `permissionPreset` | `"read-only"` | unset | One named posture instead of hand-combining the options around it. Unset changes nothing. An applied preset replaces `permissionMode`, `skipPermissions`, `controlRequestBehavior` and `controlRequestToolBehaviors` outright, filters `proxyTools`, and unions its own names into `extraDisallowedTools`. `read-only` forces `skipPermissions: false` (the CLI exits with `bypassPermissions not supported in restricted mode` if both are passed), replaces any `permissionMode` with `--restricted` (CLI 2.1.258+: no Bash, REPL or other code runners, no WebFetch, file tools confined to the working directories, bypass refused), adds `--permission-prompts none` (CLI 2.1.263+), disallows `Bash`, `Write`, `Edit`, `NotebookEdit`, `REPL`, `JavaScript` and `WebFetch` via `--disallowedTools`, drops `bash`/`write`/`edit`/`webfetch`/`task`/`task_batch` from `proxyTools`, forces `controlRequestBehavior: "deny"` and ignores `controlRequestToolBehaviors` entirely. Every override is logged at NOTICE. An unknown preset name applies nothing and WARNs rather than guessing. On a CLI below either flag gate the preset still holds through `--disallowedTools` plus the plugin's own deny, with a WARN naming what is lost. Reads (`Read`, `Grep`, `Glob`, `WebSearch`) still work; anything else that would prompt, including bridged MCP tools and the `question` proxy, is denied. |
|
|
119
122
|
| `controlRequestBehavior` | `allow` / `deny` | `allow` | Automatically answer CLI `can_use_tool` requests if emitted. Forced to `deny` by `permissionPreset: "read-only"`. Not an opencode permission prompt or a sandbox; bypass/pre-allowed tools may never ask. `AskUserQuestion` defaults to deny. |
|
|
120
123
|
| `controlRequestToolBehaviors` | object of tool name to `allow`/`deny` | unset | Case-insensitive per-tool override of the above (`Bash`, `Read`, `mcp__github__list_prs`). Do not allow `AskUserQuestion`: that can let headless Claude self-answer. |
|
|
121
124
|
| `controlRequestDenyMessage` | string | built-in text | Override ordinary deny text. `AskUserQuestion` always uses its own stop-and-wait message. |
|
|
@@ -128,7 +131,7 @@ Defaults below describe normal headless opencode use when the key is absent.
|
|
|
128
131
|
| `mcpConfig` | string or string[] | unset | Extra `--mcp-config` paths or inline JSON passed alongside the bridged config. |
|
|
129
132
|
| `strictMcpConfig` | boolean | `false` | Headless `--strict-mcp-config`: use only explicitly supplied MCP configs, ignoring other MCP sources, not all settings/credentials/hooks. The interactive wrapper adds it whenever it passes MCP paths, independently of this option. |
|
|
130
133
|
| `hotReloadMcp` | boolean | `true` | With bridging on, compare merged MCP config/status at turn start and respawn on drift after pending proxy calls resolve. Keeps the session via headless `--resume`. Does not reload arbitrary provider options or watch explicit `mcpConfig` contents. |
|
|
131
|
-
| `proxyOpencodeMcpTools` | boolean | `false` | Route opencode's MCP-backed tools through opencode's executor instead of Claude's own `--mcp-config` child, so each call is permission-prompted and rendered as an opencode tool row. Default changed `true` to `false` here, with no behaviour change: at `true` it routed nothing, because discovery read opencode's tool registry, which never contains MCP tools. Discovery now reads the model tool set opencode passes the provider, verified live on opencode 1.18.31 / Claude Code 2.1.263. **Tell the user to set `strictMcpConfig: true` alongside it**: a server also present in Claude Code's own config is reached directly and the proxy is bypassed, which looks exactly like the option doing nothing. A routed call runs with the calling agent's permissions. Servers whose tools are not found stay on the direct bridge and log a warning. Do not promise exactly-once side effects across failures, retries or opencode versions; verify routing before using write-capable tools. |
|
|
134
|
+
| `proxyOpencodeMcpTools` | boolean | `false` | Route opencode's MCP-backed tools through opencode's executor instead of Claude's own `--mcp-config` child, so each call is permission-prompted and rendered as an opencode tool row. Default changed `true` to `false` here, with no behaviour change: at `true` it routed nothing, because discovery read opencode's tool registry, which never contains MCP tools. Discovery now reads the model tool set opencode passes the provider, verified live on opencode 1.18.31 / Claude Code 2.1.263. **Tell the user to set `strictMcpConfig: true` alongside it**: a server also present in Claude Code's own config is reached directly and the proxy is bypassed, which looks exactly like the option doing nothing. A routed call runs with the calling agent's permissions. Servers whose tools are not found stay on the direct bridge and log a warning. Inert with `bridgeOpencodeMcp: false`, which leaves no bridged server list to match names against. Do not promise exactly-once side effects across failures, retries or opencode versions; verify routing before using write-capable tools. |
|
|
132
135
|
| `proxyOpencodeTools` | string[] | `[]` | Forward explicitly named opencode tools (case-insensitive): V1 resolves registry ids; V2 resolves the current model tool snapshot and its actual JSON Schema, including synthesized Code Mode `execute`, without re-exposing tools absent from that snapshot. Covers plugin-declared tools such as DCP's `compress` and V2 Code Mode. Same broker as other proxies; collisions and unknown names warn. Explicit allowlist only, because calls run in opencode with the agent's permissions. `execute` grants access to the session's whole Code Mode catalog, not just MCP, and is refused by the read-only preset. |
|
|
133
136
|
| `stripContextReminders` | boolean | `false` | Strip opencode-dcp `<dcp-system-reminder>` blocks from user/assistant message text, including the fresh-session rebuild. Only when no `compress` is proxied via `proxyTools` or `proxyOpencodeTools`; reachable compress makes it inert. Resolved from config, so a configured-but-unregistered name still counts as reachable. Leaves opencode's own `<system-reminder>` blocks alone. |
|
|
134
137
|
| `multiStepContinuation` | boolean | `true` | Append a system-prompt hint to chain tool calls in one turn instead of stopping between subtasks. |
|
|
@@ -136,7 +139,7 @@ Defaults below describe normal headless opencode use when the key is absent.
|
|
|
136
139
|
| `compactionModel` | string | `"claude-haiku-4-5"` | `/compact` uses a fresh short-lived headless process without the usual bridge/proxy/skill wiring. Nonblank `CLAUDE_CODE_COMPACTION_MODEL` wins. This is inference and can be billed. |
|
|
137
140
|
| `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` from headless/interactive spawn env, allowing stored auth to be used. Does not log in, change the parent env, or guarantee subscription billing if other CLI/cloud auth is configured. Warns at startup when either nonempty variable is present, regardless of the flag. |
|
|
138
141
|
| `idleProcessTimeoutMs` | number | unset | Kill a conversation's idle `claude` worker this many ms after a finished turn. The timer starts when a turn completes, reuse cancels it, and a worker found mid-turn when it fires is re-timed rather than killed. The session id is kept, so the next message resumes transparently. Unset or `0` keeps workers until LRU eviction (16 processes, oldest idle first). Values above `2147483647` are ignored. Not applied to the interactive transport. Deleting a chat in opencode releases its workers and session ids immediately regardless. |
|
|
139
|
-
| `turnStats` | boolean | `false` | Append one `▌ **stats:**` line to each finished turn: cost, wall duration, CLI turn count,
|
|
142
|
+
| `turnStats` | boolean | `false` | Append one `▌ **stats:**` line to each finished turn: cost, wall duration, CLI turn count, input/output/cache-read/cache-write tokens, and a permission-denial count when the turn had any, taken from the CLI's own `result`. Never on a compaction turn or a turn that ended in error. Its own text part, stripped from transcripts rebuilt for the CLI, so the model never sees it. The same numbers are logged at INFO regardless, and `modelUsage` plus `permission_denials` always reach `providerMetadata`. Reported cost is the CLI's figure, not a billing guarantee. |
|
|
140
143
|
| `bridgeOpencodeSkills` | boolean | `false` | Stage the user's opencode skills for Claude's native Skill tool as `opencode-skills:<name>`, on the headless and interactive spawns (never compaction). Covers every root opencode reads: project `.opencode/`, `.claude/`, `.agents/` walking up, the opencode config dirs (`skill/` and `skills/`), and global `~/.claude/skills` and `~/.agents/skills` under opencode's own `OPENCODE_DISABLE_EXTERNAL_SKILLS` / `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` switches. Requires the CLI's `--help` to advertise `--plugin-dir`; otherwise no-op. Bridged skills are also listed in opencode's forwarded system prompt, so a large skill set costs prompt tokens twice, which is why it is off by default; `true` opts the user's skills in. Bundled skill staging ignores this option, but still requires flag support and successful discovery/staging. |
|
|
141
144
|
| `bridgeSkipNativeSkills` | boolean | `true` | Leave a skill unbridged when the Claude session already loads it: from `<CLAUDE_CONFIG_DIR>/skills`, the project's `.claude/skills`, or an installed plugin's `skills/`. Matched by resolved directory, by byte-identical SKILL.md, or (user/project scope only, since plugin skills are namespaced `<plugin>:<name>`) by name. A name match means `Skill("<name>")` answers from Claude's copy, not opencode's, so it is logged at WARN with both paths. The plugin scan reads `installed_plugins.json` and does not check whether the plugin is enabled. `false` bridges everything and reinstates the duplicates. |
|
|
142
145
|
| `interactive` | boolean | unset (headless) | Experimental PTY transport; explicit boolean wins over `CLAUDE_CODE_INTERACTIVE_TRANSPORT`. Needs `Bun.Terminal`; otherwise headless fallback. Compaction stays headless. Does not wire the headless proxy server or disallowed-tools controls; no equivalent opencode permission guarantee or `/btw`. The skill bridge does apply. Never enable to bypass a billing/access restriction. |
|
|
@@ -171,6 +174,7 @@ their secret values. Arbitrary MCP `{env:NAME}` placeholders are outside this li
|
|
|
171
174
|
| `CLAUDE_CLI_PATH` | Direct factory fallback for absent `cliPath`. Normal opencode registration supplies `"claude"`; set the option explicitly there. |
|
|
172
175
|
| `CLAUDE_CONFIG_DIR` | CLI auth/settings/session directory. Non-default account wrappers override it; default headless account inherits it if set. Login is a user-approved interactive action, never a diagnostic probe. |
|
|
173
176
|
| `CLAUDE_CODE_EFFORT_LEVEL` | Shell-level CLI effort. Request variant/agent effort wins on a normal spawn. Compaction omits request/agent effort, but still inherits the shell env. |
|
|
177
|
+
| `CLAUDE_CODE_PROMPT_CACHE_TTL` | Shell-level CLI prompt cache TTL for the main conversation. An agent's `cacheTtl` (or `defaultSubagentCacheTtl`) wins on that agent's spawn; with neither set the plugin writes nothing and the shell value, or the CLI's own default, stands. |
|
|
174
178
|
| `CLAUDE_CODE_DISABLE_THINKING` | CLI-owned, conventionally `1` to disable thinking. Plugin leaves it intact and suppresses its own thinking flags/summary defaults if enabled. |
|
|
175
179
|
| `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING` | CLI-owned adaptive-thinking control. Either disable variable suppresses the plugin's own thinking flags/summary defaults, not just adaptive flags. Empty/`0`/`false`/`no`/`off` are false, case-insensitive. |
|
|
176
180
|
| `CLAUDE_CODE_SHOW_THINKING_SUMMARIES` | Headless spawn fills in `1` only if unset and neither disable flag is enabled. Any explicit value is preserved and suppresses the plugin's `--thinking-display` override; `0` requests suppression from the CLI. |
|
|
@@ -193,22 +197,33 @@ their secret values. Arbitrary MCP `{env:NAME}` placeholders are outside this li
|
|
|
193
197
|
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | Set to `1` on every spawned `claude` under the same never-overwrite rule. Suppresses non-essential CLI network traffic and independently blocks auto-update. An empty string counts as user-set and is left alone; the CLI reads it as off. |
|
|
194
198
|
| `OPENCODE_CONFIG` | Explicit config file, also read by the disk MCP bridge before project layers. |
|
|
195
199
|
| `OPENCODE_CONFIG_DIR` | Additional `.opencode`-style config/skill root. The plugin's direct agent-file fallback does not use it; agents must reach the config hook or a supported agent directory. |
|
|
196
|
-
| `OPENCODE_WORKTREE` | Overrides the disk MCP bridge's project walk-up boundary. |
|
|
200
|
+
| `OPENCODE_WORKTREE` | Overrides the disk MCP bridge's project walk-up boundary. opencode 1.x layering only: the opencode 2 layering walks to the filesystem root and applies no worktree boundary at all. |
|
|
197
201
|
| `XDG_CONFIG_HOME` | Global MCP/skill/AGENTS discovery root (`<value>/opencode`); defaults to the home `.config`. Direct agent-file fallback still uses `~/.config/opencode/agent(s)`. |
|
|
198
202
|
| `XDG_CACHE_HOME` | Account wrapper/cache-cleanup root override; do not assume the default cache path when upgrading. |
|
|
199
203
|
| `HOME` | Home expansion and direct agent-file discovery (other paths also use OS homedir). Do not change it to switch accounts. |
|
|
200
204
|
| `USERPROFILE` | Home fallback where `HOME` is absent. |
|
|
201
205
|
| `OPENCODE_VERSION` | Startup diagnostics version fallback, not a capability override. |
|
|
206
|
+
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | opencode's own flag, read by opencode and never by this plugin. On opencode 1.x it is what makes opencode advertise `background` on its `task` tool, and that advertised schema is the only thing the plugin reads. `OPENCODE_EXPERIMENTAL` turns it on as a blanket. It must be in the environment that launches opencode. Unconditional on opencode 2. |
|
|
207
|
+
| `OPENCODE_DISABLE_EXTERNAL_SKILLS` | opencode's own switch, honoured by the skill bridge: any value other than empty / `0` / `false` drops both `~/.claude/skills` and `~/.agents/skills` from discovery. |
|
|
208
|
+
| `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` | The same switch for `~/.claude/skills` alone; `~/.agents/skills` is unaffected by it. |
|
|
202
209
|
|
|
203
210
|
## Recipes
|
|
204
211
|
|
|
212
|
+
**Which major each recipe is for.** Every options fragment below is written in the
|
|
213
|
+
opencode 1.x spelling, `provider.claude-code.options`, which opencode 2 also reads. On a
|
|
214
|
+
config that only ever serves opencode 2, put the same fragment under
|
|
215
|
+
`providers.claude-code.settings` instead. Fragments belong inside that options object,
|
|
216
|
+
never at the config root. The opencode 2 recipe has the full precedence list.
|
|
217
|
+
|
|
205
218
|
### Minimum install
|
|
206
219
|
|
|
207
220
|
```json
|
|
208
221
|
{ "plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"] }
|
|
209
222
|
```
|
|
210
223
|
|
|
211
|
-
Everything else is optional. Models appear in the picker without extra config.
|
|
224
|
+
Everything else is optional. Models appear in the picker without extra config. The
|
|
225
|
+
`plugin` key is read by both opencode majors, so this block needs no edit after an
|
|
226
|
+
opencode 2 upgrade.
|
|
212
227
|
|
|
213
228
|
### opencode 2
|
|
214
229
|
|
|
@@ -218,9 +233,9 @@ Same package, same config. 2.x's native key is `plugins` (plural), but it still
|
|
|
218
233
|
{ "plugins": ["@khalilgharbaoui/opencode-claude-code-plugin"] }
|
|
219
234
|
```
|
|
220
235
|
|
|
221
|
-
- Check the major first with `opencode --version`. `plugin`
|
|
222
|
-
- `provider.claude-code.options` still
|
|
223
|
-
- A local checkout is loaded by pointing `plugins` at its **`dist`** directory, never the repository root.
|
|
236
|
+
- Check the major first with `opencode --version`. `plugin` is read by both majors (recorded in `docs/agents-history.md` #g39); `plugins` is read by 2.x only.
|
|
237
|
+
- Provider settings, lowest precedence first: `provider.claude-code.options` (1.x's spelling, still read on 2.x), `provider.claude-code.settings`, `providers.claude-code.settings` (2.x's own), and the plugin entry's own `options`, which wins over all three. Any option of this plugin can sit in any of them; the plugin entry is the usual home for `accounts`: `{"package": "@khalilgharbaoui/opencode-claude-code-plugin", "options": {"accounts": ["work"]}}`.
|
|
238
|
+
- A local checkout is loaded by pointing `plugins` at its **`dist`** directory, never the repository root: 2.x resolves a configured plugin path as `<dir>/server` or `<dir>/index`.
|
|
224
239
|
- Known 2.x differences: `/btw` is answered after the running turn rather than inside it, and there is no todo panel (2.x has no `todowrite` tool). Do not set `hostApi`; the 2.x entrypoint sets it, and forcing it on 1.x breaks every proxied tool call.
|
|
225
240
|
|
|
226
241
|
#### V2 MCP and Code Mode
|
|
@@ -408,8 +423,7 @@ Never on compaction turns, title stubs, or the interactive transport.
|
|
|
408
423
|
{ "proxyTools": ["Bash", "Edit", "Write", "WebFetch", "Task"], "extraDisallowedTools": ["NotebookEdit"] }
|
|
409
424
|
```
|
|
410
425
|
|
|
411
|
-
|
|
412
|
-
the config root. Preserve other wanted proxies when changing this replacement list.
|
|
426
|
+
Preserve other wanted proxies when changing this replacement list.
|
|
413
427
|
`Read`, `Glob` and `Grep` have tool mappings/disallowed-name entries but no selectable
|
|
414
428
|
proxy definitions in this version, just like `NotebookEdit` has no proxy. Adding them
|
|
415
429
|
to `proxyTools` warns and leaves the built-ins unproxied. Use `extraDisallowedTools`
|
|
@@ -440,7 +454,9 @@ That one line is the whole posture. Do not also set `skipPermissions`,
|
|
|
440
454
|
`permissionMode`, `controlRequestBehavior` or `controlRequestToolBehaviors`
|
|
441
455
|
alongside it: the preset replaces all four and logs each value it dropped.
|
|
442
456
|
`proxyTools` is filtered rather than replaced, so a list naming `Question`
|
|
443
|
-
keeps it while `Bash`, `Edit`, `Write`, `WebFetch` and `Task` go
|
|
457
|
+
keeps it while `Bash`, `Edit`, `Write`, `WebFetch` and `Task` go, and
|
|
458
|
+
`extraDisallowedTools` is added to rather than replaced, so names already
|
|
459
|
+
listed there survive.
|
|
444
460
|
|
|
445
461
|
Read-only is enforced at three layers because no single one covers the plugin:
|
|
446
462
|
`--restricted` removes the CLI's own command and code-running tools, the
|
|
@@ -487,12 +503,53 @@ Names below become `mcp__opencode_proxy__<name>`; input config is case-insensiti
|
|
|
487
503
|
| `write` | `"Write"`, default; replaces CLI Write. |
|
|
488
504
|
| `webfetch` | `"WebFetch"`, default; replaces CLI WebFetch. |
|
|
489
505
|
| `task` | `"Task"`, default; disables CLI Agent and dispatches opencode subagents under its permissions. No proxy deadline by default; a positive `proxyToolTimeoutMs` entry adds one. Takes `background: true` only on a host that runs background subagents (see below). |
|
|
490
|
-
| `task_batch` | Included with Task; one MCP call fans out two or more independent task inputs concurrently. Separate task calls were measured serial
|
|
506
|
+
| `task_batch` | Included with Task; one MCP call fans out two or more independent task inputs concurrently. Separate task calls were measured serial (2026-09-06, two 8-second calls: the second MCP request left the CLI 7 ms after the first resolved). The input must be a `tasks` array of at least two items, each with `description`, `prompt` and `subagent_type`; anything else is refused before the calls are queued. |
|
|
491
507
|
| `task_status` | Included with Task, and only on a host that runs background subagents. Reads a background subagent's state by `task_id` and collects its result. A recovery path for a completion notification that never arrived, not a progress poll; a result is handed over once. Answered in-process (opencode has no such tool) and refuses any session that is not this conversation's subagent. Not nameable in `proxyTools`. |
|
|
492
508
|
| `task_cancel` | Included with Task, same host gate as `task_status`. Aborts a background subagent's child session; a cancelled subagent sends no completion notification. |
|
|
493
509
|
| `question` | `"Question"`, opt-in; replaces AskUserQuestion only if the live opencode registry has question. Round-trip verified on plugin 0.18.0 / CLI 2.1.258 / opencode 1.18.29, headless and as a real TUI form, with no `permission` block; grant `permission.question` only if a subagent's form is refused. Opt-in because it disables Claude's own AskUserQuestion. |
|
|
494
510
|
| `compress` | `"Compress"`, opt-in; in-process summary/reset interceptor, no opencode permission prompt and no built-in replacement. Discards prior CLI detail on a later eligible turn, retaining the summary, not the full transcript. Keep off unless explicitly requested. Reset round-trip verified live on CLI 2.1.263 / opencode 1.18.31. Not the same tool as a forwarded opencode `compress` (see `proxyOpencodeTools`): this one resets the Claude session, that one compresses opencode's transcript. Enabling both leaves this one holding the name. |
|
|
495
511
|
|
|
512
|
+
### How a proxied call ends
|
|
513
|
+
|
|
514
|
+
A proxied call is held open until an event ends it, and the plugin listens to the
|
|
515
|
+
`claude` process, the stream and the control protocol for those events rather than
|
|
516
|
+
inferring failure from elapsed time. opencode's result resolves the call. An abort
|
|
517
|
+
interrupts the CLI and rejects the turn's pending calls, unless opencode still reports
|
|
518
|
+
the session busy (opencode 1.18 aborts the signal of every tool step while it runs the
|
|
519
|
+
tool, so busy means the call is being served, not refused). The next user message
|
|
520
|
+
rejects what the previous turn left pending and tells the CLI. The process exiting, the
|
|
521
|
+
chat being deleted, or opencode exiting rejects the rest. That is why `task` and
|
|
522
|
+
`task_batch` carry no default deadline and a subagent runs to completion.
|
|
523
|
+
|
|
524
|
+
Three timers remain and are distinct from that: the optional per-tool deadlines
|
|
525
|
+
(`proxyToolTimeoutMs`, a backstop the user chooses), the start and inactivity
|
|
526
|
+
watchdogs (for a process that is alive but silent, which emits nothing to listen to; a
|
|
527
|
+
CLI parked in a proxied call is exempt), and the connection keepalives (SSE comments or
|
|
528
|
+
JSON whitespace every 15 s, so the CLI's HTTP client does not give up on a long call;
|
|
529
|
+
they never extend a deadline).
|
|
530
|
+
|
|
531
|
+
A deadline that passes while opencode still reports the session busy (a permission
|
|
532
|
+
prompt the user has not answered, or the tool still running) does not end the call: it
|
|
533
|
+
logs `proxy call past its deadline, but opencode is still serving it; waiting` at WARN
|
|
534
|
+
once and rechecks every minute. So an unanswered permission prompt is not a reason to
|
|
535
|
+
raise `proxyToolTimeoutMs`, and a raised deadline is never the fix for a long subagent,
|
|
536
|
+
because the default already waits for it.
|
|
537
|
+
|
|
538
|
+
Two log lines report a call that is simply taking a while, and neither is a failure or
|
|
539
|
+
ends a call:
|
|
540
|
+
|
|
541
|
+
- `proxy call still waiting, no deadline`, WARN, after five minutes and every five
|
|
542
|
+
minutes after, with tool, call id and elapsed time. Only a call whose resolved
|
|
543
|
+
deadline is `0` reaches it, which by default means `task` and `task_batch`, and also
|
|
544
|
+
any tool the user set to `0` in `proxyToolTimeoutMs`.
|
|
545
|
+
- `proxy call still waiting, deadline approaching`, WARN, once, at 60% of that call's
|
|
546
|
+
deadline, carrying `remainingMs` and naming `proxyToolTimeoutMs`. Deadlines under a
|
|
547
|
+
minute are not announced, because there the notice and the rejection would arrive
|
|
548
|
+
together.
|
|
549
|
+
|
|
550
|
+
Use them, or `/claude-code-doctor`, to tell a working subagent from a wedged one
|
|
551
|
+
before suggesting any timeout change.
|
|
552
|
+
|
|
496
553
|
### Background subagents (fire-and-collect)
|
|
497
554
|
|
|
498
555
|
Off unless the **opencode process** has `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`
|
|
@@ -524,7 +581,10 @@ verified live on 2.0.16.
|
|
|
524
581
|
Without it, opencode rejects a `background: true` call outright
|
|
525
582
|
(`Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`), losing
|
|
526
583
|
the dispatch, so the plugin strips `background` from the `task` and `task_batch` schemas
|
|
527
|
-
on such a host and registers neither extra tool.
|
|
584
|
+
on such a host and registers neither extra tool. The plugin never reads that variable
|
|
585
|
+
itself: it reads whether opencode's own advertised `task` schema carries a `background`
|
|
586
|
+
property, which is how a 1.x host publishes the flag, and on opencode 2 it does not ask
|
|
587
|
+
at all. Which way it went is in `plugin.log`:
|
|
528
588
|
|
|
529
589
|
```
|
|
530
590
|
background subagent gate {"supported":false,"registryResolved":true,"hostApi":"v1","note":"`background` stripped ..."}
|
|
@@ -543,35 +603,6 @@ answer, or opencode 2 offering it unconditionally), and the background tasks thi
|
|
|
543
603
|
process has collected or cancelled. The gate is read while a turn plans its proxy tools,
|
|
544
604
|
so a fresh process reports `Not read yet this process` until one message has been sent.
|
|
545
605
|
|
|
546
|
-
A proxied call is held open until an event ends it, and the plugin listens to the
|
|
547
|
-
`claude` process, the stream and the control protocol for those events rather than
|
|
548
|
-
inferring failure from elapsed time: opencode's result resolves the call; an abort
|
|
549
|
-
interrupts the CLI and rejects the turn's pending calls, unless opencode still reports
|
|
550
|
-
the session busy (opencode 1.18 aborts the signal of every tool step while it runs the
|
|
551
|
-
tool, so busy means the call is being served, not refused); the next user message rejects what the previous turn left pending
|
|
552
|
-
and tells the CLI; the process exiting, the chat being deleted, or opencode exiting
|
|
553
|
-
rejects the rest. That is why `task` and `task_batch` carry no default deadline and a
|
|
554
|
-
subagent runs to completion. Three timers remain and are distinct from that: the
|
|
555
|
-
optional per-tool deadlines above (a backstop the user chooses), the start and
|
|
556
|
-
inactivity watchdogs (for a process that is alive but silent, which emits nothing to
|
|
557
|
-
listen to; a CLI parked in a proxied call is exempt), and the connection keepalives
|
|
558
|
-
(SSE comments or JSON whitespace every 15 s, so the CLI's HTTP client does not give up
|
|
559
|
-
on a long call; they never extend a deadline). A deadline that passes while opencode
|
|
560
|
-
still reports the session busy (a permission prompt the user has not answered, or the
|
|
561
|
-
tool still running) does not end the call: it logs `proxy call past its deadline, but
|
|
562
|
-
opencode is still serving it; waiting` at WARN once and is rechecked every minute. So an
|
|
563
|
-
unanswered permission prompt is not a reason to raise `proxyToolTimeoutMs`. Do not present a raised deadline as the
|
|
564
|
-
fix for a long subagent; the default already waits for it. A deadline-free call is not
|
|
565
|
-
silent while it waits: it logs `proxy call still waiting, no deadline` at WARN after
|
|
566
|
-
five minutes and every five minutes after, with tool, call id and elapsed time. That
|
|
567
|
-
line is a status report, never a failure; it does not end the call. A call that HAS a
|
|
568
|
-
deadline instead logs `proxy call still waiting, deadline approaching` once, at 60% of
|
|
569
|
-
that deadline, carrying `remainingMs` and naming `proxyToolTimeoutMs`; deadlines under
|
|
570
|
-
a minute are not announced, because there the notice and the rejection would arrive
|
|
571
|
-
together. Neither line means something is wrong and neither ends a call. Use them, or
|
|
572
|
-
`/claude-code-doctor`, to tell a working subagent from a wedged one before suggesting
|
|
573
|
-
any timeout change.
|
|
574
|
-
|
|
575
606
|
### Let Claude load the user's opencode skills
|
|
576
607
|
|
|
577
608
|
```json
|
|
@@ -580,8 +611,11 @@ any timeout change.
|
|
|
580
611
|
|
|
581
612
|
The bridge is off by default. With it on, `Skill("<name>")` works for any skill opencode
|
|
582
613
|
advertises. Bridged names are `opencode-skills:<name>`, including this bundled skill as
|
|
583
|
-
`opencode-skills:claude-code-plugin`. The package also
|
|
584
|
-
|
|
614
|
+
`opencode-skills:claude-code-plugin`. The package also makes opencode itself list the
|
|
615
|
+
bundled skill: on opencode 1.x by adding its directory to `skills.paths` in the config
|
|
616
|
+
hook, on opencode 2 by registering it through the `skill` domain (a skill opencode
|
|
617
|
+
already found under the same id is left alone). Older opencode versions may not support
|
|
618
|
+
either surface.
|
|
585
619
|
The native Claude bridge needs `--plugin-dir` support and is wired into the headless
|
|
586
620
|
streaming and interactive spawns, never compaction. Set `true`
|
|
587
621
|
only when the user asks for it, since a large skill set costs prompt tokens twice; the
|
|
@@ -591,9 +625,10 @@ User roots, in precedence order: walking from cwd to filesystem root, `.opencode
|
|
|
591
625
|
then `.claude/skills` then `.agents/skills` at each level; home `.opencode/skills`;
|
|
592
626
|
`OPENCODE_CONFIG_DIR/{skills,skill}`; `XDG_CONFIG_HOME/opencode/{skills,skill}` (home
|
|
593
627
|
`.config` fallback); then `~/.claude/skills` and `~/.agents/skills`. Those last two are
|
|
594
|
-
opencode's external scans
|
|
595
|
-
`OPENCODE_DISABLE_CLAUDE_CODE_SKILLS`
|
|
596
|
-
walk-up
|
|
628
|
+
opencode's external scans: `OPENCODE_DISABLE_EXTERNAL_SKILLS` drops both and
|
|
629
|
+
`OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` drops `~/.claude/skills` alone. Neither is ever
|
|
630
|
+
reached through the walk-up, and a workspace that happens to BE the home directory does
|
|
631
|
+
not smuggle them in early. First name wins, so a project shadows a global and an opencode-managed copy
|
|
597
632
|
shadows an external one; enabled user bridging can shadow bundled names. A skill is known
|
|
598
633
|
by the `name:` its SKILL.md frontmatter declares (directory basename when it declares
|
|
599
634
|
none or an unusable one), which is the name opencode advertises. Only immediate
|
|
@@ -614,10 +649,14 @@ not just one.
|
|
|
614
649
|
{ "idleProcessTimeoutMs": 900000 }
|
|
615
650
|
```
|
|
616
651
|
|
|
617
|
-
|
|
618
|
-
conversation's `claude` process
|
|
619
|
-
|
|
620
|
-
|
|
652
|
+
Idle eviction is **off by default**. With the option unset, or set to `0`, a
|
|
653
|
+
conversation's `claude` process is kept until the LRU cap evicts it, which is why many
|
|
654
|
+
open chats cost memory: an idle `claude --print` holds roughly 250 MB. This example
|
|
655
|
+
frees a worker fifteen minutes after its last turn ends; the session id is retained, so
|
|
656
|
+
the next message resumes the same conversation through `--resume` and only pays for the
|
|
657
|
+
spawn. The cap is 16 live processes, oldest idle first. Neither the timer nor the cap
|
|
658
|
+
ever takes a worker mid-turn: a process found in flight when the timer fires is re-timed
|
|
659
|
+
instead of killed, and a round where all 16 are busy evicts nothing and warns.
|
|
621
660
|
|
|
622
661
|
### Different `/compact` model
|
|
623
662
|
|
|
@@ -643,11 +682,14 @@ restart. Logs rotate above 5 MB to `plugin.log.1`, which can also contain privat
|
|
|
643
682
|
|
|
644
683
|
A published version does not reach a running opencode. First distinguish an npm pin,
|
|
645
684
|
npm latest resolution, and a local `file://` install. Preserve a pin unless the user
|
|
646
|
-
requested changing it.
|
|
647
|
-
`~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
685
|
+
requested changing it. An `@latest` install is frozen in opencode's package cache at
|
|
686
|
+
`~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest/`, and a
|
|
687
|
+
plain restart never re-resolves it: removing that one directory and then fully
|
|
688
|
+
relaunching is what picks a new version up. Inspect the actual cache location and
|
|
689
|
+
package identity and get approval before removing only that stale package directory,
|
|
690
|
+
never the whole cache or auth/session directories. Respect platform/XDG paths
|
|
691
|
+
(`XDG_CACHE_HOME` moves it). Then fully relaunch every opencode window, including serve
|
|
692
|
+
and GUI processes. A `file://` install uses the checkout's
|
|
651
693
|
`dist/`: rebuild with `npm run build` and restart after approval, not cache deletion.
|
|
652
694
|
No manual skill copy/update is needed. Do not publish or release as part of configuring.
|
|
653
695
|
|
|
@@ -717,12 +759,25 @@ relevant, redacted spawn/bridge entry for actual routing after an approved norma
|
|
|
717
759
|
Useful log lines to search for (redact payloads): `spawning new claude process`,
|
|
718
760
|
`bridged opencode skills into claude`, `interrupt sent for aborted turn`, `btw:`,
|
|
719
761
|
`rendering opencode-side tool result as text`, `proxy-mcp tool call received`,
|
|
720
|
-
`evicting idle claude process`, `
|
|
762
|
+
`evicting idle claude process`, `evicting LRU claude process`, `background subagent gate`,
|
|
763
|
+
`proxy call still waiting`, `fast mode` warnings.
|
|
764
|
+
|
|
765
|
+
Version requirements. The first four are flag gates in `src/cli-version.ts`; the last two
|
|
766
|
+
are model floors enforced outside the plugin. Check with `claude --version`; a binary
|
|
767
|
+
that does not answer it disables every gated flag.
|
|
768
|
+
|
|
769
|
+
| Claude Code CLI | What it gates |
|
|
770
|
+
|---|---|
|
|
771
|
+
| 2.1.142+ | `--thinking-display summarized`, so Opus 4.7 thinking summaries |
|
|
772
|
+
| 2.1.220+ | fast mode, which is `--settings '{"fastMode":true}'` |
|
|
773
|
+
| 2.1.258+ | `--restricted` (the first layer of `permissionPreset: "read-only"`) and the `side_question` control request behind `/btw` |
|
|
774
|
+
| 2.1.263+ | `--permission-prompts none` (the second read-only layer) |
|
|
775
|
+
| 2.1.280+ | `claude-opus-5-5`; the API rejects it from an older CLI with a 400 naming that floor |
|
|
776
|
+
| 2.1.284+ | `claude-sonnet-5-5` on its real limits; an older CLI still runs it, on fallback limits, and the plugin warns |
|
|
721
777
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
`claude --version`.
|
|
778
|
+
Below a flag gate the plugin drops the flag rather than failing the spawn, and says so
|
|
779
|
+
at WARN. `--plugin-dir` (the skill bridge) has no published version marker, so it is
|
|
780
|
+
probed through the binary's own `--help` instead of a semver threshold.
|
|
726
781
|
|
|
727
782
|
Only if a proxy security check is specifically requested: identify the exact local
|
|
728
783
|
proxy port first, not every opencode listener. An unauthenticated `initialize` with
|
|
@@ -736,10 +791,11 @@ versions, cwd and its resolution tier, providers, accounts, `proxyTools`, disk M
|
|
|
736
791
|
servers, the `permissionPreset` in force per provider (`provider: preset`, `none` where
|
|
737
792
|
unset, an unknown name marked `(unknown, nothing applied)`, plus a
|
|
738
793
|
**Permission preset overrides** block listing what an applied preset replaced),
|
|
739
|
-
transport, whether an `ANTHROPIC_API_KEY` is present
|
|
740
|
-
live `claude` processes (opencode session, model, pid, in flight,
|
|
741
|
-
proxy calls with their deadlines
|
|
742
|
-
proxy URL (`401, good`; anything
|
|
794
|
+
transport, `planModeQuestion`, `turnStats`, whether an `ANTHROPIC_API_KEY` is present
|
|
795
|
+
(never its value), the live `claude` processes (opencode session, model, pid, in flight,
|
|
796
|
+
age, effort), pending proxy calls with their deadlines (`none` for a deadline-free
|
|
797
|
+
`task`), one unauthenticated `initialize` against each proxy URL (`401, good`; anything
|
|
798
|
+
else is flagged unsafe), and the last stderr of any child that produced some. Prefer it over asking for
|
|
743
799
|
`plugin.log` for a first look. It carries no bearer token, no key value and no system
|
|
744
800
|
prompt. A user-defined `claude-code-doctor` command is never overwritten. The name has
|
|
745
801
|
no space in it: opencode would read the second word as an argument.
|
|
@@ -764,13 +820,14 @@ same as "no": on a fresh process, send a message and run it again before conclud
|
|
|
764
820
|
anything. Only a 1.x host that said no is told about
|
|
765
821
|
`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS`.
|
|
766
822
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
823
|
+
A **Plan usage** section is always printed, but it is empty unless asked for: the plain
|
|
824
|
+
command prints one line saying how to fill it. `/claude-code-doctor usage` fills it with
|
|
825
|
+
the CLI's own `/cost` answer (subscription vs API key, 5-hour and 7-day window use,
|
|
826
|
+
reset times, what is driving them), quoted rather than reinterpreted. Measured free on
|
|
827
|
+
2.1.280 (`num_turns: 0`, `$0`, no API call), so suggest it for "how much have I used"
|
|
828
|
+
and limit questions. It is opt-in only because it starts a short-lived `claude`, which
|
|
829
|
+
runs the user's `SessionStart` hooks and takes a few seconds; say that when suggesting
|
|
830
|
+
it. Do not propose `--bare` to skip the hooks: it never reads OAuth, so it reports
|
|
774
831
|
nothing about a subscription.
|
|
775
832
|
|
|
776
833
|
Claude Code stream events the plugin now surfaces without debug logging: a rate-limit
|
|
@@ -810,7 +867,8 @@ Prefer the doctor: it needs no logging change and no restart.
|
|
|
810
867
|
| "Failed to authenticate: OAuth session expired", one account, turns failing in milliseconds | That account's CLI login lapsed | `claude auth status` for it, then log in again with the command the `▌ **claude account:**` note prints (`CLAUDE_CONFIG_DIR=<that account's dir> claude auth login`). Restart opencode after: a switch taken from the failover form lasts until restart. Login is a user action, never a diagnostic probe |
|
|
811
868
|
| A tool call reported as rejected although it ran | Two fixed causes: opencode 1.18.32 aborts the provider signal of every step ending in tool calls, read as an operator stop (0.26.1); and a call waiting on an unanswered permission prompt was rejected at the flat 10-minute deadline, after which the late approval cancelled Claude's next call (0.26.2) | Upgrade to 0.26.2+ and relaunch. Do NOT raise `proxyToolTimeoutMs` for this: a deadline now waits while opencode reports the session busy |
|
|
812
869
|
| `proxy call still waiting` in the log, or a `task` that looks stuck | Expected: `task`/`task_batch` carry no default deadline, and the line is a status report | `/claude-code-doctor` lists pending calls with tool, age and deadline. Tell a working subagent from a wedged one there before proposing any timeout change; see the note under "Proxy tool names" |
|
|
813
|
-
| An MCP server's tools are simply absent | Claude Code could not connect that server | Read the once-per-process WARN at session start. `mcpServers` in the ready block is disk discovery, not live connectivity; fix the server where it is configured |
|
|
870
|
+
| An MCP server's tools are simply absent | Claude Code could not connect that server | Read the once-per-process WARN at session start, and the doctor's **MCP config entries Claude Code skipped** section for one the CLI refused outright. `mcpServers` in the ready block is disk discovery, not live connectivity; fix the server where it is configured |
|
|
871
|
+
| An MCP server opencode has configured is missing on the FIRST turn of a fresh `opencode run`, but present in the TUI | Not a config fault. The bridge reads opencode's live MCP status when the turn plans its spawn, and a server that is still connecting is correctly read as not enabled: measured on opencode 2.0.16, the turn was planned 47 ms before opencode logged `mcp connected server=cbm`. A reused `claude` process keeps the MCP config it was spawned with, so a later turn in the same run does not re-bridge it either | Send a second message in a new chat, or use the TUI, which connects servers well before the first prompt. No provider option changes it, and both opencode majors behave the same way |
|
|
814
872
|
| `permissionPreset` set but nothing about the session looks restricted | The option never reached that provider, or the name is not one the plugin knows (only `read-only` exists) | Read the `permissionPreset` row in `/claude-code-doctor`, or `permissionPresets` in the ready block, for the provider the conversation is on: `none` means it is not configured there (each account is its own provider id), `applied: false` with a name means an unrecognised name applied nothing, and `overrides` lists what an applied preset replaced |
|
|
815
873
|
| `permissionPreset: "read-only"` set, but reads are unconfined or something still prompts | `--restricted` needs CLI 2.1.258 and `--permission-prompts none` needs 2.1.263; below those the preset falls back to `--disallowedTools` plus the plugin's own deny and WARNs naming what is lost | `claude --version`. Below 2.1.258 the working-directory confinement on reads is gone; below 2.1.263 the denial happens in the plugin instead of the CLI. The preset still holds, with one layer fewer |
|
|
816
874
|
| `/btw` shows "Queued" or "requires an idle Claude Code session" | Plugin older than 0.15.2, or a window started before the current build | Upgrade and restart. `/btw` also needs Claude Code 2.1.258+ |
|
|
@@ -834,17 +892,17 @@ Prefer the doctor: it needs no logging change and no restart.
|
|
|
834
892
|
| "What does the plugin actually think is going on?" | Startup diagnostics go to a log that is off by default | Run `/claude-code-doctor` in the session; paste that instead of the log |
|
|
835
893
|
| A turn ended with no answer and nothing said why | The CLI's `result` carried a failure subtype, or a rate limit was rejected | Both are now written into the transcript as `▌` lines; read the subtype or the limit reason there |
|
|
836
894
|
| The reply is empty and there is no error either | Claude finished the turn without writing anything or calling a tool | A `▌ **no reply:**` note says so, and says whether it thought first. Nothing failed and nothing is pending: send the message again. There is no automatic retry, and `"autoContinueIncompleteTurns": false` removes the note too |
|
|
837
|
-
| A CLI tool row looks successful but its output is an error | Plugin older than
|
|
895
|
+
| A CLI tool row looks successful but its output is an error | Plugin older than 0.19.0 forwarded `is_error` results as successes | Upgrade; failed CLI tools now render as failed |
|
|
838
896
|
| Claude "forgot" the earlier part of a long conversation | Claude Code compacted its own context | Look for the `▌ **context compacted:**` note in the transcript |
|
|
839
897
|
| Claude forgot the whole conversation at once | Claude Code cleared it (`/clear` sent as a message, or a plan-mode exit that clears context) | Look for the `▌ **claude code reset:**` note. The plugin does not replay history there on purpose; a new opencode session gets a clean slate |
|
|
840
898
|
| Wanting the per-turn cost in the chat | Not shown by default | Set `turnStats: true` and restart opencode |
|
|
841
|
-
| On the interactive transport, every turn ends with a `▌ **claude code error:**` note naming `end_turn` (or `stop_sequence` / `max_tokens`), and `turnStats` never prints | Plugin older than
|
|
842
|
-
| On the interactive transport with a working directory under `/tmp` (or any symlinked path), the turn hangs until the 30-minute turn timeout and then reports no terminal stop reason | Plugin older than
|
|
843
|
-
| On the interactive transport, a turn's output tokens look about double, and `turnStats` shows one call's input where the turn used many | Plugin older than
|
|
844
|
-
| opencode auto-compacts a Claude session far below the model's window, often several times in a row after tool-heavy turns | Plugin older than
|
|
899
|
+
| On the interactive transport, every turn ends with a `▌ **claude code error:**` note naming `end_turn` (or `stop_sequence` / `max_tokens`), and `turnStats` never prints | Plugin older than 0.33.0 put the stop reason in the synthesized `result`'s `subtype`, and any non-`success` subtype finishes the turn as an error, which also suppresses the stats footer | Upgrade and relaunch. A turn that reaches a terminal stop reason now synthesizes the shape a headless turn emits (`subtype: "success"`, the stop reason in a top-level `stop_reason`), so it finishes as an ordinary reply. A turn that reaches NO terminal stop reason is still an error on purpose, so truncation stays visible |
|
|
900
|
+
| On the interactive transport with a working directory under `/tmp` (or any symlinked path), the turn hangs until the 30-minute turn timeout and then reports no terminal stop reason | Plugin older than 0.33.0 named the transcript directory from `path.resolve`, which does not follow symlinks, so it tailed a file Claude Code never writes. On macOS `/tmp` is a symlink to `/private/tmp` | Upgrade and relaunch. The directory is now named from the cwd's resolved real path, which is what the CLI uses (`/tmp/scratch` is `~/.claude/projects/-private-tmp-scratch`). Check the `jsonlPath` in the `prepared interactive claude session` log line against the directory that actually exists under `<CLAUDE_CONFIG_DIR>/projects/` |
|
|
901
|
+
| On the interactive transport, a turn's output tokens look about double, and `turnStats` shows one call's input where the turn used many | Plugin older than 0.32.0 summed the session transcript's usage per RECORD, and the JSONL writes one record per content block with the call's usage repeated on each | Upgrade and relaunch. Counting is now once per API call: a four-tool turn that reported 1,306 output tokens reports its real 653, and the stats line carries the turn's totals as it does headlessly. Headless turns were never affected by this one |
|
|
902
|
+
| opencode auto-compacts a Claude session far below the model's window, often several times in a row after tool-heavy turns | Plugin older than 0.31.0 reported the CLI's turn-summed usage (every API call's cache reads added up) as the context size | Upgrade and relaunch. opencode's per-message tokens are now the last call's context, so its cost figure for a multi-call turn is lower than the real one; the real cost is in `turnStats` and `providerMetadata["claude-code"].costUsd` |
|
|
845
903
|
| Turn ends with an error naming an exit code or signal and a stderr tail | The `claude` child died mid-turn without emitting its terminal `result` | Read the quoted stderr; that is the CLI's own reason. Older builds reported this as a normal stop, so a truncated answer looked finished |
|
|
846
|
-
| An answer is cut off with no error, in a window with many open chats | Plugin older than
|
|
847
|
-
| A `claude` worker lingers after its chat was deleted, or after opencode quit | Plugin older than
|
|
904
|
+
| An answer is cut off with no error, in a window with many open chats | Plugin older than 0.20.0: LRU eviction could kill a process mid-turn | Upgrade. Eviction now takes the oldest idle process and skips the round entirely when all 16 are busy; a configured `idleProcessTimeoutMs` re-times a busy worker rather than killing it |
|
|
905
|
+
| A `claude` worker lingers after its chat was deleted, or after opencode quit | Plugin older than 0.20.0 | Upgrade. Deleting a chat now releases its workers; every retained worker is killed when opencode exits |
|
|
848
906
|
|
|
849
907
|
## Which login bills what
|
|
850
908
|
|