Skip to content

docs: investigate issue 76 (router models unavailable during OpenCode plugin reload) - #77

Draft
robertn702 wants to merge 1 commit into
mainfrom
robertn702/issue-76-model-registry-race
Draft

robertn702 wants to merge 1 commit into
mainfrom
robertn702/issue-76-model-registry-race

Conversation

@robertn702

Copy link
Copy Markdown
Owner

Refs #76. This is a draft with an investigation, not a fix. The root cause is in OpenCode V2 core, so this PR does not close the issue.

Findings

Both failures in #76 have one cause, reproduced on the real host:

  • OpenCode re-activates plugins when a local plugin module or a config directory's plugin//plugins/ folder changes. Orca's config overlay is one such folder, and it was rewritten by the session's own orca worktree create call at 08:23.
  • Plugin.activate disposes every plugin from the changed one onward, then sets them up again in order.
  • SessionRunner.drain waits for awaitActivation only once, before its step loop. A step that starts inside the reload window resolves against a partial registry:
    • Router disposed → ModelUnavailableError: Model unavailable: reasoning-router/claude-opus-5-5, persisted as provider.no-route (the 08:23 incident).
    • Only opencode.config.provider and other post plugins disposed (they run after user plugins) → the alias exists, but its sources are missing: reasoning-router: source model … not found (the 07:01 incident, all five sources).
  • Affected: OpenCode 2.0.28 (the running server) and 2.0.29 (current latest).

The router can't fix this. During the window its registrations don't exist, and hiding the source gap would send requests to the placeholder alias. The report has the suggested upstream change (await activation before each step's context load and before compaction) and a draft upstream report, which has not been filed.

Changes

  • docs/investigations/issue-76.md: a sanitized report covering evidence (timestamps and lifecycle metadata only), OpenCode host behavior read from the 2.0.28 binary, reproduction results, ruled-out hypotheses, why there is no plugin-side fix, the suggested upstream fix, remaining unknowns, and the next discriminating experiment.
  • scripts/repro-issue-76.mjs: an isolated real-host reproducer. It uses a private HOME/XDG tree under /tmp/opencode, a loopback fake Anthropic upstream, and blocks all other egress. It exits 0 when the session completes and 1 when the failure reproduces. It is not wired into CI because it fails until OpenCode is fixed.

Verification

  • node scripts/repro-issue-76.mjs before 3000 on 2.0.28: fails 3/3 with Model unavailable: reasoning-router/claude-opus-5-5.
  • node scripts/repro-issue-76.mjs after 3000 on 2.0.28: fails 3/3 with reasoning-router: source model src/claude-opus-5-5 not found.
  • Same two commands with delayMs 0 (controls): complete 3/3 each.
  • Same two commands with OPENCODE_BIN set to an isolated 2.0.29: both fail the same way.
  • npm run check: passes (typecheck, Biome, 36 test files / 465 tests). The one warning is pre-existing (packages/classifiers/test/jev.test.ts:128).

No live service, shared config, installed plugin, or shared OpenCode install was modified.

Remaining unknowns

  • Which process rewrote the overlay at 07:01.
  • Whether per-step gating also covers requests already in flight when the router's hooks are disposed.
  • The upstream fix is untested; OpenCode V2 ships as a compiled binary.

Router aliases and wrap sources go missing when a session step runs
while OpenCode V2 re-activates plugins. SessionRunner.drain awaits
plugin activation only once, before its step loop, and Plugin.activate
disposes every plugin from the changed one onward before setting them
up again. A step in that window either cannot resolve the
reasoning-router alias (ModelUnavailableError, provider.no-route) or
sees the alias without its sources, because opencode.config.provider
runs after user plugins.

scripts/repro-issue-76.mjs reproduces both shapes deterministically on
OpenCode 2.0.28 and 2.0.29 under a private /tmp/opencode tree with a
loopback fake upstream. It is a manual reproducer, not part of CI,
because it fails until OpenCode gates each step on activation.

No reasoning-router change: the plugin is unregistered during the
window, and masking the source gap would forward requests to the
placeholder alias. The report includes the suggested upstream fix.

Refs #76
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant