Skip to content

fix(server): await plugin activation before listing plugins - #54676

Closed
potoior wants to merge 3 commits into
anomalyco:devfrom
potoior:plugin-list-await
Closed

potoior wants to merge 3 commits into
anomalyco:devfrom
potoior:plugin-list-await

Conversation

@potoior

@potoior potoior commented Oct 12, 2026 •

Copy link
Copy Markdown

Summary

opencode plugin list reads the plugin registry without waiting for plugin activation, so on a cold location the first call can return an empty list ("No plugins found") while the configured plugins are still registering (issue #54654). Same cold-location race already fixed for mcp.list, skill.list, agent.list, and model.list.

Change

  • packages/server/src/handlers/plugin.ts — plugin.list now yield* awaitPlugins before reading the inventory, matching model.list.
  • packages/protocol/src/groups/plugin.ts — declare ServiceUnavailableError on the plugin.list endpoint (the await can fail if activation times out).
  • packages/client — regenerated so the endpoint contract carries the new error type.

Verification

  • Regression test packages/server/test/plugin-endpoint.test.ts registers a local plugin that only becomes available after its async setup completes. Without the await, plugin.list returns [] and the test fails; with it, the plugin is listed. 1/1 pass.
  • packages/protocol, packages/server, packages/client typecheck clean.

Fixes #54654


Added by the opencode bot (AI-generated).

Related: #51680, #54192, #51282, #53724

Public API: GET /api/plugin (plugin.list) can now return 503 ServiceUnavailableError, and the first call on a directory that hasn't loaded yet can take up to 5s (additive; success response unchanged)

plugin.list read the registry without waiting for plugin activation, so a
cold-location call could return an empty list while the configured plugins
were still registering (same race already fixed for mcp.list, skill.list,
agent.list and model.list). Await plugin activation before listing, matching
model.list, and add the endpoint's ServiceUnavailableError to the protocol
contract (regenerating the client).

A regression test registers a local plugin that becomes available only after
setup completes; without the await, plugin.list returns [] and the test fails.
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Oct 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 72 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the problem. I ran the #54654 reproduction from source: on the base branch, the first opencode plugin list in each of three new directories printed "No plugins found". With this PR, every call listed the plugin. The new test passes here and fails when copied onto the base, and typecheck is clean in server, protocol and client.

Public API: GET /api/plugin (plugin.list) can now return 503 ServiceUnavailableError if plugins haven't finished loading within 5s. This is an addition, and the generated client's declaredStatuses gain 503. It is backwards compatible apart from the new error status. Existing callers keep the same request and success response, but the first call on a directory that hasn't loaded yet can now take up to 5s.

Concerns:

  • packages/protocol/openapi.json wasn't regenerated. bun run check:generated in packages/protocol fails with this PR. Please run bun run generate there and commit the result.
  • Commit 8905af5 removed the separate plugin-activation wait endpoint, noting that "Catalog reads remain non-blocking". #54219 later added the same awaitPlugins wait to model.list, and this PR follows that newer pattern. Maintainers may want to confirm that blocking plugin.list is the intended direction.
  • plugin.check and plugin.update still read the plugin list without waiting (see inline comment).

Related: #51680, #54192, #51282, #53724

return yield* response(Plugin.Service.use((plugin) => plugin.list()))
}),
)
.handle("plugin.check", (ctx) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plugin.check (and plugin.update below it) also builds its inventory from plugins.list() without waiting. On a directory the server hasn't loaded yet, plugin check <target> / plugin update <target> can wrongly fail with "Plugin package is not in the current server inventory". The CLI's inventory.ts also calls plugin.list first, so with this PR that path waits, but the server handlers still race when called directly. You don't need to fix this here. If you do, yield* awaitPlugins at the start of both handlers would keep the endpoints consistent.

@potoior

potoior commented Oct 13, 2026

Copy link
Copy Markdown
Author

Hi @Hona @thdxr — this PR is ready for review. CI is green (check-compliance, check-standards, check-duplicates all pass) and it is linked to issue #54654. Could you take a look / approve when you have a moment? Thanks!

Keeps these endpoints consistent with plugin.list so a cold location can't
fail with a false 'not in the current server inventory'. Declares
ServiceUnavailableError on plugin.check and regenerates the client/openapi.
@potoior

potoior commented Oct 13, 2026

Copy link
Copy Markdown
Author

Feedback addressed: plugin.check and plugin.update now yield* awaitPlugins before resolving, keeping them consistent with plugin.list so a cold location can't falsely report "not in the current server inventory". Declared ServiceUnavailableError on plugin.check and regenerated the client + openapi (check:generated passes).

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 72-hour window.

Feel free to open a new pull request that follows our guidelines.

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Oct 15, 2026
@github-actions github-actions Bot closed this Oct 15, 2026
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.

cli: opencode plugin list prints "No plugins found" on the first call per cold location

1 participant