@khalilgharbaoui/opencode-claude-code-plugin 0.33.1 → 0.34.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khalilgharbaoui/opencode-claude-code-plugin",
3
- "version": "0.33.1",
3
+ "version": "0.34.0",
4
4
  "description": "Claude Code CLI provider plugin for opencode",
5
5
  "author": "Khalil Gharbaoui",
6
6
  "type": "module",
@@ -21,7 +21,7 @@
21
21
  "build": "tsup",
22
22
  "dev": "tsup --watch",
23
23
  "typecheck": "tsc --noEmit",
24
- "test": "OPENCODE_CLAUDE_CODE_LOG_FILE=0 tsx --test test-bridge.ts test-broker.ts test-proxy-mcp.ts test-proxy-task.ts test-auto-continue.ts test-has-new-user-content.ts test-get-claude-user-message.ts test-logger.ts test-cli-args.ts test-permission-presets.ts test-session-manager.ts test-compaction-model.ts test-tool-mapping.ts test-cwd-resolution.ts test-todo-ledger.ts test-session-affinity.ts test-config-models.ts test-ask-user-question.ts test-claude-session-wrapper.ts test-spawn-env.ts test-respawn.ts test-startup-diagnostics.ts test-subagent-hint.ts test-exit-plan-mode-question.ts test-compress-tool.ts test-agent-models.ts test-side-question.ts test-btw-command.ts test-effort-sessions.ts test-tool-block-index.ts test-context-usage.ts test-skill-bridge.ts test-turn-stats.ts test-cli-events.ts test-cli-events-stream.ts test-result-fallback.ts test-doctor.ts test-configure-skill.ts test-unattended-replay.ts test-process-lifecycle.ts test-account-failover.ts test-host-tools.ts test-v2-entrypoint.ts test-v2-client.ts test-tmp-dir.ts test-cleanup-stale.ts test-account-wrapper.ts test-runtime-status-sessions.ts test-index-hooks.ts test-silent-turn.ts test-mcp-tool-result-name.ts test-model-fallback.ts test-do-generate.ts test-interactive-usage.ts test-background-subagents.ts test-interactive-result.ts"
24
+ "test": "OPENCODE_CLAUDE_CODE_LOG_FILE=0 tsx --test test-bridge.ts test-broker.ts test-proxy-mcp.ts test-proxy-task.ts test-auto-continue.ts test-has-new-user-content.ts test-get-claude-user-message.ts test-logger.ts test-cli-args.ts test-permission-presets.ts test-session-manager.ts test-compaction-model.ts test-tool-mapping.ts test-cwd-resolution.ts test-todo-ledger.ts test-session-affinity.ts test-config-models.ts test-ask-user-question.ts test-claude-session-wrapper.ts test-spawn-env.ts test-respawn.ts test-startup-diagnostics.ts test-subagent-hint.ts test-exit-plan-mode-question.ts test-compress-tool.ts test-agent-models.ts test-side-question.ts test-btw-command.ts test-effort-sessions.ts test-tool-block-index.ts test-context-usage.ts test-skill-bridge.ts test-turn-stats.ts test-cli-events.ts test-cli-events-stream.ts test-result-fallback.ts test-doctor.ts test-configure-skill.ts test-unattended-replay.ts test-process-lifecycle.ts test-account-failover.ts test-host-tools.ts test-v2-entrypoint.ts test-v2-client.ts test-tmp-dir.ts test-cleanup-stale.ts test-account-wrapper.ts test-runtime-status-sessions.ts test-index-hooks.ts test-silent-turn.ts test-mcp-tool-result-name.ts test-model-fallback.ts test-do-generate.ts test-interactive-usage.ts test-background-subagents.ts test-interactive-result.ts test-cli-probe-cache.ts test-mcp-late-connect.ts"
25
25
  },
