Repository navigation
Description
Summary
When any plugin's setup() takes roughly 500 ms or longer, every github-copilot model ends up with enabled: false and disappears from /models and opencode models, even though the Copilot credential is connected. The state does not recover later: a long-running background service and ctx.model.reload() keep the models hidden.
Environment
- opencode version: 2.1.1
- OS: Windows 11, Microsoft Windows [Version 10.0.26300.9711] (win32 x64)
- Terminal: Unavailable: terminal environment variables are not set (run from the OpenCode agent shell tool)
- Shell: PowerShell 7 (pwsh)
- Install/channel: latest
- Active plugins: the minimal reproduction uses only the
slow-setupplugin below. The issue was first observed with an internal organization plugin that performs network requests duringsetup().
Reproduction
-
Connect GitHub Copilot (
/connect→ GitHub Copilot, OAuth). Without plugins,opencode models --standalonelists thegithub-copilot/*models (38 for this account). -
Create an isolated config with only one local plugin:
$XDG_CONFIG_HOME/opencode/plugins/slow-setup/package.json{"name":"slow-setup","type":"module","exports":{".":"./index.mjs"}}$XDG_CONFIG_HOME/opencode/plugins/slow-setup/index.mjsexport default { id: "slow-setup", async setup() { await new Promise((resolve) => setTimeout(resolve, 1000)); }, };
-
Run
opencode models --standalonefrom an empty directory withXDG_CONFIG_HOMEpointing to that config. -
Count the
github-copilot/lines.
Results (each line is a separate fresh --standalone run):
| setup() delay | github-copilot models listed |
|---|---|
| 0 ms | 38 |
| 0 ms | 38 |
| 500 ms | 0 |
| 1000 ms | 0 |
| 1000 ms | 0 |
| 3000 ms | 0 |
Expected Behavior
A plugin's slow setup() should not change which models another provider integration enables. Copilot models available to the connected account should stay enabled, or should become enabled once the integration finishes loading.
Actual Behavior
The github-copilot models are still present in the catalog, but all of them have enabled: false. A diagnostic ctx.model.transform registered after the slow plugin observed:
providers: ["github-copilot", "opencode", ...]
github-copilot enabled/status values: ["false/active"]
Without the slow plugin, the same transform observed ["true/active"].
GET /api/provider/github-copilot still returns the provider (activation: "auto"), and opencode auth list shows the credential as stored. The other provider (opencode, activation enabled) is unaffected.
Additional Context
- Reproduced consistently on every run (see table). The threshold appears to be under 500 ms.
- Recovery does not happen: in the long-running shared background service, the models stay hidden after
ctx.model.reload()and after querying other locations. - Workaround: explicitly declaring a model in config makes that single model visible again:
{"providers":{"github-copilot":{"models":{"claude-sonnet-5.5":{"disabled":false}}}}} - Calling
ctx.agent.list()orctx.command.list()during setup does not trigger the issue. Only elapsed time does. - Two Copilot credentials (personal and organization) are stored for the
github-copilotintegration; one is active. The minimal reproduction was not tested with a single credential.
Activity
opencode-agent commentedon Oct 20, 2026
Thanks for the clear reproduction, I could confirm this.
I used a local mock of the Copilot
/modelsendpoint instead of a real account. Setup: a fresh isolated config, one importedgithub-copilotOAuth credential pointing at the mock, and yourslow-setupplugin as the only plugin. Then I ranopencode models --standaloneand counted thegithub-copilot/lines.dev(0718864d, run from source): still happens. Without the plugin the models are listed; withslow-setupnone are, even with a 0 ms delay. Startup from source is slower, which fits a startup timing problem rather than anything specific to your plugin.The script below reproduces it. I only checked the
modelslisting, not the long-running service orctx.model.reload()case. Until this is fixed, your workaround of declaring the Copilot models you need underproviders.github-copilot.modelsin config is the way to keep them visible.Reproduction script (repro.sh)
Run it:
bash repro.shDante-dan commentedon Oct 20, 2026
I found a pitfall in the mock script above: each delay gets a new XDG home, but
auth importuses the managed service's fixed port. In my runs, a later home hit “Managed service port 49375 ... already in use”; the import error was discarded, so counting zero models was not a valid reproduction. Useauth import --standaloneand verify that import succeeds before comparing delays.A controlled startup probe passes on current dev: Copilot discovery finishes while a Promise plugin holds activation, then the configured model catalog is built; the picker-enabled model remains visible. The Copilot test file passes 30 tests. This covers that ordering, not the original Windows scenario, and is not a production fix.
Could you share redacted server logs for 0 ms and 1000 ms runs using the same connected config, including any plugin activation failures?
vRune4 commentedon Oct 21, 2026
Thanks both. Here are server logs from the same connected config, as requested.
Setup
/connect(noauth import, so the managed-service port issue does not apply here).XDG_CONFIG_HOMEcontaining only theslow-setupplugin; empty working directory.OPENCODE_LOG_LEVEL=DEBUG opencode models --standalone --print-logs, run back to back.Results
(The 0 ms count was 38 when I filed the issue yesterday; I don't know why it changed. Two Copilot credentials are stored for the integration, personal and organization, one of them active.)
Observations
loading plugintoprovider.updatedtakes about 460 ms with a 0 ms delay and about 1470 ms with a 1000 ms delay.Watcher subscribe/started lines are removed; usernames in paths are replaced with
<user>. Nothing else is changed.Server log, 0 ms delay (27 Copilot models)
Server log, 1000 ms delay (0 Copilot models)
If there's a log scope or flag that traces Copilot discovery, tell me and I'll rerun with it.
Dante-dan commentedon Oct 21, 2026
Thanks—your real-OAuth runs rule out the mock script’s import problem. There isn't a separate discovery trace flag in 2.1.1: this path logs failures, but not successful discovery, even at DEBUG.
I prepared a diagnostic branch on current dev. With
OPENCODE_LOG_LEVEL=DEBUGand--print-logs, it reports:syncing GitHub Copilot modelssynced GitHub Copilot models, with usable-model and picker-enabled countsIt logs no tokens, endpoint URLs or account identifiers and leaves model availability unchanged. Existing Copilot provider/catalog tests pass (31 tests);
bun run checkalso passes (Node 22.16 withNODE_OPTIONS=--experimental-strip-types). I haven't reproduced your Windows/two-credential scenario.If you can run that branch against the same connected config, those discovery lines for each delay would distinguish a response with zero picker-enabled models from models being disabled later. This is diagnostic instrumentation, not a fix; I haven't established the root cause.
vRune4 commentedon Oct 23, 2026
Thanks, this was exactly the right instrumentation. I ran your diagnostic commit against the same connected config, and the result changes the picture.
Setup
681156c82ffromanomalyco/opencodeplus only the patch from your commit04005cc7(applied on top; nothing else taken from the fork). One local deviation: I removed the@silvia-odwyer/photon-nodeentry frompatchedDependencies, becausebun install(1.4.4, Windows) failed to apply that patch. It is unrelated to providers.bun run src/index.ts serve --print-logsfrompackages/cli,OPENCODE_LOG_LEVEL=DEBUG, using a copy of my 2.1.1 database (OPENCODE_DB) with the real Copilot OAuth credential. Client: released 2.1.1opencode models --server ...from an empty directory. IsolatedXDG_CONFIG_HOMEwith onlyslow-setup.Results (four fresh server starts, alternating)
models=21 pickerEnabled=0models=21 pickerEnabled=0models=21 pickerEnabled=0models=21 pickerEnabled=0Key log lines (timestamps only):
Interpretation
/modelsresponse contains 21 models and none withmodel_picker_enabled: true.That leaves two questions I can't answer from here:
model_picker_enabled: falseas disabled correct when an account has zero picker-enabled models? It may be an account or policy setting on GitHub's side, but then a message would help more than an empty list.I haven't checked the inactive organization credential.
vRune4 commentedon Oct 23, 2026
Update with the root cause, which turned out to be different from the title.
The active credential is a Copilot Free account (
copilot_internal/user:copilot_plan: individual,access_type_sku: free_limited_copilot, endpointapi.individual.githubcopilot.com). The plugin delay only decides whether the catalog is built before or after Copilot discovery. It doesn't cause the problem.What
/modelsreturns for this account (41 entries, queried with the same OAuth credential OpenCode uses)model_picker_enabledisfalsefor all 41 models.policy.stateisenabledforclaude-haiku-4.5,gpt-4.1,gpt-4.1-2025-04-14,gpt-5.6-luna,gpt-6-luna,mai-code-1.1-flash;disabledfor the premium models; absent for legacy and embedding models.What actually works (one short request each)
policy.stategpt-4.1/chat/completions)claude-haiku-4.5model_not_supportedmodel_not_supportedgpt-5.6-lunamodel_not_supported(/responsesand/chat/completions)model_not_supportedgpt-6-lunamodel_not_supported(/responsesand/chat/completions)model_not_supportedSo a Copilot Free account can use at least
gpt-4.1through this integration, but OpenCode hides it becausemodel_picker_enabledis false. Neither flag on its own predicts what is usable:policy.state: enabledalso covers models GitHub then rejects.Resulting behavior
model_not_supported.{"providers":{"github-copilot":{"models":{"gpt-4.1":{"disabled":false}}}}}makesgpt-4.1visible and usable in both cases.Suggestions
model_picker_enabled: falseas unavailable on its own, at least whenpolicy.stateisenabled. An account where every model ends up hidden is a strong signal that the filter is wrong.Feel free to retitle the issue (for example "github-copilot: Copilot Free accounts get no usable models").
vRune4 commentedon Oct 23, 2026
Correction to my previous comment: GitHub's plan documentation says that on Copilot Free and Copilot Student, "access to models is available through auto model selection only". So
model_picker_enabled: falseon every model is intended for this plan, not a filtering mistake.My first suggestion (don't treat
model_picker_enabled: falseas unavailable whenpolicy.stateisenabled) therefore doesn't hold up: GitHub rejects most of those models anyway, andgpt-4.1working looks like an undocumented leftover. I also testedgpt-5.6-luna-free-autoandmai-code-1.1-flash; both returnmodel_not_supported.The other two points still apply:
Dante-dan commentedon Oct 23, 2026
Thanks for testing the diagnostic branch and checking the plan restriction. I kept
model_picker_enabled: falseauthoritative.The first Copilot refresh currently runs in the background, so plugin activation can finish with the static models.dev catalog before discovery completes. This verified patch completes that refresh during activation. The catalog after activation therefore follows the account response regardless of unrelated plugin setup time. Discovery failures retain the existing fallback/warning; startup now waits for the existing 5-second fetch timeout. Account-switch refresh is unchanged.
All 33 Copilot provider/catalog tests pass (76 assertions), including two regressions that fail with the original background refresh.
bun run checkand pre-push checks both pass (34/34 tasks). I haven't reproduced the Windows/two-credential environment. GitHub rejected PR creation with aCreatePullRequestpermission error, so the tested patch is available on the fork.The empty-state explanation remains open. The current available-model list cannot distinguish zero picker-enabled models from failed discovery or local filtering. I propose an optional provider availability reason,
no_picker_models, set only after successful nonempty discovery with zero picker-enabled entries, tied to the source connection and cleared on account changes or later selectable results. The provider-specific picker could then explain that no selectable models are exposed, with a link to the plan restrictions, instead of the generic empty message. It would neither infer a plan nor enable rejected models. Would this small provider-status/UI design fit the intended approach?