Feature hasn't been suggested before.
Describe the enhancement you want to request
The chat.message plugin hook fires before the message is sent to the LLM (confirmed by reading packages/opencode/src/session/prompt.ts line ~1075). Plugins can mutate output.parts in-place to modify the message content. However, there is no documented or supported way to block/cancel a message entirely.
Currently:
throw new Error() — stops plugin execution but the message still goes through (the Effect promise rejects but OpenCode silently catches it)
return { noReply: true } — return value is ignored by the trigger() function
- Mutating
output.parts — works as a workaround to replace message content, but the original message is still "sent" (just with replaced content)
Proposed solution
Add an official mechanism to cancel a user message from chat.message hooks. Options:
- Output flag: Allow
output.cancel = true or output.blocked = true to prevent the message from being sent to the LLM. The UI should display a notification explaining the block.
- Throw-to-block: Make
throw new Error("reason") in chat.message actually cancel the message and display the error to the user (similar to how tool.execute.before throw blocks tool execution).
- Return value: Check the return value of
chat.message hooks — { cancel: true, reason: "..." } cancels the message.
Use case
Security plugins need to scan user prompts for secrets, PII, and prompt injection attacks before they reach the LLM. Without a proper blocking mechanism:
- Secrets pasted in prompts reach the model even if detected
- The only workaround is mutating
output.parts to replace content — this works but the user gets no clear UI feedback that their message was blocked (the LLM just receives a synthetic message and responds accordingly)
This is equivalent to Claude Code's UserPromptSubmit hook which can return { decision: "block", reason: "..." } to prevent a prompt from being sent.
Current workaround
Mutate output.parts in-place to replace the user's message with a security warning:
async 'chat.message'(input, output) {
const text = output.parts
.filter(p => p.type === 'text' && !p.synthetic)
.map(p => p.text || '').join('\n');
if (containsSecret(text)) {
const firstPart = output.parts[0] || {};
output.parts.length = 0;
output.parts.push({
...firstPart,
type: 'text',
text: 'Message blocked: secret detected.',
synthetic: true,
});
}
}
This works because output.parts is the same reference as resolvedParts in prompt.ts, so mutations propagate. But it's fragile and undocumented.
Related issues
Additional context
- ai-guardian uses this workaround successfully for OpenCode integration
- The
trigger() function in packages/opencode/src/plugin/index.ts calls hooks sequentially and returns output — it could check for a cancel flag before returning
Feature hasn't been suggested before.
Describe the enhancement you want to request
The
chat.messageplugin hook fires before the message is sent to the LLM (confirmed by readingpackages/opencode/src/session/prompt.tsline ~1075). Plugins can mutateoutput.partsin-place to modify the message content. However, there is no documented or supported way to block/cancel a message entirely.Currently:
throw new Error()— stops plugin execution but the message still goes through (the Effect promise rejects but OpenCode silently catches it)return { noReply: true }— return value is ignored by thetrigger()functionoutput.parts— works as a workaround to replace message content, but the original message is still "sent" (just with replaced content)Proposed solution
Add an official mechanism to cancel a user message from
chat.messagehooks. Options:output.cancel = trueoroutput.blocked = trueto prevent the message from being sent to the LLM. The UI should display a notification explaining the block.throw new Error("reason")inchat.messageactually cancel the message and display the error to the user (similar to howtool.execute.beforethrow blocks tool execution).chat.messagehooks —{ cancel: true, reason: "..." }cancels the message.Use case
Security plugins need to scan user prompts for secrets, PII, and prompt injection attacks before they reach the LLM. Without a proper blocking mechanism:
output.partsto replace content — this works but the user gets no clear UI feedback that their message was blocked (the LLM just receives a synthetic message and responds accordingly)This is equivalent to Claude Code's
UserPromptSubmithook which can return{ decision: "block", reason: "..." }to prevent a prompt from being sent.Current workaround
Mutate
output.partsin-place to replace the user's message with a security warning:This works because
output.partsis the same reference asresolvedPartsinprompt.ts, so mutations propagate. But it's fragile and undocumented.Related issues
permission.askplugin hookpermission.askplugin hook (duplicate)Additional context
trigger()function inpackages/opencode/src/plugin/index.tscalls hooks sequentially and returnsoutput— it could check for a cancel flag before returning