26
26
  "dependencies": {
27
27
  "@ai-sdk/provider": "^3.0.8",
@@ -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; do not use the known-broken question form to configure itself.
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` or the two known account-limit error texts, 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. |
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 five options around it. Unset changes nothing. `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. |
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. |
@@ -127,8 +130,9 @@ Defaults below describe normal headless opencode use when the key is absent.
127
130
  | `bridgeOpencodeMcp` | boolean | `true` | Discover/translate disk MCP config plus runtime enabled status. False stops this bridge, not explicit `mcpConfig`, the built-in-tool proxy, or Claude's own MCP settings. Only bridge trusted servers. |
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
- | `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. |
133
+ | `hotReloadMcp` | boolean | `true` | With bridging on, compare merged MCP config/status at turn start and respawn on drift, so a server enabled, disabled or finished connecting since the spawn reaches the model. Keeps the session via headless `--resume`. Acts only at a safe boundary: never during compaction, never on the interactive transport, and never while a proxied call is pending, a turn is in flight or a plan-mode approval is outstanding. Logs the joined and left server names at INFO. One respawn per conversation per `CLAUDE_CODE_MCP_HOT_RELOAD_COOLDOWN_MS` (default 60000) so a flapping server cannot respawn every turn. Does not reload arbitrary provider options or watch explicit `mcpConfig` contents. |
134
+ | `mcpConnectWaitMs` | number | `3000` | How long the first turn waits for MCP servers the host reports as still connecting before planning the spawn without them. Only opencode 2 reports that state (`pending`); opencode 1's status call blocks until every server decides, so this is a no-op there and costs one status call as before. `0` disables the wait; negative or non-numeric values fall back to the default. A server slower than the budget is still bridged (pending is not read as disabled) and `hotReloadMcp` brings a later one in on the next turn. |
135
+ | `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
136
  | `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
137
  | `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
138
  | `multiStepContinuation` | boolean | `true` | Append a system-prompt hint to chain tool calls in one turn instead of stopping between subtasks. |
@@ -136,7 +140,7 @@ Defaults below describe normal headless opencode use when the key is absent.
136
140
  | `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
141
  | `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
142
  | `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, and input/output/cache-read/cache-write tokens, 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. |
143
+ | `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
144
  | `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
145
  | `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
146
  | `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 +175,7 @@ their secret values. Arbitrary MCP `{env:NAME}` placeholders are outside this li
171
175
  | `CLAUDE_CLI_PATH` | Direct factory fallback for absent `cliPath`. Normal opencode registration supplies `"claude"`; set the option explicitly there. |
172
176
  | `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
177
  | `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. |
178
+ | `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
179
  | `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
180
  | `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
181
  | `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 +198,34 @@ their secret values. Arbitrary MCP `{env:NAME}` placeholders are outside this li
193
198
  | `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
199
  | `OPENCODE_CONFIG` | Explicit config file, also read by the disk MCP bridge before project layers. |
195
200
  | `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. |
201
+ | `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. |
202
+ | `CLAUDE_CODE_MCP_HOT_RELOAD_COOLDOWN_MS` | Minimum gap in ms between two `hotReloadMcp` respawns of one conversation; default 60000 for missing, non-numeric or negative values. A server flapping between connected and failed would otherwise cost a kill and a `--resume` spawn every turn. `0` disables the guard. A real second change is not lost, it lands on the first turn after the gap. |
197
203
  | `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
204
  | `XDG_CACHE_HOME` | Account wrapper/cache-cleanup root override; do not assume the default cache path when upgrading. |
199
205
  | `HOME` | Home expansion and direct agent-file discovery (other paths also use OS homedir). Do not change it to switch accounts. |
200
206
  | `USERPROFILE` | Home fallback where `HOME` is absent. |
201
207
  | `OPENCODE_VERSION` | Startup diagnostics version fallback, not a capability override. |
208
+ | `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. |
209
+ | `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. |
210
+ | `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` | The same switch for `~/.claude/skills` alone; `~/.agents/skills` is unaffected by it. |
202
211
 
203
212
  ## Recipes
204
213
 
214
+ **Which major each recipe is for.** Every options fragment below is written in the
215
+ opencode 1.x spelling, `provider.claude-code.options`, which opencode 2 also reads. On a
216
+ config that only ever serves opencode 2, put the same fragment under
217
+ `providers.claude-code.settings` instead. Fragments belong inside that options object,
218
+ never at the config root. The opencode 2 recipe has the full precedence list.
219
+
205
220
  ### Minimum install
206
221
 
207
222
  ```json
208
223
  { "plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"] }
