Repository navigation
Description
Summary
On v2.0.9 running as the background service (opencode serve --service, desktop app), config edits repeatedly fail with ERROR failed to reload plugins — TypeError: pe is not a function. After these failed reloads, the live service silently loses all file-discovered custom agents and commands: /api/agent and /api/command (and the TUI pickers) degrade to builtins only until the service is restarted. A freshly started server on the identical config loads everything, so the definitions and config are valid.
Environment
- opencode version: 2.0.9
- OS: Darwin 27.0.0 (darwin arm64), macOS
- Terminal: OpenCode desktop app (session host); TERM_PROGRAM=Apple_Terminal, TERM=xterm-256color
- Shell: /bin/zsh
- Install/channel: latest; binary at
~/.opencode/bin/opencode, background service mode - Active plugins: none in final state (transient local plugins were registered and removed during the session that triggered the bug; config has no
pluginkey afterwards)
Reproduction
- Set up custom agents (e.g.
~/.config/opencode/agent/review.md) and commands (project.opencode/commands/*.md×23, global~/.config/opencode/commands/*.md×1). Start the background service and confirm via/api/agent//api/commandthat they are loaded. - With the service running (active sessions, MCP servers connected, two locations booted: home + project), edit
~/.config/opencode/opencode.jsonrepeatedly — in my case registering then removing local plugin entries, including a moment where a registered plugin's files were deleted before its config key was removed. - Each reload logs:
ERROR failed to reload plugins cause="Cause([Die(TypeError: pe is not a function. (In 'pe()', 'pe' is an instance of Object))])" - Afterwards
/api/agentreturns only the 7 builtins (customreviewagent gone) and/api/commandreturns only the 2 builtins (all 24 custom commands gone). No error is logged at the moment the catalogs degrade — the loss is silent. opencode service restart→ everything comes back.
Honest trigger caveat: on an isolated opencode serve (no --service, no connected clients) I could not trigger the crash with an mtime touch, a byte-identical rewrite, or adding a valid no-op plugin entry. It reproduced only on the shared --service instance with active sessions and multiple booted locations, which points at the concurrent/multi-location reload path also seen in #48121.
Expected Behavior
A failed plugin reload should not take down the agent/command catalogs, and file-discovered agents/commands should remain registered. Note the agent/command directories are not subscribed by any watcher (only skills and config files are), so once the catalogs degrade there is no self-healing path short of a restart.
Actual Behavior
Log excerpts (paths trimmed; timestamps UTC):
13:07:26 ERROR failed to reload plugins cause="Cause([Die(TypeError: pe is not a function. (In 'pe()', 'pe' is an instance of Object))])"
13:25:02 ERROR failed to reload plugins cause=… (same defect, recurs ≥10× over ~30 min)
13:35:19 WARN failed to load plugin target="<project>/.opencode/plugin/aios-canary" cause="ENOENT …" (plugin files removed before config key)
13:35:19 ERROR failed to reload plugins cause=… (same TypeError, ×3 in <200 ms)
After this window, on the still-running service:
/api/agent → build, compaction, explore, general, plan, summary, title (custom "review" agent gone)
/api/command → init, review (23 project + 1 global commands gone)
Same binary + same config on a fresh opencode serve: 8 agents (incl. review) and 26 commands load correctly — verified twice on isolated instances.
Additional Context
- Workaround:
opencode service restart. - During the same window the background service also restarted twice without user action (may be unrelated, included for completeness).
- Recurring
WARN failed to load OpenCode provider config … 401 GET https://opencode.ai/console/api/v2/configalso appears on both healthy and unhealthy instances — likely unrelated auth noise. - Related issues in the same subsystem: core: concurrent location plugin reloads crash with r.base.get #48121 (concurrent location plugin reloads crash with
r.base.get), v2: deferred plugin transform failure leaves readiness latch closed and bricks catalog #44920, v2: duplicate plugin ID hangs the server — activation defect never opens readiness latch #44975. The new part reported here is the silent loss of the agent/command catalogs until restart.
Activity
Nuokfish commentedon Sep 19, 2026
Workaround applied on the reporting machine:
opencode service restartat 2026-09-19 21:55:57 CST; will attach post-restart catalog verification shortly.Nuokfish commentedon Sep 19, 2026
Post-restart verification on the reporting machine: agents =
build, compaction, explore, general, plan, review, summary, title; commands =26 commands: archive-project, check-work, deliver-closeout, force, generate-prd, generate-report, generate-spc, grill-me, init, log-time, log-work, migrate-log, next-step, quick-log, refresh-daily-insights, refresh-dashboard-insights, review, review-all, review-artifact, review-evolution, review-project, start-project, startup, suspend-project, switch-project, sync-company-dictionary; verdict = PASS (review agent restored, project commands restored).holny commentedon Sep 20, 2026
Dug into the plugin supervisor after seeing this. The
TypeError: pe is not a functionside looks like the legacy-export problem already tracked in #50021, but the durable defect is that a failed activation takes down the whole generation - file-discovered agents and commands included - and supervisor.ts:229 only logs the die, so with nothing watching the agent/command dirs there's no way back until restart. Happy to take this one. Plan: keep the last-good generation on a failed activation (treat the bad entry as a per-plugin failure like the duplicate-ID path already does) and add a regression test for the reload-failure path. Should have a PR in a day or two.holny commentedon Sep 21, 2026
Quick update before opening the PR: keeping the last-good generation on a failed activation turns out to collide with semantics the registry tests pin down on purpose.
disables plugins after replay failures discovered during setup without restoring the old generation(packages/core/test/plugin.test.ts:349) asserts that a failing revision tears the previous activation down, and the lifecycle tests require stops to fire before starts, which a keep-previous fallback breaks. So a naive fallback contradicts existing tested behavior rather than filling a gap. Would you rather see failed reloads retain the previous generation (revisiting those tests), or should the fix stay narrower — supervisor retrying from the last good packages while the registry semantics stay as-is?holny commentedon Sep 24, 2026
Following up on my question above — either answer works for me: keep-last-good if that's the intended semantics (I'll revisit the registry test that pins removal), or I'll go with the narrow fix (keep
61dce72334's applySafe + a regression test that the catalog survives a failed reload). Just don't want to guess wrong on intent before opening the PR.holny commentedon Sep 26, 2026
Still happy to take this in whichever direction you prefer — the narrow fix (applySafe + catalog-survival regression test, commit already on my branch) is a day of latency away from a PR, and the keep-last-good route is fine too if that's the intended semantics. Otherwise I'll go with the narrow fix when I next pick this up.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I could reproduce this on 2.0.9 with a plain
opencode serve;--service, the desktop app and active sessions aren't needed. It still happens on the latestdev(62ae125).What triggers it here: a local plugin whose
setupreturns an object instead of a cleanup function, e.g.setup: async () => ({}).opencode.json. Custom agents and commands are still listed.failed to reload plugins … TypeError: … is not a function. (In '…()', '…' is an instance of Object), which matches your error. Ondevthe unminified name iscleanup./api/agentno longer has the customreviewagent and/api/commandreturns onlyinitandreview, in every booted location. Nothing else is logged.The script below reproduces it. One difference from what you saw: here, a later unrelated config edit restored the agents and commands without a restart.
The transient plugins you registered may have returned an object from
setup, for example a hooks object. If so, that would explain the error.Closely related: #48121, where a different reload error also leaves agents unavailable until restart.
Reproduction script (repro.sh)
Run it:
bash repro.shholny commentedon Nov 10, 2026
PR #57775 fixes this trigger: the plugin teardown was calling whatever
setup()returned as a cleanup function, and the repro'ssetup: async () => ({})plugin is exactly the case it guards — the value is now ignored unless it's an actual function, so the reload no longer aborts and the agent/command catalogs stay intact. Once it merges this repro should pass without the restart.