@khalilgharbaoui/opencode-claude-code-plugin 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -469,7 +469,7 @@ reaches the CLI, so there is nothing there to fall back from.
469
469
  | `mcpConfig` | string \| string[] | – | Extra `--mcp-config` paths/JSON passed alongside the bridged config. |
470
470
  | `strictMcpConfig` | boolean | `false` | Pass `--strict-mcp-config` so Claude loads **only** the configured servers and ignores `~/.claude/settings.json`. |
471
471
  | `hotReloadMcp` | boolean | `true` | With MCP bridging on, compare the merged MCP config and runtime status at the start of each turn and respawn the `claude` process when they drifted, so a server you just enabled, disabled or finished connecting becomes visible without restarting opencode or opening a new chat. It only ever acts at a safe boundary: never during `/compact`, never on the interactive transport, never while a proxied call is still in the air, a turn is still running or a plan-mode approval is outstanding, and the Claude session id is preserved for `--resume` so the conversation continues. A log line at INFO names which servers joined and left. A server that flaps buys at most one respawn per minute per conversation (`CLAUDE_CODE_MCP_HOT_RELOAD_COOLDOWN_MS`). Set `false` to keep a cached subprocess until the chat is reset. It does not reload other provider options and does not watch the contents of files named in `mcpConfig`. |