209
224
  ```
210
225
 
211
- Everything else is optional. Models appear in the picker without extra config.
226
+ Everything else is optional. Models appear in the picker without extra config. The
227
+ `plugin` key is read by both opencode majors, so this block needs no edit after an
228
+ opencode 2 upgrade.
212
229
 
213
230
  ### opencode 2
214
231
 
@@ -218,9 +235,9 @@ Same package, same config. 2.x's native key is `plugins` (plural), but it still
218
235
  { "plugins": ["@khalilgharbaoui/opencode-claude-code-plugin"] }
219
236
  ```
220
237
 
221
- - Check the major first with `opencode --version`. `plugin` works on both majors (measured: 2.0.11 loaded a plugin listed under `plugin`); `plugins` is read by 2.x only.
222
- - `provider.claude-code.options` still works on 2.x; `provider.claude-code.settings` is the native spelling and wins where both are set. `accounts` may also sit in the plugin entry's own `options`.
223
- - A local checkout is loaded by pointing `plugins` at its **`dist`** directory, never the repository root.
238
+ - 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.
239
+ - 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"]}}`.
240
+ - 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
241
  - 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
242
 
226
243
  #### V2 MCP and Code Mode
@@ -408,8 +425,7 @@ Never on compaction turns, title stubs, or the interactive transport.
408
425
  { "proxyTools": ["Bash", "Edit", "Write", "WebFetch", "Task"], "extraDisallowedTools": ["NotebookEdit"] }
409
426
  ```
410
427
 
411
- Options fragments in recipes belong inside `provider.claude-code.options`, not at
412
- the config root. Preserve other wanted proxies when changing this replacement list.
428
+ Preserve other wanted proxies when changing this replacement list.
413
429
  `Read`, `Glob` and `Grep` have tool mappings/disallowed-name entries but no selectable
414
430
  proxy definitions in this version, just like `NotebookEdit` has no proxy. Adding them
415
431
  to `proxyTools` warns and leaves the built-ins unproxied. Use `extraDisallowedTools`
@@ -440,7 +456,9 @@ That one line is the whole posture. Do not also set `skipPermissions`,
440
456
  `permissionMode`, `controlRequestBehavior` or `controlRequestToolBehaviors`
441
457
  alongside it: the preset replaces all four and logs each value it dropped.
442
458
  `proxyTools` is filtered rather than replaced, so a list naming `Question`
443
- keeps it while `Bash`, `Edit`, `Write`, `WebFetch` and `Task` go.
459
+ keeps it while `Bash`, `Edit`, `Write`, `WebFetch` and `Task` go, and
460
+ `extraDisallowedTools` is added to rather than replaced, so names already
461
+ listed there survive.
444
462
 
445
463
  Read-only is enforced at three layers because no single one covers the plugin:
446
464
  `--restricted` removes the CLI's own command and code-running tools, the
@@ -487,12 +505,53 @@ Names below become `mcp__opencode_proxy__<name>`; input config is case-insensiti
487
505
  | `write` | `"Write"`, default; replaces CLI Write. |
488
506
  | `webfetch` | `"WebFetch"`, default; replaces CLI WebFetch. |
489
507
  | `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 on CLI 2.1.258. |
508
+ | `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
509
  | `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
510
  | `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
511
  | `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
512
  | `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
513
 
514
+ ### How a proxied call ends
515
+
516
+ A proxied call is held open until an event ends it, and the plugin listens to the
517
+ `claude` process, the stream and the control protocol for those events rather than
518
+ inferring failure from elapsed time. opencode's result resolves the call. An abort
519
+ interrupts the CLI and rejects the turn's pending calls, unless opencode still reports
520
+ the session busy (opencode 1.18 aborts the signal of every tool step while it runs the
521
+ tool, so busy means the call is being served, not refused). The next user message
522
+ rejects what the previous turn left pending and tells the CLI. The process exiting, the
523
+ chat being deleted, or opencode exiting rejects the rest. That is why `task` and
524
+ `task_batch` carry no default deadline and a subagent runs to completion.
525
+
526
+ Three timers remain and are distinct from that: the optional per-tool deadlines
527
+ (`proxyToolTimeoutMs`, a backstop the user chooses), the start and inactivity
528
+ watchdogs (for a process that is alive but silent, which emits nothing to listen to; a
529
+ CLI parked in a proxied call is exempt), and the connection keepalives (SSE comments or
530
+ JSON whitespace every 15 s, so the CLI's HTTP client does not give up on a long call;
531
+ they never extend a deadline).
532
+
533
+ A deadline that passes while opencode still reports the session busy (a permission
534
+ prompt the user has not answered, or the tool still running) does not end the call: it
535
+ logs `proxy call past its deadline, but opencode is still serving it; waiting` at WARN
536
+ once and rechecks every minute. So an unanswered permission prompt is not a reason to
537
+ raise `proxyToolTimeoutMs`, and a raised deadline is never the fix for a long subagent,
538
+ because the default already waits for it.
539
+
540
+ Two log lines report a call that is simply taking a while, and neither is a failure or
541
+ ends a call:
542
+
543
+ - `proxy call still waiting, no deadline`, WARN, after five minutes and every five
544
+ minutes after, with tool, call id and elapsed time. Only a call whose resolved
545
+ deadline is `0` reaches it, which by default means `task` and `task_batch`, and also
546
+ any tool the user set to `0` in `proxyToolTimeoutMs`.
547
+ - `proxy call still waiting, deadline approaching`, WARN, once, at 60% of that call's
548
+ deadline, carrying `remainingMs` and naming `proxyToolTimeoutMs`. Deadlines under a
549
+ minute are not announced, because there the notice and the rejection would arrive
550
+ together.
551
+
552
+ Use them, or `/claude-code-doctor`, to tell a working subagent from a wedged one
553
+ before suggesting any timeout change.
554
+
496
555
  ### Background subagents (fire-and-collect)
497
556
 
498
557
  Off unless the **opencode process** has `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`
@@ -524,7 +583,10 @@ verified live on 2.0.16.
524
583
  Without it, opencode rejects a `background: true` call outright
525
584
  (`Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`), losing
526
585
  the dispatch, so the plugin strips `background` from the `task` and `task_batch` schemas
527
- on such a host and registers neither extra tool. Which way it went is in `plugin.log`:
586
+ on such a host and registers neither extra tool. The plugin never reads that variable
587
+ itself: it reads whether opencode's own advertised `task` schema carries a `background`
588
+ property, which is how a 1.x host publishes the flag, and on opencode 2 it does not ask
589
+ at all. Which way it went is in `plugin.log`:
528
590
 
529
591
  ```
530
592
  background subagent gate {"supported":false,"registryResolved":true,"hostApi":"v1","note":"`background` stripped ..."}
@@ -543,35 +605,6 @@ answer, or opencode 2 offering it unconditionally), and the background tasks thi
543
605
  process has collected or cancelled. The gate is read while a turn plans its proxy tools,
544
606
  so a fresh process reports `Not read yet this process` until one message has been sent.
545
607
 
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
608
  ### Let Claude load the user's opencode skills
576
609
 
