You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Plugin reload during a turn fails the step retry and marks every skill as removed #51128
If a plugin reloads while a turn is running (e.g. a local plugin file is edited), a step retry that lands during the reload reads a half-rebuilt catalog. The turn ends as failed without calling the provider again (we have also seen Model unavailable: <provider>/<model> for a model defined in config), and the session gets a permanent system instruction saying every skill "is no longer available and must not be used".
The runner waits for plugins.awaitActivation once per drain, but each step and retry goes back through prepareContext → context.select / resolveModel without waiting again. Possibly the same root cause as #47114, which also happened just after a plugin reload.
Plugins
One local plugin (plugins: [{ "package": "./plugins/probe" }]) whose setup awaits 3 seconds; the delay only widens the window.
OpenCode version
v2.0.16 (also the current v2 branch)
Steps to reproduce
Config with a provider/model defined in opencode.json and the local plugin above.
Point the provider at a local stub that answers the first request with HTTP 500 and later ones with 400.
Send a prompt. As soon as the stub sees the first request, append a comment to the plugin file.
The retry runs during the reload: the stub never receives a second request, the turn ends failed, and the session now contains "The following skill IDs are no longer available and must not be used: …".
Waiting for plugin activation in prepareContext (and in manual compaction's resolveContext) fixes it: the retry waits for the reload, then reaches the provider normally.
Screenshot and/or share link
n/a
Operating System
Debian 13 (Linux 6.12)
Terminal
n/a (reproduced over the HTTP API with opencode serve)
Confirming on v2.0.15 (Windows): hot-reloading a local plugin during a turn reproduces both symptoms
Repro: a local plugin configured via plugins (a directory). Edit any file under its src/** while a session turn is running (this triggers the plugin hot-reload). Observed:
1. The turn dies and never reaches the provider again
level=ERROR message="Failed to drain Session"
cause="SessionRunnerModel.ModelUnavailableError: Model unavailable: r4-coder/deepseek-v4.1-flash
at SessionRunnerModel.resolve
at SessionContext.load
at SessionRunner.advanceToStep
at SessionRunner.drain"
The model is defined in opencode.json (a custom provider), i.e. a config-defined model — same shape as #47114.
2. The session then carries the "all skills removed" instruction
The following skill IDs are no longer available and must not be used:
api-and-interface-design, bootstrapblazor, brainstorming, code-simplification, ...
followed later by a system update re-adding them.
This is consistent with the description: a step/retry re-enters prepareContext → context.select / resolveModel while the catalog is half-rebuilt, after the runner already waited for activation once per drain.
Notes
It is intermittent and usually self-heals on the next location boot, but while it lasts the turn ends failed and any auto-continuation loop (e.g. a goal plugin driving multi-turn work) stops until the model resolves again.
Reproduced over the HTTP API with opencode serve; no provider stub needed — the failure happens before any provider request.
+1 for #51133 (await plugin activation in prepareContext, and in manual compaction's resolveContext).
Confirming on v2.0.26 (macOS, managed background service). Hit it 4 times today, in two
different sessions — both were editing files under a watched plugin directory
(~/.config/opencode/vendor/...) during their own turn when it happened.
Every occurrence has the exact reported shape:
turn dies at SessionRunner.advanceToStep → SessionRunnerModel.resolve with SessionRunnerModel.ModelUnavailableError for a config-declared provider that was working
fine seconds before and after;
in both sessions the failure lands in the same second as the "following skill IDs are no
longer available" system instruction — matches the half-rebuilt-catalog theory;
the session parks idle mid-work with a completed tool result and no client-visible error;
a manual "Continue." prompt re-resolves the model and resumes normally.
Log sample:
level=ERROR message="Failed to drain Session"
cause="SessionRunnerModel.ModelUnavailableError: Model unavailable: <provider>/<model>
at SessionRunnerModel.resolve ... at SessionRunner.advanceToStep ... at SessionRunner.drain"
So: reproducible trigger = plugin/vendor file write during a running turn. Fix PR #54012
describes exactly this ("prevent transient plugin registry teardown from stopping an
already-running session") — was it closed for a reason, or does it still need a reviewer?
Confirming on v2.0.25 (Windows 11, OpenChamber-managed opencode serve), with one addition: the provider that vanished was not edited at all. Editing an earlier-ordered plugin is enough, because of the prefix rule in Plugin.activate.
What happened (2026-10-11, two occurrences, both the exact stack from this issue):
18:46:54.49Z <config>/plugins/squeez.js rewritten on disk (auto-discovered plugin, ordered before the configured ones)
18:46:55.9Z "loading plugin" for every plugin, in ~10 live locations
18:46:56–57Z model.updated in every location (12 in that minute; baseline is 1–5/min)
18:46:59.02Z ERROR "Failed to drain Session"
SessionRunnerModel.ModelUnavailableError: Model unavailable: claude-code/claude-opus-5-5
at SessionRunnerModel.resolve
at SessionContext.load
at SessionRunner.advanceToStep
at SessionRunner.drain
18:50:53–59Z another local plugin's dist/ rebuilt (also ordered before the provider plugin)
18:51:09.93Z same error, same stack, a different session
claude-code is a provider registered by a third plugin (ctx.provider.transform, activation: "enabled"), whose files did not change. It still disappeared, because activate keeps only the unchanged prefix alive ("Registrations are ordered by setup, so only the unchanged prefix can stay alive"): every plugin after the changed one has its scope closed, then each is set up again in order. State.batch only coalesces notifications ("Reads inside stay fresh"), so until the provider plugin's own setup reaches provider.transform, any reader sees a catalog without it. That gap is the sum of the setup time of every plugin ordered before it, plus whatever its own setup awaits first.
The runner side is as described above: plugins.awaitActivation runs once in drain (session/runner/llm.ts), while every later step goes advanceToStep → prepareContext → context.load → SessionRunnerModel.resolve without waiting again. A session in a multi-step tool loop only has to cross a step boundary inside the gap. The failure is terminal for the turn, so in the UI it reads as "the provider disconnected".
Two things make this much easier to hit than "someone edited a plugin during a turn" suggests:
One changed file fans out to every live location at once (about ten here), so every running session is exposed to the same gap.
+1 for awaiting activation in prepareContext and in the manual-compaction context.select / resolveModel path. Retrying ModelUnavailableError once after awaitActivation would cover any remaining reader as well.
Plugin-side mitigation for anyone hitting this meanwhile: call ctx.provider.transform first in setup, before any await on I/O (ours awaited a port bind and a CLI probe of up to 3 s before registering). It narrows the gap but cannot close it, since earlier plugins still set up first.
Still reproduces on 2.0.28 and 2.0.29, and #54012 was closed without merging, so there's no fix in flight that I can see.
Two things I haven't seen in this thread yet.
The reload doesn't need anyone to edit a plugin. Any write to a config directory's plugin/ or plugins/ folder triggers it. In my case Orca rewrote its config overlay during an orca worktree create that the session itself ran, and the next step failed.
The failure has a second shape. If the reloaded plugin is ordered after mine, my plugin stays registered but the post plugins (opencode.config.provider and the rest) get disposed. The provider my plugin registers still resolves, but the config models it wraps are gone, so it failed with source model ... not found for all five of them.
I reproduced both shapes against a real opencode binary in an isolated HOME with a loopback fake upstream. With a 3s reload window each fails 3/3 on 2.0.28 and 2.0.29, and with no delay both complete 3/3. The script needs my plugin built, but the harness is generic. Script is repro-issue-76.mjs. Notes are in issue-76.md.
Confirming on v2.0.25 (macOS, OpenChamber-managed opencode serve). Three hits in one server run today, same stack every time (SessionRunner.drain → advanceToStep → SessionContext.load → SessionRunnerModel.resolve → ModelUnavailableError), each for a different config-declared model under the same custom provider (vercel/vmc/daily-economy, daily-balance, daily-intelligence).
One more trigger for the list: a git operation run by the session's own tool call. No plugin file was edited. The branch switch rewrote .opencode/opencode.json (one line, a different pin for the same plugin), AGENTS.md, and two symlinks under .agents/skills/. That alone reloads every plugin and rebuilds the catalog mid-turn.
Timeline of the clearest one (all in the same session):
01:56:35.089 shell tool: git stash push -u … && git switch main
01:56:35.976 watcher stopped .agents/skills (reload #1 starts)
01:56:36.217 loading plugin ×7 (local + 2 github: plugins)
01:56:36.319 event config.updated
01:56:36.497 ERROR Failed to drain Session
SessionRunnerModel.ModelUnavailableError: Model unavailable: vercel/vmc/daily-economy
01:56:36.562 event provider.updated (65 ms after the failure)
01:56:36.570 event model.updated
01:56:36.693 loading plugin ×7 again (reload #2: skills dir and config file change separately)
01:56:51 user typed "retry" → model resolves, turn continues
The other two had the same shape: git reset --hard … && git rebase origin/main from the session's tool call 1.3 s before the error (different worktree), and one reload in a sibling worktree whose trigger I did not pin down. In all three, provider.updated landed 50–300 ms after the ERROR line.
What the user sees: the tool result completes, the session goes idle with no visible error, plus the "The following skill IDs are no longer available and must not be used" system update followed by a second update re-adding them. "retry" resumes normally.
Two notes for anyone reproducing:
opencode --version on PATH said 2.0.29 while the long-running server was still 2.0.25, so check the server's version, not the CLI's.
Any repo that commits .opencode/opencode.json and switches between branches with different plugin pins will hit this on every checkout, so this is not limited to people editing plugins.
+1 for awaiting activation in prepareContext (or retrying ModelUnavailableError once after awaitActivation).
Activity
Just-Silver commentedon Sep 25, 2026
Confirming on
v2.0.15(Windows): hot-reloading a local plugin during a turn reproduces both symptomsRepro: a local plugin configured via
plugins(a directory). Edit any file under itssrc/**while a session turn is running (this triggers the plugin hot-reload). Observed:1. The turn dies and never reaches the provider again
The model is defined in
opencode.json(a custom provider), i.e. a config-defined model — same shape as #47114.2. The session then carries the "all skills removed" instruction
followed later by a system update re-adding them.
This is consistent with the description: a step/retry re-enters
prepareContext→context.select/resolveModelwhile the catalog is half-rebuilt, after the runner already waited for activation once per drain.Notes
failedand any auto-continuation loop (e.g. a goal plugin driving multi-turn work) stops until the model resolves again.opencode serve; no provider stub needed — the failure happens before any provider request.+1 for #51133 (await plugin activation in
prepareContext, and in manual compaction'sresolveContext).JKratto commentedon Oct 10, 2026
Confirming on v2.0.26 (macOS, managed background service). Hit it 4 times today, in two
different sessions — both were editing files under a watched plugin directory
(
~/.config/opencode/vendor/...) during their own turn when it happened.Every occurrence has the exact reported shape:
SessionRunner.advanceToStep→SessionRunnerModel.resolvewithSessionRunnerModel.ModelUnavailableErrorfor a config-declared provider that was workingfine seconds before and after;
longer available" system instruction — matches the half-rebuilt-catalog theory;
a manual "Continue." prompt re-resolves the model and resumes normally.
Log sample:
So: reproducible trigger = plugin/vendor file write during a running turn. Fix PR #54012
describes exactly this ("prevent transient plugin registry teardown from stopping an
already-running session") — was it closed for a reason, or does it still need a reviewer?
mradwankhalil commentedon Oct 11, 2026
Confirming on v2.0.25 (Windows 11, OpenChamber-managed
opencode serve), with one addition: the provider that vanished was not edited at all. Editing an earlier-ordered plugin is enough, because of the prefix rule inPlugin.activate.What happened (2026-10-11, two occurrences, both the exact stack from this issue):
claude-codeis a provider registered by a third plugin (ctx.provider.transform,activation: "enabled"), whose files did not change. It still disappeared, becauseactivatekeeps only the unchanged prefix alive ("Registrations are ordered by setup, so only the unchanged prefix can stay alive"): every plugin after the changed one has its scope closed, then each is set up again in order.State.batchonly coalesces notifications ("Reads inside stay fresh"), so until the provider plugin's ownsetupreachesprovider.transform, any reader sees a catalog without it. That gap is the sum of the setup time of every plugin ordered before it, plus whatever its ownsetupawaits first.The runner side is as described above:
plugins.awaitActivationruns once indrain(session/runner/llm.ts), while every later step goesadvanceToStep→prepareContext→context.load→SessionRunnerModel.resolvewithout waiting again. A session in a multi-step tool loop only has to cross a step boundary inside the gap. The failure is terminal for the turn, so in the UI it reads as "the provider disconnected".Two things make this much easier to hit than "someone edited a plugin during a turn" suggests:
+1 for awaiting activation in
prepareContextand in the manual-compactioncontext.select/resolveModelpath. RetryingModelUnavailableErroronce afterawaitActivationwould cover any remaining reader as well.Plugin-side mitigation for anyone hitting this meanwhile: call
ctx.provider.transformfirst insetup, before anyawaiton I/O (ours awaited a port bind and a CLI probe of up to 3 s before registering). It narrows the gap but cannot close it, since earlier plugins still set up first.robertn702 commentedon Oct 14, 2026
Still reproduces on 2.0.28 and 2.0.29, and #54012 was closed without merging, so there's no fix in flight that I can see.
Two things I haven't seen in this thread yet.
The reload doesn't need anyone to edit a plugin. Any write to a config directory's
plugin/orplugins/folder triggers it. In my case Orca rewrote its config overlay during anorca worktree createthat the session itself ran, and the next step failed.The failure has a second shape. If the reloaded plugin is ordered after mine, my plugin stays registered but the post plugins (
opencode.config.providerand the rest) get disposed. The provider my plugin registers still resolves, but the config models it wraps are gone, so it failed withsource model ... not foundfor all five of them.I reproduced both shapes against a real
opencodebinary in an isolatedHOMEwith a loopback fake upstream. With a 3s reload window each fails 3/3 on 2.0.28 and 2.0.29, and with no delay both complete 3/3. The script needs my plugin built, but the harness is generic. Script isrepro-issue-76.mjs. Notes are inissue-76.md.rebelliard commentedon Oct 16, 2026
Confirming on v2.0.25 (macOS, OpenChamber-managed
opencode serve). Three hits in one server run today, same stack every time (SessionRunner.drain→advanceToStep→SessionContext.load→SessionRunnerModel.resolve→ModelUnavailableError), each for a different config-declared model under the same custom provider (vercel/vmc/daily-economy,daily-balance,daily-intelligence).One more trigger for the list: a git operation run by the session's own tool call. No plugin file was edited. The branch switch rewrote
.opencode/opencode.json(one line, a different pin for the same plugin),AGENTS.md, and two symlinks under.agents/skills/. That alone reloads every plugin and rebuilds the catalog mid-turn.Timeline of the clearest one (all in the same session):
The other two had the same shape:
git reset --hard … && git rebase origin/mainfrom the session's tool call 1.3 s before the error (different worktree), and one reload in a sibling worktree whose trigger I did not pin down. In all three,provider.updatedlanded 50–300 ms after theERRORline.What the user sees: the tool result completes, the session goes idle with no visible error, plus the "The following skill IDs are no longer available and must not be used" system update followed by a second update re-adding them. "retry" resumes normally.
Two notes for anyone reproducing:
opencode --versionon PATH said 2.0.29 while the long-running server was still 2.0.25, so check the server's version, not the CLI's..opencode/opencode.jsonand switches between branches with different plugin pins will hit this on every checkout, so this is not limited to people editing plugins.+1 for awaiting activation in
prepareContext(or retryingModelUnavailableErroronce afterawaitActivation).