472
- | `mcpConnectWaitMs` | number | `3000` | How long the first turn of a conversation waits for MCP servers opencode reports as still connecting before planning the `claude` spawn without them. Only opencode 2 can report that state (`pending`); opencode 1's own status call blocks until every server has decided, so this budget is what makes the two majors behave alike. Set `0` to always plan with whatever the host says at that instant. A server slower than the budget is not lost either way: it is still bridged, and `hotReloadMcp` moves the conversation onto a process that has it on the next turn. |
472
+ | `mcpConnectWaitMs` | number | `3000` | How long the first turn of a conversation waits for MCP servers opencode reports as still connecting before planning the `claude` spawn without them. Only opencode 2 can report that state (`pending`); opencode 1's own status call blocks until every server has decided, so this budget is what makes the two majors behave alike. Set `0` to always plan with whatever the host says at that instant. Aborting the turn also ends the wait at once. A server slower than the budget is not lost either way: it is still bridged, and `hotReloadMcp` moves the conversation onto a process that has it on the next turn. |
473
473
  | `proxyOpencodeMcpTools` | boolean | `false` | Route opencode's MCP-backed tools through the in-process `opencode_proxy` server instead of bridging them straight into Claude's `--mcp-config`, so each call executes once, inside opencode, with its permission prompt and its tool row. **The default changed from `true` to `false` in this release, and no behaviour changed with it:** at `true` it used to route nothing at all, because discovery read opencode's tool registry, which contains built-ins and plugin-declared tools and has never contained an MCP tool. Discovery now reads the model tool set opencode passes the provider, which is where MCP tools actually are, so the option works, and turning it on is the operator's decision rather than a silent migration of traffic that the direct bridge is handling today. Two caveats before enabling it: pair it with `strictMcpConfig: true`, because a server also registered in Claude Code's own config is reached directly and bypasses the proxy entirely; and a routed call runs in opencode with the calling agent's permissions, the same trade [`proxyOpencodeTools`](#options-reference) makes. Servers whose tools are not found stay on the direct bridge, and a warning says so, so do not treat this as an exactly-once guarantee for write-capable tools. |
474
474
  | `proxyOpencodeTools` | string[] | `[]` | Forward explicitly named opencode tools through the proxy (for example, a plugin's `compress` or V2 Code Mode `execute`). V1 uses registry ids; V2 uses the current model tool snapshot, including its real JSON Schema and agent visibility, not the registry's empty schemas. A forwarded tool runs inside opencode with the calling agent's permissions. A name already held by a proxy def is dropped with a warning. The read-only preset refuses `execute`. See [Forwarding opencode's own tools](#forwarding-opencode-s-own-tools) and [V2 Code Mode](#v2-code-mode). |
475
475
  | `stripContextReminders` | boolean | `false` | Remove opencode-dcp's `<dcp-system-reminder>` blocks from message text when no `compress` tool is proxied, so an order the model cannot follow stops being re-sent with every message that carries it. Inert as soon as `compress` is reachable. See [Trimming unsatisfiable context reminders](#trimming-unsatisfiable-context-reminders). |
@@ -851,7 +851,7 @@ A proxied call ends when something happens to it, not when a clock runs out. The
851
851
  | What happens | What the plugin does |
852
852
  |---|---|
853
853
  | opencode returns the tool's result | resolves the call; the CLI gets the result and carries on |
854
- | you abort the turn (Esc / Ctrl+C) | sends the CLI an `interrupt`, which answers with its own result, and rejects every call the turn had pending, whether the abort lands before content, mid-turn, or while opencode is running the tool between two stream boundaries |
854
+ | you abort the turn (Esc / Ctrl+C) | sends the CLI an `interrupt`, which answers with its own result, and rejects every call the turn had pending, whether the abort lands before content, mid-turn, or while opencode is running the tool between two stream boundaries. A stop that lands before the turn has asked the CLI for anything is the one exception: it interrupts nothing and releases nothing, because the calls still pending there belong to the previous step and the next message orphans them as usual |
855
855
  | you send the next message in that chat | rejects every call the previous turn left pending as orphaned, so the CLI gets an error result and the new turn starts clean |
856
856
  | the `claude` process closes its output or exits, mid-turn or between turns | rejects its pending calls; a mid-turn death also ends the turn as a visible error |
857
857
  | you delete the chat in opencode, or opencode exits | kills the worker and rejects its pending calls |
@@ -944,6 +944,8 @@ When Claude Code refused an entry in an `--mcp-config` it was handed, an **MCP c
944
944
 
945
945
  A **Plugins Claude Code did not load** section works the same way for Claude plugins: one the CLI demoted at load time (for example, a dependency that is not installed) is absent from its plugin list, so its skills, commands and MCP servers are silently missing. The skill bridge is such a plugin (`opencode-skills`), and a failure there is reported as the plugin's own bug rather than your config. A plugin warning only counts when its content did not load; advisory feedback about a plugin that did load stays in the log at INFO.
946
946
 
947
+ A **Hooks Claude Code ran that failed** section covers your own Claude Code hooks, which are the third thing that fails without leaving a trace. Claude Code runs a `SessionStart` hook on every `claude` it starts for you, and when one exits non-zero it discards the hook's contribution and answers the turn normally: the context that hook was supposed to add is simply missing, on every turn of that session, with nothing on screen. The section names the hook, the event, its exit code and its outcome, and it is a warning in your terminal the first time it happens. Only the hook's **stderr** is shown, capped: a hook's stdout is what Claude Code splices into the model's context, so it has no business in a bug report. These are your hooks in your Claude Code settings, not opencode's, and the plugin never passes `--include-hook-events`, so only the `SessionStart` family is ever reported.
948
+
947
949
  ```text
948
950
  /claude-code-doctor usage
949
951
  ```
@@ -954,6 +956,34 @@ The `permissionPreset` row reads `provider: preset` for every registered provide
954
956
 
955
957
  Nothing secret goes in it: not the proxy bearer token, not the value of `ANTHROPIC_API_KEY`, not the system prompt, not a pending call's arguments. A `claude-code-doctor` command you defined yourself is never overwritten. The name has no space in it because opencode reads everything after the first space as the command's arguments. The whole exchange is kept out of any transcript replayed to the CLI, like a `/btw` pair.
956
958
 
959
+ ### Filing an issue: /claude-code-doctor bundle
960
+
961
+ ```text
962
+ /claude-code-doctor bundle
963
+ ```
964
+
965
+ **When filing an issue, paste `/claude-code-doctor bundle`.** It returns the report above plus the recent `NOTICE`, `WARN` and `ERROR` lines from this process's plugin log, redacted so the whole thing is safe to put in a public issue. It starts no process and costs no tokens, so unlike `usage` it stays instant.
966
+
967
+ The point is `plugin.log` itself. It is off by default, and when it is on it has no redaction guarantee at all: it holds spawn argv with `--settings` JSON and absolute paths, the bridged MCP config target, your skill directories, opencode and Claude session ids, and error prose the CLI wrote. Nobody can safely attach it to a GitHub issue, so bug reports arrive as screenshots and guesses instead.
968
+
969
+ The redaction is an **allowlist**, not a filter, because a filter fails silently the first time someone logs a new field. Per line, what survives is:
970
+
971
+ - the timestamp and the level,
972
+ - the message text **only** when it is one of the 112 `NOTICE`/`WARN`/`ERROR` message literals extracted from the plugin's own source. A message built at runtime, including every CLI error string the plugin re-logs, becomes `[redacted message, N chars]` and only its data fields remain,
973
+ - data fields whose key is on an explicit allowlist **and** whose value is then the kind that entry declares: versions, counts, booleans, enums, durations, exit codes, model and tool and server names, paths, and the loopback proxy URL with its query dropped. The allowlist applies at every nesting depth.
974
+
975
+ Everything else, including every key the allowlist does not name, becomes `[redacted, N chars]`, which keeps the shape so you can see a field was there without seeing it. Session ids become a short hash salted per bundle, so two lines about one conversation still correlate in the paste and nowhere else, and your home directory becomes `~` across the whole report, the table included.
976
+
977
+ Never in a bundle: prompt or reply text, system prompts or the appended prompt file, tool inputs or outputs, file contents, environment values, bearer tokens, the proxy `authToken`, API keys, `Authorization` headers, MCP server env or headers, URL credentials or query strings, or the raw spawn argv. The argv is kept as option names with every value replaced, which is what a spawn bug report actually needs.
978
+
979
+ It is capped at 120 lines and 24,000 bytes, newest first, and says how many lines it left out. With file logging off it says so, tells you how to turn it on, and still returns the report:
980
+
981
+ ```sh
982
+ OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode
983
+ ```
984
+
985
+ The plain `/claude-code-doctor` output is unchanged by any of this.
986
+
957
987
  ## Per-turn stats
958
988
 
959
989
  Off by default. With `turnStats: true`:
@@ -1145,6 +1175,7 @@ Each chat keeps a long-lived `claude` subprocess so the model retains its native
1145
1175
  - **New chat** → fresh process under the new session key.
1146
1176
  - **Resumed chat after restart** → in-memory state is gone; a new process spawns and the conversation history is summarized and prepended.
1147
1177
  - **Abort (Esc / Ctrl+C)** → the plugin sends the Claude CLI a stream-json `interrupt` control request, so the CLI actually stops generating and running tools instead of finishing the abandoned turn on your bill. The process stays alive for the next message in that chat, and any proxied call the aborted turn left behind is released when that message arrives (see [How a proxied call ends](#how-a-proxied-call-ends)). If a turn is somehow still running when the next one starts, it is interrupted first (5 s cap). Contributed by [@broskees](https://github.com/broskees).
1178
+ - **Abort during the first moment of a turn** → a turn spends a little time preparing before it asks the CLI for anything: resolving the spawn directory, probing the `claude` version, reading opencode's MCP status and tool registry. A stop pressed in that window used to be dropped, and the turn spawned, ran and billed anyway. It now ends the turn there: nothing is spawned, nothing is written, no running process is interrupted, and the reply is simply empty.
1148
1179
  - **Idle timeout** → when `idleProcessTimeoutMs` is set, a completed headless turn arms an eviction timer (unset or `0` keeps workers until LRU eviction). Reuse cancels it, a worker found mid-turn when it fires is left alone and re-timed, and eviction preserves the session id, so the next message resumes the same conversation with `--resume`. An idle `claude --print` holds around 250 MB, which is the reason to set it if you keep many chats open.
1149
1180
  - **Cap**: 16 active processes, LRU eviction. A process that is mid-turn is never the victim: eviction takes the oldest **idle** one, and when every process is busy it evicts nothing and warns instead, so a running answer is never truncated to make room.
1150
1181
  - **Deleted chat** → deleting a session in opencode kills its `claude` workers at once and forgets their session ids and per-chat state; there is nothing left to resume. Other chats, and the shared fallback bucket used when no session id is known, are untouched.
@@ -1516,7 +1547,8 @@ Four checks answer almost everything. Run them in this order, and stop as soon a
1516
1547
  | Check | What it tells you |
1517
1548
  |---|---|
1518
1549
  | `/claude-code-doctor` in the session | The plugin version actually loaded, the `claude` path and version, which providers and accounts registered, `proxyTools`, the `permissionPreset` per provider and what it replaced, the working directory and which rule picked it, every live `claude` child, and every pending proxy call. No model is called and nothing is billed. Start here. |
1519
- | `OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode`, then grep `~/.local/share/opencode-claude-code/plugin.log` | Whether the plugin loaded at all, and every warning it emitted. The log file is off by default, so turning it on needs a relaunch. |
1550
+ | `/claude-code-doctor bundle` in the session | The same report plus this process's recent `NOTICE`/`WARN`/`ERROR` log lines, redacted by allowlist so you can paste the lot into a public issue. **This is what to attach to a bug report.** See [Filing an issue](#filing-an-issue-claude-code-doctor-bundle). |
1551
+ | `OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode`, then grep `~/.local/share/opencode-claude-code/plugin.log` | Whether the plugin loaded at all, and every warning it emitted. The log file is off by default, so turning it on needs a relaunch. The raw log is **not** safe to attach to an issue: it has no redaction guarantee and can hold whole system prompts. Use `/claude-code-doctor bundle` for that. |
1520
1552
  | `claude --version` | Whether a version-gated feature can work at all. Version floors: 2.1.142 thinking summaries, 2.1.220 fast mode, 2.1.258 `/btw` and `--restricted`, 2.1.263 `--permission-prompts none`, 2.1.280 `claude-opus-5-5`. |
1521
1553
  | `claude auth status`, or `CLAUDE_CONFIG_DIR=~/.claude-<name> claude auth status` | Which account is signed in, and whether its login is still valid. |
1522
1554
 
@@ -1528,6 +1560,7 @@ Four checks answer almost everything. Run them in this order, and stop as soon a
1528
1560
  | `Model unavailable` for a model id you typed | The `providers` field of the ready block, or the same line in `/claude-code-doctor` | Use the provider id that line actually lists. With no `accounts` configured on opencode 2 the id is `claude-code`, so `claude-code-default/<model>` fails while the plugin is perfectly healthy (measured on opencode 2.0.16, 2026-09-27). Declaring [`accounts`](#multiple-claude-code-accounts) is what creates `claude-code-default`. |
1529
1561
  | 400 `Third-party apps now draw from your extra usage…` | `/claude-code-doctor` for the account the conversation is on, then `claude auth status` for its plan | An account-level usage gate, not a plugin fault: extra usage is off, or the window is exhausted. Wait for the reset, or move to another configured account. This is one of the two error texts that open the [account failover](#account-failover) form, so with several accounts you get the form instead of the error. Enabling paid usage or changing authentication is a billing decision and nothing here makes it for you. |
1530
1562
  | `Tool result name changed`, and the turn aborts, on opencode 2 | The `plugin` version in `/claude-code-doctor` | Fixed in 0.28.1: a CLI-executed tool's result used to reach opencode under a different name than its call, and opencode 2.0.16 aborts the turn on that mismatch, which broke every Claude-side MCP server call. Upgrade, then **fully quit and relaunch every opencode window**: plugin code is read once at process start, so a new package in a running window changes nothing. |
1563
+ | A Claude Code hook you configured has no effect, and Claude never mentions it | The **Hooks Claude Code ran that failed** section of `/claude-code-doctor` | The hook exited non-zero, so Claude Code discarded its contribution and answered the turn anyway. The section gives the exit code and the hook's stderr. Fix or remove it in your own Claude Code settings; only `SessionStart` and `Setup` hooks are visible here, because the plugin does not pass `--include-hook-events`. |
1531
1564
  | `plugin ready` is missing from the log | That the log file is actually on, since it is off by default | If it is on and the line is still absent, the plugin never loaded. Check the package is in `plugin` (1.x) or `plugins` (2.x), that a local checkout points at `dist/` on 2.x, and that you relaunched rather than opened a new session. `/claude-code-doctor` answers the same questions without enabling logging. |
1532
1565
  | `Failed to authenticate: OAuth session expired`, one account, every turn failing in milliseconds | `CLAUDE_CONFIG_DIR=~/.claude-<name> claude auth status` for that account | Log it in again. The plugin writes a `▌ **claude account:**` note naming the account and the exact command, for example `CLAUDE_CONFIG_DIR=~/.claude-work claude auth login`, and offers the switch form when another account exists. Restart opencode afterwards: a switch made from that form lasts until opencode restarts. |
1533
1566
  | A tool call reported as rejected although it really ran | The `plugin` version in `/claude-code-doctor` | Upgrade to 0.26.2 or newer. Two separate causes, both fixed: opencode 1.18.32 aborts the provider signal of every step that ends in tool calls and the plugin read that as you pressing stop (0.26.1), and a call waiting on an unanswered permission prompt was rejected at the flat 10-minute deadline, after which your late approval cancelled Claude's next call (0.26.2). A deadline now waits while opencode reports the session busy, so an unanswered prompt is never a reason to raise `proxyToolTimeoutMs`. |