577
610
  ```json
@@ -580,8 +613,11 @@ any timeout change.
580
613
 
581
614
  The bridge is off by default. With it on, `Skill("<name>")` works for any skill opencode
582
615
  advertises. Bridged names are `opencode-skills:<name>`, including this bundled skill as
583
- `opencode-skills:claude-code-plugin`. The package also registers its skill directory
584
- with opencode's `skills.paths`; older opencode versions may not support that surface.
616
+ `opencode-skills:claude-code-plugin`. The package also makes opencode itself list the
617
+ bundled skill: on opencode 1.x by adding its directory to `skills.paths` in the config
618
+ hook, on opencode 2 by registering it through the `skill` domain (a skill opencode
619
+ already found under the same id is left alone). Older opencode versions may not support
620
+ either surface.
585
621
  The native Claude bridge needs `--plugin-dir` support and is wired into the headless
586
622
  streaming and interactive spawns, never compaction. Set `true`
587
623
  only when the user asks for it, since a large skill set costs prompt tokens twice; the
@@ -591,9 +627,10 @@ User roots, in precedence order: walking from cwd to filesystem root, `.opencode
591
627
  then `.claude/skills` then `.agents/skills` at each level; home `.opencode/skills`;
592
628
  `OPENCODE_CONFIG_DIR/{skills,skill}`; `XDG_CONFIG_HOME/opencode/{skills,skill}` (home
593
629
  `.config` fallback); then `~/.claude/skills` and `~/.agents/skills`. Those last two are
594
- opencode's external scans and obey its own `OPENCODE_DISABLE_EXTERNAL_SKILLS` and
595
- `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` variables; they are never reached through the
596
- walk-up. First name wins, so a project shadows a global and an opencode-managed copy
630
+ opencode's external scans: `OPENCODE_DISABLE_EXTERNAL_SKILLS` drops both and
631
+ `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` drops `~/.claude/skills` alone. Neither is ever
632
+ reached through the walk-up, and a workspace that happens to BE the home directory does
633
+ not smuggle them in early. First name wins, so a project shadows a global and an opencode-managed copy
597
634
  shadows an external one; enabled user bridging can shadow bundled names. A skill is known
598
635
  by the `name:` its SKILL.md frontmatter declares (directory basename when it declares
599
636
  none or an unusable one), which is the name opencode advertises. Only immediate
@@ -614,10 +651,14 @@ not just one.
614
651
  { "idleProcessTimeoutMs": 900000 }
615
652
  ```
616
653
 
617
- The default is thirty minutes: that long after a turn ends with no new message, the
618
- conversation's `claude` process exits, and the next message resumes the same
619
- conversation. This example shortens it to fifteen; `0` keeps workers until the
620
- 8-process LRU cap evicts the oldest idle one. Neither ever kills a worker mid-turn.
654
+ Idle eviction is **off by default**. With the option unset, or set to `0`, a
655
+ conversation's `claude` process is kept until the LRU cap evicts it, which is why many
656
+ open chats cost memory: an idle `claude --print` holds roughly 250 MB. This example
657
+ frees a worker fifteen minutes after its last turn ends; the session id is retained, so
658
+ the next message resumes the same conversation through `--resume` and only pays for the
659
+ spawn. The cap is 16 live processes, oldest idle first. Neither the timer nor the cap
660
+ ever takes a worker mid-turn: a process found in flight when the timer fires is re-timed
661
+ instead of killed, and a round where all 16 are busy evicts nothing and warns.
621
662
 
622
663
  ### Different `/compact` model
623
664
 
@@ -643,11 +684,14 @@ restart. Logs rotate above 5 MB to `plugin.log.1`, which can also contain privat
643
684
 
644
685
  A published version does not reach a running opencode. First distinguish an npm pin,
645
686
  npm latest resolution, and a local `file://` install. Preserve a pin unless the user
646
- requested changing it. Some opencode versions freeze latest in
647
- `~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest/`.
648
- Inspect the actual cache location/package identity and get approval before removing
649
- only that stale package directory, never the whole cache or auth/session directories.
650
- Respect platform/XDG paths. Then fully relaunch. A `file://` install uses the checkout's
687
+ requested changing it. An `@latest` install is frozen in opencode's package cache at
688
+ `~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest/`, and a
689
+ plain restart never re-resolves it: removing that one directory and then fully
690
+ relaunching is what picks a new version up. Inspect the actual cache location and
691
+ package identity and get approval before removing only that stale package directory,
692
+ never the whole cache or auth/session directories. Respect platform/XDG paths
693
+ (`XDG_CACHE_HOME` moves it). Then fully relaunch every opencode window, including serve
694
+ and GUI processes. A `file://` install uses the checkout's
651
695
  `dist/`: rebuild with `npm run build` and restart after approval, not cache deletion.
