Repository navigation
Open
Description
Summary
OpenCode Desktop exposes the "Custom OpenAI-compatible provider" form in Settings → Providers, but the save handler unconditionally throws provider.custom.unavailable, so the flow can never succeed on any server — including the app's own bundled local server.
Environment
- opencode version: Desktop 2.0.14 (bundled
opencode-cli v2.0.14, dpkgopencode 2.0.14) - OS: Ubuntu 26.04.1 LTS, Linux 7.0.0-31-generic, x86_64
- Terminal: Unavailable: this is the Desktop (Electron) UI, no terminal involved
- Shell: /bin/bash
- Install/channel: release
.debdownloaded from the Desktop section of https://opencode.ai/v2/docs - Active plugins: none found in config
Reproduction
- Install
opencode-desktop-linux-amd64.deb(Desktop 2.0.14) and launch OpenCode Desktop. - Open Settings → Providers.
- Select the custom provider entry ("Custom OpenAI-compatible provider").
- Fill in valid-looking values, e.g. provider ID
local, nameMy Local Server, baseURLhttp://localhost:8080/v1, model IDmy-model. - Click Save/Connect.
Expected Behavior
The provider is written to the user config (providers.<id>) and shows up in the model picker — the same result as adding the block to opencode.json manually.
Actual Behavior
Save always fails with the toast: "Custom providers are unavailable on this server" (i18n key provider.custom.unavailable).
The shipped app.asar build shows the mutation is a stub with no server or protocol condition:
mutationFn: async e => {
throw Error(a.t(`provider.custom.unavailable`))
}It throws for every server, so the dialog is reachable but unusable.
Additional Context
- Not a server/config problem: adding the provider manually to
~/.config/opencode/opencode.jsonloads it correctly on the very server the app spawns —GET /api/provideron the bundled server returns{"id":"local","name":"My Local Server","activation":"enabled", ...}. The failure is isolated to the dialog's save path. - Frequency: 100% reproducible (unconditional code path).
- Current source appears to rework this: the same handler is gated with
if ((await serverSDK().protocol) !== "v1") throw ...and the settings entry is wrapped in<Show when={protocol() === "v1"}>, so it looks like the shipped 2.0.14 build predates that change. - Suggestion: either ship the save implementation in the build, or hide the entry point until it works — the UI currently offers an action that can never succeed.
Reacted by Aravya Singh, AI Agent Tip Line, QiAn0107, Matthias Weigand, Tim Steenwyk, Luca, Alex, Stephen Fluin, Alexandre Alapetite, Nico Hörter and 5 more
Reacted by AI Agent Tip Line and Matt Hill
Metadata
Metadata
Assignees
Labels
No labels
Activity
techysy commentedon Sep 23, 2026
Additional technical detail (root cause analysis)
Confirming and extending #50650 from the
opencode serve/ web-UI side (not just Desktop).Environment:
opencode2.0.12,opencode serve+ browser UI. Same unconditional throw.1. The stub is not protocol-independent in
devOn the 2.0.12 release the handler is untouched:
On
devit has been partially implemented, but is gated behind a v1-only server check(
packages/app/src/components/dialog-custom-provider.tsx:134):And
packages/app/src/utils/server-protocol.tsprobes for the legacy endpoint:So the dialog can only ever work against a v1 server. That is consistent with the TODO, but it
means the feature is not merely "not implemented yet" — it is actively gated off for v2 servers.
2. The write APIs genuinely do not exist on a v2 server (verified)
I enumerated every registered route in
packages/server/src/handlers/for 2.0.12:all of them are
.get(), except one.put()on a session handler. There is no config write,and no
auth.set.Probing a live
opencode serve2.0.12 instance:So the
TODOcomment is accurate — there is no client-side workaround. This matches thereporter of #50650 observing that the app's own source references a
serverSDK().client.auth.set(...)that has no counterpart on the server.
3. There is also no
opencode configwrite command as a fallbackopencode configonly exposeslist/path.opencode config set …falls through to the defaulthandler and just launches the TUI. So neither the UI nor the CLI can persist a custom provider.
4. Workaround that does work
Hand-editing the global config is unaffected — the engine loads it correctly on the same server
where the dialog throws. Two substitutions are supported (
packages/core/src/config/variable.ts):{env:VAR}reads from the process env,{file:path}from a file (supports~/) — both are usefulfor keeping the key out of the config file.
This is what the reporter meant by "works when added manually" — but it is worth stating explicitly
that this is the only path today, because the protocol gate in
devmeans even a future v2implementation must add the missing server endpoints first.
Suggested split
/api/config,/api/auth).devimplementation can drop the!== "v1"gate.Until the server side exists, the UI should probably hide the custom-provider entry rather than
present a form that cannot be submitted — showing it implies the feature is available.
jpike88 commentedon Sep 23, 2026
welp. opencode is too unstable to use. moving on
CengizKuscu commentedon Sep 24, 2026
Additional reproduction on v2.0.16 (macOS Desktop, Apple Silicon), plus a source-level note on the gated handler.
1. Still broken in 2.0.16 — not just an old build
The shipped desktop bundle still contains the unconditional stub. Extracted from the running server's own web assets (
/_assets/dialog-CdLYz1aX.js):No server or protocol condition. Every Submit fails with
Custom providers are unavailable on this server, exactly as reported. So the "shipped build predates the fix" theory does not fully explain 2.0.16.2. The
protocol === "v1"gate can never pass on the bundled serverThe current
devsource gates the save on the detected server protocol:detectServerProtocol(packages/app/src/utils/server-protocol.ts) probes/global/healthand/api/healthand requires a JSON body before returning"v1", otherwise it defaults to"v2".On the desktop app's own bundled server (
OPENCODE_CLIENT=desktop, v2.0.16) those probes do not return JSON:GET /global/health200butcontent-type: text/html→ HTML catch-all page, sovalueis not an objectGET /api/health404→ rejected by!response.okGET /api/info200 application/json→ works, but is not one of the probed pathsdetectServerProtocoltherefore falls through toreturn "v2". Combined with the!== "v1"check, the custom-provider dialog can never succeed on the app's own bundled v2 server — even once the save implementation actually ships. Thev1-only gate looks like a leftover from the compatibility layer rather than an intentional product decision, since the dialog's own copy points users at the OpenAI-compatible provider docs and the equivalent manualproviders.<id>config works fine on that same server.3. Workaround (config file)
Adding the block to
~/.config/opencode/opencode.jsonworks correctly on the bundled server, including model limits and capabilities. Verified end-to-end withopencode run:{ "$schema": "https://opencode.ai/config.json", "providers": { "myprovider": { "name": "My Provider", "env": ["MYPROVIDER_API_KEY"], "package": "@opencode/ai/providers/openai-compatible", "settings": { "baseURL": "http://localhost:20128/v1", "apiKey": "sk-..." }, "models": { "my-model": { "modelID": "upstream/model-id", "name": "My Model", "limit": { "context": 200000, "output": 64000 }, "capabilities": { "tools": true, "input": ["text", "image"], "output": ["text"] } } } } } }Suggest either shipping the save implementation without the
v1gate, or hiding the entry point (thedialog.provider.custom.labelentry) on v2 servers so the UI does not offer an action that cannot succeed.Environment
2.0.16(bundled CLIv2.0.16), macOS Apple Silicon (Darwin 27.0.0)/bin/zshAI-Agent-Tip-Line commentedon Sep 27, 2026
Experiencing the same issue. Very frustrating!
alexisvalentino commentedon Oct 3, 2026
This issue still persists even after 2 weeks! Any updates?
Mechful commentedon Oct 4, 2026
Any updates about this problem?
EverythingsComputer commentedon Oct 8, 2026
yes this issue remains active in the latest version. I have rolled back to a previous month's version due to this - here's to hoping the devs give it some focus soon!
davispuh commentedon Oct 12, 2026
Same issue with
opencode v2.0.25alexmocanu commentedon Oct 15, 2026
Same issue with 2.0.29
prigacv60 commentedon Oct 17, 2026
Still present on the latest release, 2.0.32 (Desktop on Windows 10.0.26100 x64; the bundled CLI and the local server both report 2.0.32).
Filling the custom provider form in completely (provider ID, name, API base URL, model id) and submitting still fails with the same toast: "Request failed / Custom providers are unavailable on this server" (
provider.custom.unavailable).Two extra data points that may help whoever picks this up:
out/renderer/assets/dialog-DcarZ3_2.jsthe custom entry ($ = "_custom") renders formDe, and its onlymutationFnunconditionally doesthrow Error(r.t(`provider.custom.unavailable`))— there is no capability or version check anywhere near it.GET /api/configis read-only, andPATCH /api/experimental/configaccepts a single field,shell(Config.Patchschema). No endpoint for writing providers or credentials exists.Workaround for anyone else landing here — define the provider by hand in
~/.config/opencode/opencode.json, and the server picks it up at runtime, no restart needed:{ "providers": { "my-provider": { "name": "My Provider", "package": "@opencode/ai/providers/openai-compatible", "settings": { "baseURL": "https://example.com/v1", "apiKey": "..." }, "models": { "some-model-id": { "name": "Some Model" } } } } }AtmanActive commentedon Oct 19, 2026
still broken on 2.0.32
css521 commentedon Oct 22, 2026
Proposed a fix in #57997, targeting
dev. It replaces the unconditional error with a save flow that writesproviders.<id>to the global JSONC config while preserving existing settings, stores literal API keys through the credential API, and refreshes the catalog so the new models become selectable. Success is reported after the models load; rejected writes keep the form available for retry.Local verification passed: repository lint/typechecks, the server suite (88 passed, 1 skipped), and the targeted browser regressions. Before/after screenshots and full validation details, including existing baseline failures, are in the PR. It is open for review.
ahmadsbeh commentedon Oct 23, 2026
Adding diagnostic details from Windows 11 (Desktop 2.1.4, latest channel) that may help reviewing the referenced PR #57997 — originally reported in #58533 (expected to close as duplicate):
GET /api/configis read-only, and in 2.1.4PATCH /api/experimental/configaccepts only{ "shell": ... }(Config.Patch,additionalProperties: false) — so GUI clients have no way to persist a provider today. (Matches the "Public API impact" note in the PR description.)providers.<id>withpackage: "@opencode/ai/providers/openai-compatible",settings.baseURL,models), verified on the same running server viaGET /api/providerright after the write — no restart needed. File-watcherprovider.updatedevents fire on save. So the polling approach in the PR should work for reload timing, though the bot review's concern about tying the form to reload timing is valid on Windows too.Happy to help test a build with this fix if useful.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report, @throny! I couldn't run the Desktop app here, but the source matches what you found.
provider.custom.unavailable, whatever server it is connected to. A TODO in the code says custom providers will come back once V2 has the config and credential APIs they need.dev(fa1260d6c), and already in 2.2.0, that form has been removed from the desktop provider dialog (feat(app): match the TUI integration dialog in desktop (beta) #56877). The error should no longer appear there, but for now you can only add a custom OpenAI-compatible provider by editingopencode.json, as you did.A maintainer with the Desktop app will need to confirm how it behaves in the current release.
miaorunquan commentedon Nov 2, 2026
Another reproduction on Desktop 2.2.5 (Windows)
opencode-cli2.2.5, Windows x64~/.config/opencode/opencode.json(legacyprovidershape)Steps:
Expected: a way to add/edit an OpenAI-compatible provider from Settings. The 2.0.x dialog offered this entry (although its save always failed, see above); in the current release the entry is gone entirely.
Notes:
settings.providers.tag.customi18n key, but noprovider.custom/ custom-provider form code, which is consistent with the removal in feat(app): match the TUI integration dialog in desktop (beta) #56877.Related: #57997 would restore the form with a working save, and #51527 tracks the missing credential path for custom provider IDs.