Skip to content

fix(plugin): stop exporting buildHiddenAgentConfig — opencode 1.17 invokes every entry export as a plugin factory - #182

Closed
iceteaSA wants to merge 1 commit into
cortexkit:masterfrom
iceteaSA:fix-plugin-entry-export
Closed

fix(plugin): stop exporting buildHiddenAgentConfig — opencode 1.17 invokes every entry export as a plugin factory#182
iceteaSA wants to merge 1 commit into
cortexkit:masterfrom
iceteaSA:fix-plugin-entry-export

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

The bug

On opencode 1.17, the magic-context plugin fails to load on every start:

level=ERROR message="failed to load plugin"
  path=…/magic-context/packages/plugin
  error="undefined is not an object (evaluating 'allowedTools')"

Consequences: no hooks or tools registered, empty MC runtime log, migration churn each boot, all ctx_* tools dead.

Root cause

opencode 1.17 invokes every exported function in a plugin's entry module (packages/plugin/src/index.ts) as its own plugin factory — calling it fn(ctx).

The helper buildHiddenAgentConfig is exported (introduced in 29a49cb6 "mason: apply D19 resilience safe parts"). So opencode calls it directly:

buildHiddenAgentConfig(ctx)        // prompt = ctx object, allowedTools = undefined
  → buildAllowOnlyPermission(undefined)
  → for (const tool of undefined)  // throws "undefined is not an object"

An instrumented stack trace confirmed the caller was opencode core (an Effect frame) invoking buildHiddenAgentConfig directly — not our own config hook. The DB (PRAGMA quick_check: ok) and the plugin cache were both empirically ruled out.

The entry module must export only default (the plugin factory). Any other exported function gets mis-invoked as a factory.

The fix

Make buildHiddenAgentConfig module-local (remove export). It is only used inside index.ts, so this is safe.

Verified post-build:

dist export keys: ["default"]

…and the plugin loads cleanly under opencode 1.17 — hooks register, ctx_* tools are live, transforms run.

Scope

