Bug: opencode plugin hooks never fire — wrong hook names
Summary
The opencode plugin (src/plugins/opencode/plugin.js) registers session.created and tui.prompt.append as named hook keys, but opencode's plugin API does not support these as trigger-able hooks. The flag file is never written and the per-turn reinforcement never fires.
Root cause
opencode's plugin system (@opencode-ai/plugin) defines a Hooks interface with specific named hooks that can be triggered via plugin.trigger(name, input, output). The supported hooks are:
event (catch-all event bus listener)
chat.message
chat.params
chat.headers
command.execute.before
tool.execute.before / tool.execute.after
shell.env
experimental.chat.system.transform
experimental.chat.messages.transform
experimental.session.compacting
experimental.compaction.autocontinue
session.created and tui.prompt.append are not in this list. They exist as bus events (for internal UI use) but are never dispatched to plugins via the trigger() mechanism.
Additionally, opencode's trigger pattern is (input, output) => Promise<void> with mutation on output — return values are discarded. The current plugin returns { append: ... } which is silently ignored.
Evidence
- Plugin loads successfully (confirmed in logs:
service=plugin path=file:///...plugin.js loading plugin)
session.created bus event is published but never dispatched to plugin hooks
tui.prompt.append is a TUI input-field event (inserts text into prompt widget), not a pre-send hook
- Tested with opencode 1.15.4 on Linux
Fix
The correct hooks to use:
- Session init: Run in the plugin factory function (which executes once per session at load time)
- User prompt interception: Use
chat.message hook (input, output) => Promise<void> — output.parts contains text parts with the user's message
- System prompt injection: Use
experimental.chat.system.transform hook (input, output) => Promise<void> — push reinforcement line to output.system array
Patch
export const CavemanPlugin = async (_ctx) => {
// Initialize flag at plugin load time (equivalent to session start).
const mode = getDefaultMode();
if (mode === 'off') {
try { if (existsSync(flagPath)) unlinkSync(flagPath); } catch (e) {}
} else {
safeWriteFlag(flagPath, mode);
}
return {
'chat.message': async (_input, output) => {
if (!output || !output.parts) return;
for (const part of output.parts) {
if (part && part.type === 'text' && part.text) {
const change = parseModeChange(part.text);
if (change) applyModeChange(change);
}
}
},
'experimental.chat.system.transform': async (_input, output) => {
const active = readFlag(flagPath);
if (active && !INDEPENDENT_MODES.has(active)) {
output.system.push(reinforcementLine(active));
}
},
};
};
Tested with
- opencode 1.15.4
opencode run headless mode
- Verified: flag init ✓,
/caveman ultra ✓, stop caveman ✓, system prompt injection ✓
Platform
Bug: opencode plugin hooks never fire — wrong hook names
Summary
The opencode plugin (
src/plugins/opencode/plugin.js) registerssession.createdandtui.prompt.appendas named hook keys, but opencode's plugin API does not support these as trigger-able hooks. The flag file is never written and the per-turn reinforcement never fires.Root cause
opencode's plugin system (
@opencode-ai/plugin) defines aHooksinterface with specific named hooks that can be triggered viaplugin.trigger(name, input, output). The supported hooks are:event(catch-all event bus listener)chat.messagechat.paramschat.headerscommand.execute.beforetool.execute.before/tool.execute.aftershell.envexperimental.chat.system.transformexperimental.chat.messages.transformexperimental.session.compactingexperimental.compaction.autocontinuesession.createdandtui.prompt.appendare not in this list. They exist as bus events (for internal UI use) but are never dispatched to plugins via thetrigger()mechanism.Additionally, opencode's trigger pattern is
(input, output) => Promise<void>with mutation onoutput— return values are discarded. The current plugin returns{ append: ... }which is silently ignored.Evidence
service=plugin path=file:///...plugin.js loading plugin)session.createdbus event is published but never dispatched to plugin hookstui.prompt.appendis a TUI input-field event (inserts text into prompt widget), not a pre-send hookFix
The correct hooks to use:
chat.messagehook(input, output) => Promise<void>—output.partscontains text parts with the user's messageexperimental.chat.system.transformhook(input, output) => Promise<void>— push reinforcement line tooutput.systemarrayPatch
Tested with
opencode runheadless mode/caveman ultra✓,stop caveman✓, system prompt injection ✓Platform