652
696
  No manual skill copy/update is needed. Do not publish or release as part of configuring.
653
697
 
@@ -717,12 +761,25 @@ relevant, redacted spawn/bridge entry for actual routing after an approved norma
717
761
  Useful log lines to search for (redact payloads): `spawning new claude process`,
718
762
  `bridged opencode skills into claude`, `interrupt sent for aborted turn`, `btw:`,
719
763
  `rendering opencode-side tool result as text`, `proxy-mcp tool call received`,
720
- `evicting idle claude process`, `fast mode` warnings.
764
+ `evicting idle claude process`, `evicting LRU claude process`, `background subagent gate`,
765
+ `proxy call still waiting`, `fast mode` warnings.
766
+
767
+ Version requirements. The first four are flag gates in `src/cli-version.ts`; the last two
768
+ are model floors enforced outside the plugin. Check with `claude --version`; a binary
769
+ that does not answer it disables every gated flag.
770
+
771
+ | Claude Code CLI | What it gates |
772
+ |---|---|
773
+ | 2.1.142+ | `--thinking-display summarized`, so Opus 4.7 thinking summaries |
774
+ | 2.1.220+ | fast mode, which is `--settings '{"fastMode":true}'` |
775
+ | 2.1.258+ | `--restricted` (the first layer of `permissionPreset: "read-only"`) and the `side_question` control request behind `/btw` |
776
+ | 2.1.263+ | `--permission-prompts none` (the second read-only layer) |
777
+ | 2.1.280+ | `claude-opus-5-5`; the API rejects it from an older CLI with a 400 naming that floor |
778
+ | 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
779
 
722
- Version requirements: Claude Code CLI 2.1.142+ recommended (thinking summaries),
723
- 2.1.220+ for fast mode, 2.1.258+ for `/btw`, 2.1.280+ for `claude-opus-5-5` (the
724
- API rejects it from an older CLI with a 400 naming that floor). Check with
725
- `claude --version`.
780
+ Below a flag gate the plugin drops the flag rather than failing the spawn, and says so
781
+ at WARN. `--plugin-dir` (the skill bridge) has no published version marker, so it is
782
+ probed through the binary's own `--help` instead of a semver threshold.
726
783
 
727
784
  Only if a proxy security check is specifically requested: identify the exact local
728
785
  proxy port first, not every opencode listener. An unauthenticated `initialize` with
@@ -736,10 +793,11 @@ versions, cwd and its resolution tier, providers, accounts, `proxyTools`, disk M
736
793
  servers, the `permissionPreset` in force per provider (`provider: preset`, `none` where
737
794
  unset, an unknown name marked `(unknown, nothing applied)`, plus a
738
795
  **Permission preset overrides** block listing what an applied preset replaced),
739
- transport, whether an `ANTHROPIC_API_KEY` is present (never its value), the
740
- live `claude` processes (opencode session, model, pid, in flight, age, effort), pending
741
- proxy calls with their deadlines, and one unauthenticated `initialize` against each
742
- proxy URL (`401, good`; anything else is flagged unsafe). Prefer it over asking for
796
+ transport, `planModeQuestion`, `turnStats`, whether an `ANTHROPIC_API_KEY` is present
797
+ (never its value), the live `claude` processes (opencode session, model, pid, in flight,
798
+ age, effort), pending proxy calls with their deadlines (`none` for a deadline-free
799
+ `task`), one unauthenticated `initialize` against each proxy URL (`401, good`; anything
800
+ else is flagged unsafe), and the last stderr of any child that produced some. Prefer it over asking for
743
801
  `plugin.log` for a first look. It carries no bearer token, no key value and no system
