External plugins fail to load: TypeError: fn is not a function #9296

Open
opened 2026-02-16 18:12:07 -05:00 by yindo · 0 comments
Owner

Originally created by @intergrado-cg on GitHub (Feb 13, 2026).

Originally assigned to: @rekram1-node on GitHub.

Description

External plugins fail to load in OpenCode with the error: TypeError: fn is not a function

This occurs because the plugin loading code iterates over ALL exports from a plugin module and tries to call each as a Plugin function. External plugins typically export many things besides the Plugin function (helpers, constants, types), causing the error when non-function exports are encountered.

Plugins

Any external plugin (e.g., opencode-wakatime or custom local plugins)

OpenCode version

v1.1.65 (and likely other versions)

Steps to reproduce

  1. Create a simple external plugin that exports additional things besides the Plugin function:
// test-plugin.ts
import type { Plugin } from \"@opencode-ai/plugin\";

export const MyPlugin: Plugin = async (ctx) => {
  return {
    tool: {
      hello: {
        description: \"Say hello\",
        args: {},
        async execute() { return \"Hello!\"; }
      }
    }
  };
};

export default MyPlugin;

// Additional exports that cause the bug
export const SOME_CONSTANT = \"hello\";
export const someObject = { foo: \"bar\" };
  1. Reference it in opencode config:
{
  \"plugin\": [\"file:///path/to/test-plugin/dist/index.js\"]
}
  1. Run opencode with the plugin configured

Expected: Plugin loads successfully
Actual: Error: TypeError: fn is not a function

Root Cause

In packages/opencode/src/plugin/index.ts, the plugin loading loop at line ~87 iterates over all exports:

for (const [_name, fn] of Object.entries<PluginInstance>(mod)) {
  if (seen.has(fn)) continue  // Only checks for duplicates
  seen.add(fn)
  const init = await fn(input)  // Fails when fn is not a function
  hooks.push(init)
}

Fix

Add a type check to skip non-function exports:

for (const [_name, fn] of Object.entries<PluginInstance>(mod)) {
  if (typeof fn !== \"function\") continue  // Skip non-functions
  if (seen.has(fn)) continue
  seen.add(fn)
  const init = await fn(input)
  hooks.push(init)
}

OS

Linux

Terminal

Any

Originally created by @intergrado-cg on GitHub (Feb 13, 2026). Originally assigned to: @rekram1-node on GitHub. ## Description External plugins fail to load in OpenCode with the error: `TypeError: fn is not a function` This occurs because the plugin loading code iterates over ALL exports from a plugin module and tries to call each as a Plugin function. External plugins typically export many things besides the Plugin function (helpers, constants, types), causing the error when non-function exports are encountered. ## Plugins Any external plugin (e.g., `opencode-wakatime` or custom local plugins) ## OpenCode version v1.1.65 (and likely other versions) ## Steps to reproduce 1. Create a simple external plugin that exports additional things besides the Plugin function: ```typescript // test-plugin.ts import type { Plugin } from \"@opencode-ai/plugin\"; export const MyPlugin: Plugin = async (ctx) => { return { tool: { hello: { description: \"Say hello\", args: {}, async execute() { return \"Hello!\"; } } } }; }; export default MyPlugin; // Additional exports that cause the bug export const SOME_CONSTANT = \"hello\"; export const someObject = { foo: \"bar\" }; ``` 2. Reference it in opencode config: ```json { \"plugin\": [\"file:///path/to/test-plugin/dist/index.js\"] } ``` 3. Run opencode with the plugin configured **Expected:** Plugin loads successfully **Actual:** Error: `TypeError: fn is not a function` ## Root Cause In `packages/opencode/src/plugin/index.ts`, the plugin loading loop at line ~87 iterates over all exports: ```typescript for (const [_name, fn] of Object.entries<PluginInstance>(mod)) { if (seen.has(fn)) continue // Only checks for duplicates seen.add(fn) const init = await fn(input) // Fails when fn is not a function hooks.push(init) } ``` ## Fix Add a type check to skip non-function exports: ```typescript for (const [_name, fn] of Object.entries<PluginInstance>(mod)) { if (typeof fn !== \"function\") continue // Skip non-functions if (seen.has(fn)) continue seen.add(fn) const init = await fn(input) hooks.push(init) } ``` ## OS Linux ## Terminal Any
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#9296