@khalilgharbaoui/opencode-claude-code-plugin 0.28.1 → 0.29.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.28.1",
3
+ "version": "0.29.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-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"
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-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"
25
25
  },
26
26
  "dependencies": {
27
27
  "@ai-sdk/provider": "^3.0.8",
@@ -7,10 +7,37 @@ description: Configure and troubleshoot the opencode-claude-code-plugin, the ope
7
7
 
8
8
  This plugin is `@khalilgharbaoui/opencode-claude-code-plugin`. It registers one or more
9
9
  `claude-code*` providers and routes inference through the `claude` CLI, not opencode's
10
- native Anthropic provider. Headless `--print` is the default. Subscription headless
11
- usage draws on Agent SDK credit/extra usage under Anthropic's billing policy, not a
12
- promise of free or normal interactive-plan usage. API-key/cloud billing depends on
13
- the CLI's authentication. Confirm the user's intended account and billing method.
10
+ native Anthropic provider. Headless `--print` is the default, which is the Agent SDK
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
15
+ authentication. Confirm the user's intended account and billing method.
16
+
17
+ ## How this differs from the alternatives
18
+
19
+ Three routes reach Claude from opencode, and they are not interchangeable:
20
+
21
+ 1. **opencode's native `anthropic` provider**: an Anthropic Platform API key in
22
+ opencode's auth store, pay as you go, everything running in opencode's own runtime.
23
+ 2. **This plugin**: the official `claude` CLI authenticates, so opencode inherits
24
+ whatever that CLI holds (subscription login, API key, Bedrock, Vertex). It never
25
+ reads, stores or replays a token, which is why it is a sanctioned route. What it
26
+ costs: a `claude` child per conversation, Claude Code's own context compaction and
27
+ clearing happening behind opencode's back, a locally synthesised session title
28
+ instead of a model-written one, `/compact` on its own short-lived spawn, no todo
29
+ panel on opencode 2, and opencode's native reasoning features bypassed by design.
30
+ 3. **Proxy or token-reuse plugins** (`opencode-with-claude`/Meridian,
31
+ `opencode-claude-auth`, `opencode-claude-plan`): they reuse the Claude OAuth session
32
+ outside the official client. Anthropic disallowed that for third-party use in
33
+ February 2026, and each of those projects says so in its own README (a "community
34
+ workaround", "no claims regarding compliance", "could be suspended or terminated").
35
+
36
+ Never recommend route 3, never help migrate a user onto it, and never enable
37
+ `interactive: true` to dodge a billing or access restriction. If a user asks why this
38
+ plugin is slower or heavier than a proxy, the answer is route 2's cost list above, plus
39
+ the tool mediation: proxied `Bash`/`Edit`/`Write`/`WebFetch`/`Task` execute in opencode
40
+ under its permissions instead of inside the CLI.
14
41
 
15
42
  This file ships with the package, so upgrading that package updates the bundled
16
43
  reference without a separate skill install. Do not copy it into a personal skill
@@ -83,6 +110,7 @@ Defaults below describe normal headless opencode use when the key is absent.
83
110
  | `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. |
84
111
  | `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. |
85
112
  | `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. |
113
+ | `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 `accountFailover` has no other account to offer; with 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, title stubs, `doGenerate` or the interactive transport. Writes a `▌ **model fallback:**` note that transcript rebuilds strip. Not independently read per expanded account. |
86
114
  | `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. |
87
115
  | `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. |
88
116
  | `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. |
@@ -296,6 +324,37 @@ the opencode schema requires it. Markdown fallback reads top-level scalar fields
296
324
  | `model` | Full `provider/model` pins bypass plugin model overrides, not the separate effort override. |
297
325
  | `forceModel` | Registered bare model id, preserving the caller's account even if an account suffix is supplied. Works for any discovered agent mode. |
298
326
  | `reasoningEffort` | `minimal`, `low`, `medium`, `high`, `xhigh`, `max`; invalid declarations warn and keep inherited effort. `minimal` maps to CLI `low`. Compaction skips this override. |
327
+ | `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. |
328
+
329
+ ### Degrade to another model instead of failing
330
+
331
+ ```yaml
332
+ forceModel: claude-opus-5
333
+ fallbackModels: [claude-sonnet-5, claude-haiku-4-5]
334
+ ```
335
+
336
+ Or as the default for every agent that declares none:
337
+
338
+ ```json
339
+ { "provider": { "claude-code": { "options": { "fallbackModels": ["claude-sonnet-5"] } } } }
340
+ ```
341
+
342
+ Per-agent replaces provider-level; it never merges. Unset means no chain, which is
343
+ the default. Two triggers only, never a generic error: the CLI refusing the model
344
+ (assistant `error: "model_not_found"`, or a failed result whose text is *"There's an
345
+ issue with the selected model"*, measured on CLI 2.1.280, where the result `subtype`
346
+ is misleadingly `success`), and a usage limit **only when `accountFailover` has no
347
+ other account to offer**. With another account configured the switch form wins and
348
+ the chain stays out of it: model is a capability choice, account is a billing choice.
349
+ An expired login, a billing hold and every other error kind are excluded because they
350
+ fail the same way on the next model.
351
+
352
+ On a trigger the failed process is killed, its session id dropped, a fresh process
353
+ spawns on the next model with the same account, effort and cwd, the conversation
354
+ replays, and a `▌ **model fallback:**` note names the failed model, the reason and the
355
+ serving model. The failed attempt's output is discarded entirely. Each model is tried
356
+ at most once per turn; an exhausted chain surfaces the original error unchanged.
357
+ Never on compaction turns, title stubs, `doGenerate`, or the interactive transport.
299
358
 
300
359
  ### Route a tool through opencode, or switch one off
301
360
 
@@ -532,7 +591,10 @@ includes NOTICE). Do not paste the entire log or raw spawn arguments.
532
591
 
533
592
  Fields: `plugin` (version actually loaded), `opencode`, `cwd.resolved` and `cwd.source`
534
593
  (`configured`, `process`, `captured`, `unresolved`), `providers`, `accounts`,
535
- `proxyTools`, `mcpServers`, `interactiveTransport`, `planModeQuestion`,
594
+ `proxyTools`, `mcpServers`, `permissionPresets` (one row per provider:
595
+ `{provider, preset, applied, overrides}`, `preset: "none"` where unset,
596
+ `applied: false` for `none` and for an unrecognised name, `overrides` naming the
597
+ options an applied preset replaced), `interactiveTransport`, `planModeQuestion`,
536
598
  `anthropicApiKeyInEnv`, `claudeCli.path` and `.version`
537
599
  (`not detected` means the binary did not answer `--version`, which also disables
538
600
  version-gated flags). Cwd is a startup fallback snapshot, not the per-session spawn
@@ -559,7 +621,10 @@ alone do not prove it patched. Never call `tools/call` or obtain the bearer to p
559
621
  `/claude-code-doctor` prints the same fields as the startup block plus live runtime
560
622
  state, in the chat, with no model inference and at zero tokens: plugin/opencode/CLI
561
623
  versions, cwd and its resolution tier, providers, accounts, `proxyTools`, disk MCP
562
- servers, transport, whether an `ANTHROPIC_API_KEY` is present (never its value), the
624
+ servers, the `permissionPreset` in force per provider (`provider: preset`, `none` where
625
+ unset, an unknown name marked `(unknown, nothing applied)`, plus a
626
+ **Permission preset overrides** block listing what an applied preset replaced),
627
+ transport, whether an `ANTHROPIC_API_KEY` is present (never its value), the
563
628
  live `claude` processes (opencode session, model, pid, in flight, age, effort), pending
564
629
  proxy calls with their deadlines, and one unauthenticated `initialize` against each
565
630
  proxy URL (`401, good`; anything else is flagged unsafe). Prefer it over asking for
@@ -583,10 +648,29 @@ commands are preserved. Do not use it as an automatic diagnostic probe.
583
648
 
584
649
  ## Troubleshooting
585
650
 
651
+ Key a diagnosis on the FIRST symptom the user reports, and name the ONE check that
652
+ settles it before proposing a fix. The four checks, in order of preference:
653
+ `/claude-code-doctor` in the session (no inference, no billing, reports the loaded
654
+ plugin version, the CLI path and version, providers, accounts, `proxyTools`, cwd and its
655
+ tier, live `claude` children and pending proxy calls); `OPENCODE_CLAUDE_CODE_LOG_FILE=1`
656
+ plus a grep of `plugin.log` for the named line; `claude --version` for a version gate;
657
+ and `claude auth status` (with `CLAUDE_CONFIG_DIR` for a named account) for a login.
658
+ Prefer the doctor: it needs no logging change and no restart.
659
+
586
660
  | Symptom | Cause | Fix |
587
661
  |---|---|---|
588
662
  | A config change did nothing | Options are read at startup; another opencode window is still running the old process | Fully quit every opencode window and relaunch |
589
663
  | New plugin version or model not in the picker after upgrading | Frozen `@latest` in opencode's package cache | Remove the cache dir (recipe "Upgrade the plugin") and relaunch |
664
+ | No `claude-code` provider or model in the picker at all | The plugin never loaded, or it loaded and the CLI was not usable | Check for a `plugin ready` line first: absent means not loaded (wrong `plugin`/`plugins` key, a 2.x local install not pointing at `dist/`, or no full relaunch), present with `claudeCli.version: not detected` means the binary did not answer `--version`, which also disables every version-gated flag |
665
+ | `Model unavailable` for a model id the user typed | The provider id is not what they assumed | Use the id the ready block's `providers` field lists. With no `accounts` configured on opencode 2 the id is `claude-code`, so `claude-code-default/<model>` fails while the plugin is healthy (measured on opencode 2.0.16, 2026-09-27). Declaring `accounts` is what creates `claude-code-default`; on 1.x with accounts the ids are `claude-code-default` / `claude-code-<name>` and never a bare `claude-code` |
666
+ | `Tool result name changed`, turn aborts, on opencode 2 | Before 0.28.1 a CLI-executed tool's result reached opencode under a different name than its call, and 2.0.16 aborts the turn on the mismatch, breaking every Claude-side MCP server call | Upgrade to 0.28.1+ **and fully relaunch every opencode window**; plugin code is read once at process start, so upgrading the package under a running window changes nothing |
667
+ | `plugin ready` missing from the log | Logging is off (the default), or the plugin genuinely did not load | Confirm `OPENCODE_CLAUDE_CODE_LOG_FILE=1` and a relaunch before concluding anything. `/claude-code-doctor` answers the same questions with no logging change |
668
+ | "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 |
669
+ | 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 |
670
+ | `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" |
671
+ | 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 |
672
+ | `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 |
673
+ | `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 |
590
674
  | `/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+ |
591
675
  | Model calls `Skill("x")` and gets `Unknown skill` | Wrong namespace (`opencode-skills:x`), a CLI without `--plugin-dir`, a compaction turn, or `bridgeOpencodeSkills: false` | Check the namespace and `claude --help`; remove the `false` only with approval |
592
676
  | One skill's name and description appear twice in a session | Plugin predates `bridgeSkipNativeSkills`, or it is set to `false` | Upgrade, or drop the `false` |
@@ -616,6 +700,32 @@ commands are preserved. Do not use it as an automatic diagnostic probe.
616
700
  | 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 |
617
701
  | 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 |
618
702
 
703
+ ## Which login bills what
704
+
705
+ The CLI decides; the plugin only reports. Never state which of these is in effect from
706
+ `process.env` alone, and never read or print a key's value.
707
+
708
+ - **OAuth subscription login** (`claude auth login`): turns run on the user's plan.
709
+ Headless `--print` is the Agent SDK path; see the note at the top of this file before
710
+ telling a user what that draws from. The interactive transport draws from the same
711
+ plan usage limits, so it is not a way to change what a turn costs.
712
+ - **`ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` in the launching environment**: the CLI
713
+ prefers these over the subscription login and bills Platform pay-as-you-go. This is the
714
+ only route `ignoreAnthropicApiKey: true` strips, and the plugin warns at startup
715
+ whenever either is present regardless of the flag.
716
+ - **A key the CLI found itself**, from its own `user`, `project` or `org` settings scopes
717
+ or an `apiKeyHelper`. That is CLI configuration, so no plugin option removes it. Do not
718
+ claim `ignoreAnthropicApiKey` fixes it.
719
+ - **`apiKeySource` on the CLI's `system` init event is the field that tells the truth**:
720
+ anything other than `oauth` (the subscription) or `none` means a key is in effect, and
721
+ the plugin warns once per process. An absent env var proves nothing.
722
+ - **Bedrock and Vertex**: if the CLI authenticates against either, neither a subscription
723
+ nor an Anthropic key is in play for that turn, and fast mode is excluded there (and on
724
+ Foundry), because it is first-party only.
725
+
726
+ Changing any of this is the user's decision: explain the consequence and get approval
727
+ before stripping a key, switching accounts, enabling usage credits or changing transport.
728
+
619
729
  ## Do not
620
730
 
621
731
  - Do not enable `planModeQuestion` or `"Question"` without the user asking. `"Question"`