@khalilgharbaoui/opencode-claude-code-plugin 0.55.0 → 0.57.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.55.0",
3
+ "version": "0.57.0",
4
4
  "description": "Claude Code CLI provider plugin for opencode",
5
5
  "homepage": "https://opencode-claude-code-plugin.dev/",
6
6
  "funding": {
@@ -9,9 +9,10 @@ This plugin is `@khalilgharbaoui/opencode-claude-code-plugin`. It registers one
9
9
  `claude-code*` providers and routes inference through the `claude` CLI, not opencode's
10
10
  native Anthropic provider. Headless `--print` is the default, which is the Agent SDK
11
11
  path. Never promise a user what subscription headless usage draws from: Anthropic's
12
- policy page is the only authority and it changes (fetched 2026-09-27 it carries a June 15
13
- update pausing the announced Agent SDK credit, so that usage still draws from plan usage
14
- limits and the monthly credit is unavailable). API-key/cloud billing depends on the CLI's
12
+ policy page is the only authority and it changes (read 2026-10-16: subscription usage
13
+ limits by default, and since 2026-10-07 Max and Team plans also carry monthly API credits,
14
+ Max 20x $200, spendable through `claude -p` by giving the CLI an API key from the linked
15
+ Console organization; Pro has none). API-key/cloud billing depends on the CLI's
15
16
  authentication. Confirm the user's intended account and billing method.
16
17
 
17
18
  ## How this differs from the alternatives
@@ -121,6 +122,11 @@ Defaults below describe normal headless opencode use when the key is absent.
121
122
  | `subagentDispatchCrossGroup` | boolean | `false` | Let `subagentDispatch`'s account row offer accounts outside the dispatching account's own `accountGroups` group. **Off by default and never implied by anything else**: a subagent is handed the task text the main agent writes, which can quote the conversation, and it reads the repository, so offering another group is offering that group the conversation by another route. Does nothing when `accountGroups` is unset, where every account is already one group, or when `subagentDispatch` is off. |
122
123
  | `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. |
123
124
  | `defaultSubagentCacheTtl` | string | unset | Prompt cache TTL (`5m` / `1h`) for discovered `mode: subagent` agents that declare no `cacheTtl`; the agent's own value takes precedence. Unset leaves the CLI's default (1 hour on a subscription). Unknown values warn and change nothing. Headless and interactive spawns, never compaction. |
125
+ | `defaultSubagentMaxTurns` | number | unset | `--max-turns` for discovered `mode: subagent` agents that declare no `maxTurns`; the agent's own value wins. Counted by the CLI per user message, so safe on a reused process. CLI 2.1.280+; headless only (print-only flag, the interactive transport WARNs once). Part of the session key. |
126
+ | `defaultSubagentMaxBudgetUsd` | number | unset | `--max-budget-usd` for discovered `mode: subagent` agents that declare no `maxBudgetUsd`. Counted by the CLI over the whole process's life and reset on `--resume`, hence subagents only; a main agent's `maxBudgetUsd` is refused with a WARN. Probe-gated on `claude --help`; headless only. |
127
+ | `maxBudgetUsd` | number | unset | Dollar cap per opencode conversation (its turns plus every descendant subagent), from the CLI's `total_cost_usd` (cumulative per Claude session on headless, so the ledger takes deltas). Unset: no ledger at all. Warn notes at `budgetWarnAt`; at the cap the next fresh turn ends before any spawn with a `▌ **budget:**` note and `{unified:"error", raw:"budget_exceeded"}`. Not on a turn answering a parked proxy call, not on compaction; a child gets an `error` part instead of the note; `doGenerate` gets the finish only. In memory; raising it needs an opencode relaunch. |
128
+ | `budgetWarnAt` | number[] | `[50, 80]` | Percent thresholds of `maxBudgetUsd`, one note each per conversation (only the highest when a turn jumps several). `[]` drops the notes, keeps the stop. Invalid values WARN and keep the default. |
129
+ | `maxBackgroundSubagents` | number | unset | Ceiling on background subagents running at once (the doctor's running count, process-wide). A background `task`, or a `task_batch` with background items, that would exceed it is refused whole with an `isError` tool result naming the cap and the count; nothing reaches opencode. Foreground is never capped. An unreadable count (2 s budget) refuses. |
124
130
  | `fallbackModels` | string[] | unset | Ordered models to try when the model a turn would run on is refused. Default for agents declaring no `fallbackModels`; a per-agent list replaces it rather than extending it. Same account throughout, never a switch. Armed only by the CLI refusing the model (`model_not_found`) or by a usage limit when the `accountFailover` form is not taking the turn, which is the case whenever it is `"off"` (its default) or has no other account to offer; with `"ask"` and another account the switch form wins. Entries must be registered model ids, unknown ones warn and are skipped, the current model is dropped from its own chain, each entry is tried at most once per turn, and an exhausted chain surfaces the original error. Never on compaction or title stubs; both transports. Writes a `▌ **model fallback:**` note that transcript rebuilds strip. Not independently read per expanded account. |
125
131
  | `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. |
126
132
  | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to Claude on both transports (the TUI also gets Claude Code's own `skipDangerousModePermissionPrompt` so it does not stop on that mode's confirmation; CLI 2.1.263+), 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. |
@@ -131,6 +137,7 @@ Defaults below describe normal headless opencode use when the key is absent.
131
137
  | `controlRequestDenyMessage` | string | built-in text | Override ordinary deny text. `AskUserQuestion` always uses its own stop-and-wait message. |
132
138
  | `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Case-insensitive replacement list, not additive and not a capability allowlist. Known entries expose `mcp__opencode_proxy__<name>`; omitted/unknown tools are not disabled. `Task` also brings `task_batch`; `[]` disables this list, not MCP proxying. See the proxy table for exceptions. |
133
139
  | `extraDisallowedTools` | string[] | unset | Claude built-ins to switch off outright with `--disallowedTools`, for tools that have no proxy (`["NotebookEdit"]`). Removes the capability rather than routing it. |
140
+ | `mapReadRules` | boolean | `true` | Carry opencode's own `read` permission **deny** rules onto the Read the CLI runs itself, as `--disallowedTools "Read(<pattern>)"`. `Read`, `Glob` and `Grep` are CLI-native by default, so without this a file opencode would refuse to read is read by Claude with no prompt (measured 2026-10-16: "read .env with your Read tool" returned a planted secret). Only `deny` is carried: an `ask` cannot be honoured on a spawn that has nobody to ask, and **opencode's own `*.env` default is an `ask`, not a deny**, so a default install maps nothing. Write `permission: { read: { "*.env": "deny" } }` to close that, or add `Read` to `proxyTools` so the read runs in opencode and its prompt applies. `glob`, `grep`, `list` and `external_directory` rules are not mapped (Claude's deny beats its allow, so "outside the working directory" cannot be said without denying the working directory); nor are agent-level `permission.read` overrides. `/claude-code-doctor` lists what was mapped, what was not, and the asks it cannot enforce. Compaction spawns get no rules. Set `false` to send nothing. |
134
141
  | `proxyToolTimeoutMs` | object of proxy tool name to ms | unset | Optional wall-clock backstop per tool, in ms, case-insensitive keys. A proxied call normally ends on an event the plugin listens for, not on a timer: opencode's result, an abort (the CLI is interrupted), the next user message (calls the previous turn left pending are rejected as orphaned), the `claude` process exiting, the chat being deleted, or opencode exiting. Fallback 10 min (including dynamic MCP tools); `task` and `task_batch` have no deadline, so a subagent runs to completion and a chat parked in one holds its worker until one of those events; `question` 30 min. Set both task keys to cover both. A positive value replaces the default, `0` removes that tool's deadline, negative or non-numeric values are ignored, and values above 2147483647 are clamped. Bash `input.timeout` raises the resolved deadline (and restores one after `bash: 0`); executor ceilings still apply. The generated MCP client timeout is the largest effective deadline, or the CLI's maximum while any tool has none. `compress` is intercepted without a deadline. |
135
142
  | `planModeQuestion` | boolean | `false` | Bridge `ExitPlanMode` approval to opencode's `question` and return a real CLI tool result. Requires a live question registry entry; otherwise keeps text fallback. Cannot fire on the headless transport: CLI 2.1.258 does not offer `ExitPlanMode` under `--print`, measured directly and through a full plugin probe, so the text path is what runs. On the interactive transport it fires and is verified live (1.18.34): the form's answer is pressed into the TUI's approval dialog. Headless prose yes/no is not a plan-mode unlock; interactive typed yes is. |
136
143
  | `webSearch` | `"claude"` / `"disabled"` / `"<opencode tool name>"` | `"claude"` | Default: CLI search with the query rendered as text. Custom target forwards a tool call to an existing opencode tool accepting `query`; this is mapping, not the authenticated proxy replacement, so do not assume CLI search is suppressed. `"disabled"` disallows headless `WebSearch`. |
@@ -153,6 +160,8 @@ Defaults below describe normal headless opencode use when the key is absent.
153
160
  | `resumeAfterRestart` | boolean | `true` | After an opencode restart, resume the conversation's Claude session (`--resume`) instead of replaying the thread as text. Persists session id + conversation digest per session key in `$XDG_STATE_HOME/opencode-claude-code-plugin/claude-sessions.json` (0600, 256 entries, 30 days) after each successful turn. Resumes only on the same session key and binary, with the transcript on disk and the history equal to the recorded conversation plus Claude's reply; anything else (edit, revert, compaction, account switch, open tool round trip) replays. Not on compaction. Log: `resuming the claude session from before the restart` (NOTICE) or `not resuming ...` with a reason (INFO). `false` disables reading and writing. |
154
161
  | `resumeAcrossModelChanges` | boolean | `true` | When the operator changes a conversation's model or reasoning effort mid-conversation, continue the same Claude session (`--resume <id> --model <new>`) instead of replaying the thread as text. Both are in the session key, so changing either used to start a fresh Claude conversation (issue #91); neither needs one, because `--model` and `CLAUDE_CODE_EFFORT_LEVEL` are spawn-time and the CLI applies either to a resumed transcript (measured on 2.1.288). The session is MOVED off the old key, so one Claude conversation has exactly one owner. Refused on every term `resumeAfterRestart` is refused on (another account, a transcript that is gone, a conversation that changed) plus two more: a sibling whose transcript may still be written to, and anything that is not the same cwd, request scope, opencode session, agent and prompt-cache TTL. Not on compaction, not on an account-failover switch. It reads the record `resumeAfterRestart` writes, so `resumeAfterRestart: false` turns it off too. Log: `continuing this conversation's claude session under the new model or effort` (NOTICE). |
155
162
  | `crossAccountResume` | boolean | `true` | On any account switch (a by-hand pick of another account's model in the same session, the switch form, and the switch back when the override expires), and only between accounts in the same `accountGroups` group, continue the same Claude conversation on the other account instead of replaying the thread as text. An account is a `CLAUDE_CONFIG_DIR` and a conversation is one `projects/<encoded cwd>/<id>.jsonl` inside it, so the switch copies that file into the target account's own projects dir (0600, dir 0700) and keeps the session id for the `--resume` both transports already build. Measured on CLI 2.1.288: a copied transcript resumes under another config dir with its context intact, the CLI resolves a session by FILENAME (not the `sessionId` in the records), it appends to the copy it found rather than to the cwd's path, the source comes out byte-identical, and no record holds a user, org or account id. Never overwrites: an older copy already at the target path (what a switch back finds) makes the carry take a fresh session id, and the live copy is the longest one. Refuses on nothing to carry, a non-file at the target path, or a failed copy, and each refusal falls back to the replay. Not on compaction, and inert with one account. Log: `carried this conversation's claude transcript to the other account` or `replaying this conversation as text on the other account` with a reason (both NOTICE). What is NOT verified live is a different Anthropic login answering another's conversation; `false` keeps the replay. |
163
+ | `usageWarnThresholds` | number[] | `[50, 75, 90, 95]` | Percent levels of a usage window at which a SERVED turn ends with one `▌ **usage:**` note (its own text part, stripped from rebuilt transcripts): account, window, utilization, reset in local time. Read from every headless `rate_limit_event` (not only a rejected one), whose `utilization` is a FRACTION (`0.9` is 90%) and is ABSENT on an `allowed` event with headroom, which is unknown, never zero, and crosses nothing. Readings persist per account per window in `$XDG_STATE_HOME/opencode-claude-code-plugin/account-usage.json` (`0600`, names, numbers and times only), and so does the dedup: each threshold is said once per account per window across reused processes, sessions and restarts, again only after the window's reset. A crossing in a `/compact` turn or a child session is not said there and is said by the next ordinary turn on the account. Never on `doGenerate`, never turns a served turn into a failure. `[]` turns the notes off but keeps the readings. The single-account name is `default`. Interactive transport: no event, so only a limit reply is recorded (`limited`). Log: `claude account usage crossed a warning threshold` (NOTICE). |
164
+ | `usageSwitchAt` | number / `false` | `90` | Percent at which work leaves an account BEFORE the wall, when another `failoverCandidates` account (same `accountGroups` group) exists and is not itself at that level per the store. `accountFailover: "auto"`: sets the existing account override (keyed on the account, until the window's reset; in memory, lost on restart) so the NEXT turn spawns on the other account, carried like a limit switch; the note says `New turns move to the "<account>" account until HH:MM`. `"ask"` / `"off"`: nothing moves and no form (the form only ends a FAILED turn); the note names the other account to pick a model from. The turn that crossed finishes as served. Also a threshold in its own right, so the switch is said exactly once per window. `false` or `0` never. Log: `claude account is near its usage limit; new turns move to another account until the window resets` (WARN). |
156
165
  | `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. |
157
166
  | `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. |
158
167
  | `transport` | `"auto"` / `"headless"` / `"interactive"` | unset (headless) | Explicit choice wins over `interactive` and its env var. `auto` prefers headless and probes required headless flags with free help/argument parsing and no prompt. Only definitive unsupported flags select the PTY; timeout, auth failure, missing binary and unknown output stay headless. Never retries a submitted request. Explicit or automatic PTY selection requires `Bun.Terminal`, otherwise errors. Title stubs do not probe. `/compact` follows the selected transport. On the PTY, `read-only` is `--restricted` plus `dontAsk` (CLI 2.1.263+, refused below rather than weakened) and plan mode parks `ExitPlanMode`'s dialog for the operator. `/claude-code-doctor usage` reports the free headless check unavailable when those flags are absent; it does not run a PTY inference prompt. |
@@ -397,6 +406,33 @@ comes from `organizationType` plus `organizationRateLimitTier` in
397
406
  ever displayed or logged. Unreadable or unrecognised means no suffix. There is no
398
407
  option for it.
399
408
 
409
+ ### Usage before the limit
410
+
411
+ Every headless `rate_limit_event` is kept as a reading per account per window, so the
412
+ operator hears about a window filling up before it runs out (`usageWarnThresholds`,
413
+ `usageSwitchAt` in the table above):
414
+
415
+ ```text
416
+ ▌ **usage:** the Claude account "work" has used 76% of the 5-hour window, which resets
417
+ at 2026-10-17 18:00.
418
+ ```
419
+
420
+ - One note per served turn whose account has an unannounced crossing, each threshold
421
+ once per account per window, persisted, so neither a reused process nor a restart
422
+ repeats it. Not on compaction, `doGenerate` or a child session.
423
+ - At `usageSwitchAt` with `"auto"` and an eligible other account, new turns move there
424
+ until the reset and the note says so; with `"ask"`/`"off"` the note names the account
425
+ to pick a model from. No form after a served turn.
426
+ - The CLI reports only when its view changes, so a reused process can be silent for
427
+ several turns; the last reading stands until a newer one or its reset.
428
+ - Where to look: the TUI sidebar's `Accounts` lines, one per configured account from
429
+ the first draw (`work · 5h 76% · resets 18:00`, `work · 5h 76% · 2026-10-18 01:20`
430
+ for a reset on another day, `ok` for a reading without a number, `limited` for a
431
+ refusal, `home · Max 20x · no reading yet` before an account's first turn reports,
432
+ the reset time in its own colour apart from the level) and the doctor's
433
+ `Account usage` table, which also shows which thresholds were already noted. "No
434
+ reading yet" with a recent turn usually means the interactive transport.
435
+
400
436
  ### When an account runs out of usage
401
437
 
402
438
  By default (`accountFailover` unset or `"off"`) a usage-limited turn ends with one
@@ -645,6 +681,8 @@ the opencode schema requires it. Markdown fallback reads top-level scalar fields
645
681
  | `forceModel` | Registered bare model id, preserving the caller's account even if an account suffix is supplied. Works for any discovered agent mode. |
646
682
  | `reasoningEffort` | `minimal`, `low`, `medium`, `high`, `xhigh`, `max`; invalid declarations warn and keep inherited effort. `minimal` maps to CLI `low`. Compaction skips this override. |
647
683
  | `cacheTtl` | `5m` or `1h`; anything else warns and leaves the CLI's default alone. Exported as `CLAUDE_CODE_PROMPT_CACHE_TTL`, beating a shell export of the same name. Works for any discovered agent mode; compaction skips it. |
684
+ | `maxTurns` | Positive whole number, passed as `--max-turns` (per user message, CLI 2.1.280+). Any agent mode; headless only; part of the session key. |
685
+ | `maxBudgetUsd` | Positive dollars, passed as `--max-budget-usd` (per process life, reset on resume). Discovered `mode: subagent` agents only; on a main agent it is refused with a WARN. Headless only. |
648
686
  | `fallbackModels` | Ordered registered bare model ids to try when this agent's model is refused. Both YAML spellings (`[a, b]` or a `- ` block). Replaces the provider-level `fallbackModels` rather than extending it. Keeps the caller's account; an entry carrying `@account` has it stripped. Unknown ids warn and are skipped. |
649
687
 
650
688
  ### Ask before each subagent dispatch
@@ -944,6 +982,20 @@ the theme's warning colour, faded on a finished row). That line is what
944
982
  the plugin really spawned, read from `$XDG_STATE_HOME/opencode-claude-code-plugin/session-spawns.json`,
945
983
  so `forceModel`, an agent's `reasoningEffort`, a dispatch-form answer, the fallback chain
946
984
  and a failover all show there, while opencode's own footer shows the model it asked for.
985
+ The model line ends with the subagent's spend so far (`· $0.42`, opencode's own session cost,
986
+ kept after it finishes; on a tight line the dot goes before the number does), and a running
987
+ row gets a third line: the same progress line the viewed session gets above the section
988
+ under a `Main Progress` heading while its turn runs (and 15 s after). That line is a ten-cell
989
+ `▰▱` bar, two spaces, then the todo list's completed fraction with `2/4` when the session has
990
+ one (opencode 1 only; opencode 2 exposes no todo list to a TUI plugin), else the turn's
991
+ elapsed over its estimated total (`1m02s/~2m30s`: the weighted median of what remained of
992
+ this agent and model's recorded turns that lasted longer than this one so far, weighted
993
+ toward turns with a prompt of about this length at this effort, read from
994
+ `$XDG_STATE_HOME/opencode-claude-code-plugin/turn-history.json`, which the plugin writes at
995
+ every served result; full in the warning colour once past every recorded turn), else no bar
996
+ until three turns of that agent and model are on record; plus the call count (when neither
997
+ is shown) and the running tool. No model call, no option, nothing read from the prompt but
998
+ its length.
947
999
  Clicking a row opens the subagent's session; `up` returns to the parent. More than two
948
1000
  rows fold behind a `▼` heading. Setup: nothing on opencode 2; on opencode 1 the
949
1001
  `tui.json` line under Minimum install. If the section never appears on 1.x, that line is
@@ -1339,8 +1391,12 @@ Prefer the doctor: it needs no logging change and no restart.
1339
1391
  | On a turn that replayed the thread as text, Claude knows what tools the previous reply ran but not what it concluded | Plugin 0.49.0 or older kept each replayed message's first 2,000 characters, and Claude Code runs its own tools without ending a step, so a reply's tool calls and their output (one result alone is capped at 10,000 characters) sit in the same message ahead of the prose and the answer was cut | Upgrade. Each replayed message is clipped in the middle now, keeping the answer it ends on, and the newest reply gets a larger budget. Better still, avoid the replay: it only happens when the Claude session cannot be resumed, and the `▌` note or the plugin log names the reason |
1340
1392
  | 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 |
1341
1393
  | After switching opencode agents (plan to build, build to plan) Claude ignores what was said under the other agent or answers an earlier message | Plugin older than 0.55.0: each agent runs its own `claude` process, and a reused one was sent only the newest message | Upgrade. The reused (or `--resume`d) process is now told what it missed, replayed as text ahead of the current message under `<missed_conversation>`; the log says `replaying the messages this claude session missed`. The processes stay separate on purpose (a Claude session keeps the system prompt of its first request) |
1394
+ | After an `/undo`, an edited message or a regenerated reply, Claude still acts on the turns that were removed | A `claude` session cannot have messages taken back out of its context, so the reused process still held them | Upgrade. The Claude session is dropped and the thread as it stands now is replayed into a fresh one, under a `▌ **conversation rewound:**` note; the log says `replaying the conversation as text: the thread was edited or undone` with `reason: history-rewound`. Not recognised when nothing of the old thread survives in place (an undo back to the first message, an edit of it), because that is also the shape of an opencode compaction, which must keep its process |
1342
1395
  | `prompt is too long` on the first turn after a restart or after a lost Claude session, and `/compact` fails the same way | Plugin older than 0.55.0: the text replay of the thread had per-message clips but no total | Upgrade. The replay is capped at 180,000 characters, newest first, with a `[N earlier messages omitted ...]` line when it had to drop |
1343
1396
  | opencode 2: every turn fails with `ENOENT ... posix_spawn '<claude>'` though the binary exists, only in a subdirectory of a project | Plugin older than 0.55.0 appended the session's project-relative `subpath` to its own absolute directory | Upgrade; drop a `cwd` set only to work around it |
1397
+ | Claude read a `.env` you thought opencode's `read` rules protected | opencode's own default for `*.env` is `ask`, not deny, and an `ask` cannot be carried onto a spawn with nobody to ask, so `mapReadRules` emits nothing for it. Check `/claude-code-doctor`'s read-rules section | Write `permission: { read: { "*.env": "deny", "*.env.*": "deny" } }` in opencode.json, which `mapReadRules` then carries onto Claude's own Read, or add `Read` to `proxyTools` so opencode's prompt governs it |
1398
+ | Claude read a file an opencode `read` **deny** rule covers | A deny is carried across by default. The doctor's read-rules section names an unmapped pattern (one with `..`) and the allows a deny now shadows; a `*` mid-path is narrower on Claude's side than in opencode | Reword the pattern (a trailing `/*` and a slash-free `*.env` both map exactly), proxy `Read`, or use `permissionPreset: "read-only"` |
1399
+ | A Claude Code hook on `Bash`, `Edit` or `Write` no longer fires | The tool is proxied, so Claude sees it as `mcp__opencode_proxy__<tool>` and the matcher misses | Name the proxied tool in the matcher (`mcp__opencode_proxy__bash`); hooks on CLI-native tools (`Read`) still fire |
1344
1400
  | Wanting the per-turn cost in the chat | Not shown by default | Set `turnStats: true` and restart opencode |
1345
1401
  | 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 |
1346
1402
  | 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/` |