Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneesstack72

Relationships

#3340 repo init/upgrade emits OpenCode V1-API audit plugin that fails to load on OpenCode 2

Opened by elem103 · 10/13/2026· Shipped 10/13/2026

Summary

swamp repo init / swamp repo upgrade (OpenCode tool support) writes <project>/.opencode/plugins/swamp-audit.ts against the OpenCode V1 plugin API. OpenCode 2 removed the V1 API, so the generated file fails to load and swamp's audit timeline silently stops receiving OpenCode bash invocations. The file's own header says "managed file — it will be overwritten on swamp upgrade", so every upgrade re-introduces the broken version.

Environment

  • swamp 20261013.004531.0-sha.1373120f
  • OpenCode v2.0.27
  • macOS (darwin)

Steps to reproduce

  1. swamp repo init --tool opencode (or swamp repo upgrade --tool opencode) in a git repo.
  2. Start/restart OpenCode 2 in that repo.
  3. OpenCode logs a warning and skips the plugin instead of loading it:
timestamp=... level=WARN message="failed to load plugin"
target=<project>/.opencode/plugins/swamp-audit.ts
cause="Cause([Fail(PluginModule.LoadError: Plugin must export a default definition
with an id and an effect or setup function.
(cause: SchemaError(Missing key at [\"default\"])))])"

No OpenCode bash entries reach swamp audit record --from-hook. No crash, but audit data is silently incomplete.

Root cause

The generated file uses a named export of a V1 factory that returns string-keyed hooks:

import type { Plugin } from "@opencode-ai/plugin";

export const SwampAudit: Plugin = async ({ directory }) => {
  return {
    "tool.execute.before": async (input, output) => { ... },
    "tool.execute.after": async (input) => { ... },
  };
};

OpenCode 2 requires a default-exported Plugin.define, importing Plugin from @opencode/plugin, and registers tool hooks via ctx.tool.hook("execute.before" | "execute.after", ...), with ctx.location.directory replacing the { directory } context argument. Because there is no default export, the module schema fails to load (hence SchemaError(Missing key at ["default"])).

Expected behaviour

The generated plugin loads cleanly on OpenCode 2 and records bash invocations.

Suggested fix

Emit the V2 shape in the generator (OpenCode 2). If OpenCode 1 support must be retained, emit an object that spreads Plugin.define(...) and also provides the V1 server() entry point (supported by OpenCode 1.18.29+). Keep the "managed file" header and re-run the writer on repo upgrade so existing projects get the V2 file.

Working local V2 port (verified: msg="loading plugin" with no subsequent failed to load plugin in opencode.log):

import { Plugin } from "@opencode/plugin";

const pendingCommands = new Map<string, string>();

export default Plugin.define({
  id: "swamp.audit",
  async setup(ctx) {
    await ctx.tool.hook("execute.before", (event) => {
      if (event.tool !== "bash") return;
      const command = (event.input as { command?: unknown } | undefined)?.command;
      if (typeof command === "string" && event.sessionID) {
        pendingCommands.set(event.sessionID, command);
      }
    });

    await ctx.tool.hook("execute.after", async (event) => {
      if (event.tool !== "bash") return;
      const command = pendingCommands.get(event.sessionID);
      pendingCommands.delete(event.sessionID);
      if (!command) return;

      try {
        const payload = JSON.stringify({
          tool_name: "bash",
          tool_input: { command },
          cwd: ctx.location.directory,
          session_id: event.sessionID,
        });
        const proc = Bun.spawn(
          ["swamp", "audit", "record", "--from-hook", "--tool", "opencode"],
          { stdin: new Blob([payload]) },
        );
        await proc.exited;
      } catch {
        // Must never throw — this is a hook
      }
    });
  },
});
02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 6 MOREREVIEW+ 6 MOREPR_LINKED+ 2 MORESESSION_SUMMARIZED

Shipped

10/13/2026, 3:26:45 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack7210/13/2026, 1:43:56 AM
Editable. Press Enter to edit.

stack72 commented 10/13/2026, 3:26:49 AM

Thanks @elem103 for reporting this! We shipped: Make the managed OpenCode audit plugin record shell commands on OpenCode 1.3.4+ and OpenCode 2 from one file: keep the named V1 SwampAudit export, add a default export object with id, server (SwampAudit) and an OpenCode 2 setup that hooks execute.after on the shell tool, with a shared record helper and type-only imports. Supported floor OpenCode 1.3.4 (older 1.x cannot load any file OpenCode 2 accepts). Fix the changed-file path the upgrade reports, make the doctor flag a plugin that predates OpenCode 2 support with a version-aware hint, and document the range.. The fix has been merged and a release is on its way. We appreciate your contribution to swamp.

Sign in to post a ripple.