#941·ponytail

Plugin fails to load on OpenCode v2: v1 plugin API (default export is a function, not an object)

Author: ArhamurrahemeenCreated Sep 26, 2026Updated Sep 30, 2026

Plugin fails to load on OpenCode v2 (v1 plugin API)

@dietrichgebert/[email protected] cannot be loaded by OpenCode v2. The package ships only a v1 plugin, and the v2 plugin module loader rejects it:

msg="loading plugin" id=@dietrichgebert/ponytail
  entrypoint=.../node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs
failed to load plugin target=@dietrichgebert/ponytail
  cause="PluginModule.LoadError: Plugin must export a default definition with an id
  and an effect or setup function. (cause: SchemaError(Expected object at ["default"])))"

Reproduced on opencode v2.0.18, Windows 11, Node v24.19.0. Adding the package to opencode.json plugins makes opencode resolve and import the entrypoint, then fail schema validation on every load.

Root cause

.opencode/plugins/ponytail.mjs default-exports an async function that returns a hooks object:

javascript
export default async ({ client } = {}) => {
  return {
    config: async (config) => { /* pushes commands + skills */ },
    'experimental.chat.system.transform': async (input, output) => { /* injects ruleset */ },
    'command.execute.before': async (input) => { /* persists /ponytail mode */ },
  };
};

v2 requires a default-exported object with id + setup (or effect), which is what Expected object at ["default"] reports. The v1 hook names are also gone in v2:

v1 (ponytail) v2 equivalent
config hook pushing skills.paths ctx.skill.transform(...)
config hook pushing config.command ctx.command.transform(...)
experimental.chat.system.transform ctx.session.hook("context", ...)
command.execute.before no direct equivalent

package.json main and exports["."] both point at the v1 file, and the only other plugin-shaped file is ponytail-frontmatter.cjs (a helper). There is no v2 entrypoint to fall back to.

Also stale

The header comment in ponytail.mjs says:

javascript
// OpenCode loads this as a server plugin — add it to your opencode.json:
//   { "plugin": ["@dietrichgebert/ponytail"] }

plugin (singular) is the v1 key; v2 uses plugins (plural). Following the comment as written is silently ignored by v2.

Suggested fix

Export a v2 definition, keeping the existing v1 export for older opencode:

javascript
export const ponytailV2 = {
  id: "ponytail",
  async setup(ctx) { /* ctx.session.hook("context"), ctx.command.transform, ctx.skill.transform */ },
}

and point exports at a small dispatcher, or ship a v2 entrypoint selected by the host's version. A working ~150-line v2 shim that reuses hooks/ponytail-instructions.js and hooks/ponytail-config.js unchanged was enough to restore system-prompt injection, the /ponytail <mode> command, and the six bundled skills, so the port looks small.

Happy to open a PR if a v2 branch is preferred over a shim.