One line (plus an explanatory comment so it isn't re-exported by reflex). tsc clean, build clean.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Move buildHiddenAgentConfig out of the plugin entry to stop opencode 1.17 from invoking it as a plugin factory and crashing on load. The entry now exports only default, restoring clean startup and ctx_* tools.

  • Bug Fixes
    • Moved buildHiddenAgentConfig to packages/plugin/src/hidden-agent-config.ts and imported it in index.ts; entry now exports only default.
    • Updated index-refresh.test.ts to use the new module; verified dist exports ['default'] and the plugin loads with hooks and tools active.

Written for commit d0a731f. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR fixes a crash in opencode 1.17 where every exported function in a plugin entry module is invoked as a plugin factory. The previous code exported buildHiddenAgentConfig from index.ts, causing opencode to call it with a plugin context object as prompt and undefined as allowedTools, which threw on startup.

  • packages/plugin/src/hidden-agent-config.ts (new): buildHiddenAgentConfig and clampHiddenAgentStepLimit are extracted into a standalone module with a JSDoc comment explaining why this helper must not live in the entry file.
  • packages/plugin/src/index.ts: The function and its buildAllowOnlyPermission import are removed; the module now only exports default, resolving the mis-invocation.
  • packages/plugin/src/index-refresh.test.ts: Test import updated from ./index to ./hidden-agent-config, keeping the existing unit tests intact.

Confidence Score: 5/5

Safe to merge — the change is a minimal, surgical extraction of one helper into its own module with no logic changes.

The function body is copied verbatim into hidden-agent-config.ts, the previously-broken test import is corrected in the same commit, and index.ts now carries only export default plugin. The fix directly addresses the confirmed crash path and leaves no other named exports in the entry module that opencode could mis-invoke.

No files require special attention.

Important Files Changed

Filename Overview
packages/plugin/src/index.ts Removed buildHiddenAgentConfig and the now-unused buildAllowOnlyPermission import; imports the helper from ./hidden-agent-config instead. Entry module now exports only default.
packages/plugin/src/hidden-agent-config.ts New module containing buildHiddenAgentConfig and clampHiddenAgentStepLimit — identical logic to what was in index.ts, with an added JSDoc explaining the isolation requirement.
packages/plugin/src/index-refresh.test.ts Import corrected from ./index to ./hidden-agent-config; test coverage for buildHiddenAgentConfig is preserved without changes to the assertions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[opencode 1.17 loads plugin entry\npackages/plugin/src/index.ts] --> B{Iterates all\nexported keys}
    B -->|Before fix| C[Finds: default, buildHiddenAgentConfig]
    B -->|After fix| D[Finds: default only]
    C --> E[Calls buildHiddenAgentConfig as factory\nbuildHiddenAgentConfig ctx]
    E --> F[allowedTools = undefined\nfor...of undefined → CRASH\n'undefined is not an object']
    D --> G[Calls default plugin factory\nplugin ctx]
    G --> H[Plugin loads successfully\nhooks + ctx_* tools registered]
    style F fill:#ff4444,color:#fff
    style H fill:#22bb44,color:#fff
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[opencode 1.17 loads plugin entry\npackages/plugin/src/index.ts] --> B{Iterates all\nexported keys}
    B -->|Before fix| C[Finds: default, buildHiddenAgentConfig]
    B -->|After fix| D[Finds: default only]
    C --> E[Calls buildHiddenAgentConfig as factory\nbuildHiddenAgentConfig ctx]
    E --> F[allowedTools = undefined\nfor...of undefined → CRASH\n'undefined is not an object']
    D --> G[Calls default plugin factory\nplugin ctx]
    G --> H[Plugin loads successfully\nhooks + ctx_* tools registered]
    style F fill:#ff4444,color:#fff
    style H fill:#22bb44,color:#fff
Loading

Reviews (2): Last reviewed commit: "fix(plugin): move buildHiddenAgentConfig..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 1 file

Re-trigger cubic

Comment thread packages/plugin/src/index.ts Outdated
…encode 1.17 invokes every entry export as a plugin factory

opencode 1.17 calls every exported function in a plugin's entry module
(index.ts) as its own plugin factory: fn(ctx). The helper buildHiddenAgentConfig
was exported from index.ts (introduced upstream in 29a49cb 'D19 resilience
safe parts'), so opencode invoked it as buildHiddenAgentConfig(ctx) — passing
the plugin context as `prompt` and undefined as `allowedTools` — which threw
'undefined is not an object (evaluating allowedTools)' during plugin load.
Result: no hooks/tools registered, all ctx_* tools dead, empty MC runtime log,
migration churn each boot.

The entry module must export ONLY default (the plugin factory). Move the helper
to a sibling module (hidden-agent-config.ts) where it can still be exported and
unit-tested without being mis-invoked as a factory. index-refresh.test.ts now
imports it from there.

Verified: dist export keys = ['default']; full plugin suite green (the lone
tui-config ordering flake is pre-existing on master); tsc + lint clean.
@iceteaSA
iceteaSA force-pushed the fix-plugin-entry-export branch from bc4adce to d0a731f Compare June 23, 2026 20:20
@alfonso-magic-context

Copy link
Copy Markdown
Collaborator

Thanks for catching this and for the clear write-up — the root cause you identified (OpenCode's legacy loader invoking every entry-module export as a plugin factory) is exactly right.

It's already resolved on master: the entry now uses the v1 plugin-object shape (export default { id, server }), which makes OpenCode take the readV1Plugin path and skip the Object.values(mod) export scan entirely, and buildHiddenAgentConfig is imported from a helper module rather than exported from the entry. So the failing path can't trigger regardless of which export the loader sees.

Closing as already-fixed, but the diagnosis was valuable — appreciate it.

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.

2 participants