744
802
  prompt. A user-defined `claude-code-doctor` command is never overwritten. The name has
745
803
  no space in it: opencode would read the second word as an argument.
@@ -764,13 +822,14 @@ same as "no": on a fresh process, send a message and run it again before conclud
764
822
  anything. Only a 1.x host that said no is told about
765
823
  `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS`.
766
824
 
767
- `/claude-code-doctor usage` adds a **Plan usage** section: the CLI's own `/cost` answer
768
- (subscription vs API key, 5-hour and 7-day window use, reset times, what is driving
769
- them). Measured free on 2.1.280 (`num_turns: 0`, `$0`, no API call), so suggest it for
770
- "how much have I used" and limit questions. It is opt-in only because it starts a
771
- short-lived `claude`, which runs the user's `SessionStart` hooks and takes a few
772
- seconds; say that when suggesting it. The plain command stays instant and says how to
773
- ask. Do not propose `--bare` to skip the hooks: it never reads OAuth, so it reports
825
+ A **Plan usage** section is always printed, but it is empty unless asked for: the plain
826
+ command prints one line saying how to fill it. `/claude-code-doctor usage` fills it with
827
+ the CLI's own `/cost` answer (subscription vs API key, 5-hour and 7-day window use,
828
+ reset times, what is driving them), quoted rather than reinterpreted. Measured free on
829
+ 2.1.280 (`num_turns: 0`, `$0`, no API call), so suggest it for "how much have I used"
830
+ and limit questions. It is opt-in only because it starts a short-lived `claude`, which
831
+ runs the user's `SessionStart` hooks and takes a few seconds; say that when suggesting
832
+ it. Do not propose `--bare` to skip the hooks: it never reads OAuth, so it reports
774
833
  nothing about a subscription.
775
834
 
776
835
  Claude Code stream events the plugin now surfaces without debug logging: a rate-limit
@@ -810,7 +869,8 @@ Prefer the doctor: it needs no logging change and no restart.
810
869
  | "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
870
  | 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
871
  | `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 |
872
+ | 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 |
873
+ | 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, and mostly fixed. The bridge reads opencode's live MCP status when the turn plans its spawn. On opencode 2 a server still connecting is reported `pending`, which the bridge no longer reads as disabled, and the turn waits up to `mcpConnectWaitMs` (3 s) for the host to decide; opencode 1 cannot reach this state, because its own status call blocks until every server resolves. If the server is slower than the budget it is still bridged, and if it genuinely joins later `hotReloadMcp` moves the conversation onto a process that has it on the next turn, logging `opencode MCP servers changed, respawning claude` with the joined names | Nothing, usually. For a very slow server raise `mcpConnectWaitMs`. Check `plugin.log` for `waited for opencode MCP servers to finish connecting` and for the respawn line; if neither appears and the server is still absent, the status the host reported was a real refusal (`failed`, `needs_auth`), which is opencode's to fix |
814
874
  | `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
875
  | `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
876
  | `/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 +894,17 @@ Prefer the doctor: it needs no logging change and no restart.
834
894
  | "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
895
  | 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
896
  | 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 this release forwarded `is_error` results as successes | Upgrade; failed CLI tools now render as failed |
897
+ | 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
898
  | 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
899
  | 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
900
  | 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 this fix 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 |
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 this fix 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/` |
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 this fix 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 |
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 this fix 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` |
901
+ | 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 |
902
+ | 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/` |
903
+ | 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 |
904
+ | 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
905
  | 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 this fix: LRU eviction could kill a process mid-turn | Upgrade. Eviction now takes the oldest idle process and skips the round when all 8 are busy; the 30-minute idle timer spares a busy worker too |
847
- | A `claude` worker lingers after its chat was deleted, or after opencode quit | Plugin older than this release | Upgrade. Deleting a chat now releases its workers; every retained worker is killed when opencode exits |
906
+ | 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 |
907
+ | 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
908
 
849
909
  ## Which login bills what
850
910