Summary
processFilePathForAgentsInjection in src/hooks/directory-agents-injector/injector.ts unconditionally appends to input.output.output without checking whether it is a string first. When an MCP tool (e.g. Serena via mcpm) returns a response where output.output is undefined or a non-string type, the += operator throws a TypeError, crashing the hook and breaking the session.
Version
oh-my-opencode 3.17.14 (latest as of 2026-05-05)
Steps to Reproduce
- Install any MCP server via mcpm (e.g. Serena)
- Trigger a
Read-equivalent tool call through the MCP bridge
- The MCP response arrives with
output.output as undefined (non-string) — this happens when the MCP SDK serialization format changed but the bridge wasn't updated
directoryAgentsInjectorHook fires on tool.execute.after for the read tool
processFilePathForAgentsInjection is called with output.output === undefined
- Line 51 of
injector.ts executes: input.output.output += \...`→TypeError: Cannot read properties of undefined (reading 'slice')` (or similar)
Root Cause
src/hooks/directory-agents-injector/injector.ts, line 51:
input.output.output += `\n\n[Directory Context: ${agentsPath}]\n${result}${truncationNotice}`;
There is no guard before this line. Other hooks in the codebase already use the correct pattern:
// src/hooks/tool-output-truncator.ts (line ~69686 in dist)
if (typeof output.output !== "string") return;
The ToolExecuteOutput interface in hook.ts declares output: string, but this is a TypeScript-only contract — at runtime, MCP tool responses can deliver output.output as undefined, an object, or any other type depending on the MCP server and bridge version.
Fix
Add a type guard at the top of the loop body in injector.ts, consistent with the pattern used in tool-output-truncator and other hooks:
// injector.ts — processFilePathForAgentsInjection
export async function processFilePathForAgentsInjection(input: { ... }): Promise<void> {
// Guard: output.output may be non-string at runtime (MCP bridge format changes)
if (typeof input.output.output !== "string") return;
const resolved = resolveFilePath(input.ctx.directory, input.filePath);
// ... rest of function
Alternatively, guard only the append site:
if (typeof input.output.output === "string") {
input.output.output += `\n\n[Directory Context: ${agentsPath}]\n${result}${truncationNotice}`;
}
The same guard is also needed at line 69951 in dist/index.js (the directoryReadmeInjector equivalent, processReadmeInjection).
Impact
- Severity: High — crashes the hook silently or visibly for any user running MCP servers through a bridge (mcpm, etc.) where the response format doesn't guarantee
output.output is a string
- Workaround: None without patching
dist/index.js locally
- Affected users: Anyone using Serena, or any MCP tool routed through mcpm's bridge, with oh-my-opencode 3.17.14
Additional Context
The crash was originally misattributed to mcpm's fastmcp version mismatch (see pathintegral-institute/mcpm.sh#331). After patching mcpm locally, the crash persisted — tracing it to this hook in oh-my-opencode's dist.
The directoryReadmeInjector hook likely has the same vulnerability at the equivalent append site.
Summary
processFilePathForAgentsInjectioninsrc/hooks/directory-agents-injector/injector.tsunconditionally appends toinput.output.outputwithout checking whether it is a string first. When an MCP tool (e.g. Serena via mcpm) returns a response whereoutput.outputisundefinedor a non-string type, the+=operator throws aTypeError, crashing the hook and breaking the session.Version
oh-my-opencode
3.17.14(latest as of 2026-05-05)Steps to Reproduce
Read-equivalent tool call through the MCP bridgeoutput.outputasundefined(non-string) — this happens when the MCP SDK serialization format changed but the bridge wasn't updateddirectoryAgentsInjectorHookfires ontool.execute.afterfor thereadtoolprocessFilePathForAgentsInjectionis called withoutput.output === undefinedinjector.tsexecutes:input.output.output += \...`→TypeError: Cannot read properties of undefined (reading 'slice')` (or similar)Root Cause
src/hooks/directory-agents-injector/injector.ts, line 51:There is no guard before this line. Other hooks in the codebase already use the correct pattern:
The
ToolExecuteOutputinterface inhook.tsdeclaresoutput: string, but this is a TypeScript-only contract — at runtime, MCP tool responses can deliveroutput.outputasundefined, an object, or any other type depending on the MCP server and bridge version.Fix
Add a type guard at the top of the loop body in
injector.ts, consistent with the pattern used intool-output-truncatorand other hooks:Alternatively, guard only the append site:
The same guard is also needed at line 69951 in
dist/index.js(thedirectoryReadmeInjectorequivalent,processReadmeInjection).Impact
output.outputis a stringdist/index.jslocallyAdditional Context
The crash was originally misattributed to mcpm's fastmcp version mismatch (see pathintegral-institute/mcpm.sh#331). After patching mcpm locally, the crash persisted — tracing it to this hook in oh-my-opencode's dist.
The
directoryReadmeInjectorhook likely has the same vulnerability at the equivalent append site.