Summary
plugin/claude-code/scripts/user-prompt-submit.{ps1,sh} emits a systemMessage instructing the agent, as a "CRITICAL FIRST ACTION", to run a ToolSearch with a hardcoded select: list of mcp__engram__* tool names.
When Engram is installed as a Claude Code plugin, Claude Code namespaces the plugin's MCP tools as mcp__plugin_<plugin>_<server>__* — i.e. mcp__plugin_engram_engram__mem_save. The hardcoded mcp__engram__* names therefore match nothing, and the mandated first action is a guaranteed no-op on every single prompt.
The names are correct for the other installation mode (Engram registered directly as an MCP server, where the server is literally named engram). The script is shared by both paths but written for only one.
Reproduction
- Install Engram via the plugin marketplace only — no direct
mcpServers.engram entry for the project.
- Start a session and send any prompt.
- The hook injects:
CRITICAL FIRST ACTION — Execute this ToolSearch NOW before responding to the user:
select:mcp__engram__mem_save,mcp__engram__mem_search,mcp__engram__mem_context,...
- The tools actually present in the session are
mcp__plugin_engram_engram__mem_save, mcp__plugin_engram_engram__mem_search, … so the selector resolves to zero tools.
Location
plugin/claude-code/scripts/user-prompt-submit.ps1 — Write-ToolSearchMessage, the $message string (line ~22)
plugin/claude-code/scripts/user-prompt-submit.sh — same hardcoded list
plugin/codex/scripts/user-prompt-submit.sh — same hardcoded list
Verified present on the default branch as of today, so v1.20.0 does not fix this — I checked the file contents at HEAD via the GitHub contents API, not just my locally cached 0.1.1 copy.
Why this has probably gone unnoticed
The bug is masked whenever Engram is also registered as a plain MCP server for the project — for example when installed through gentle-ai or a similar meta-installer that runs engram setup <agent> and writes an mcpServers.engram entry. In that configuration mcp__engram__* genuinely exists, the selector matches, and the hook works.
So the failure is specific to the plugin-only configuration, which is the marketplace's own primary distribution path.
Impact
Not a correctness bug — the core memory tools are already available without any ToolSearch, so memory still works. The cost is:
- Wasted work on every turn. The message is phrased as a mandatory first action, so a compliant agent spends a tool call resolving an empty selector before it can respond. With one of my own installs showing
usageCount: 6491, that is a lot of turns.
- It teaches agents to disregard the hook. An instruction that is emphatic ("CRITICAL FIRST ACTION") and verifiably inert is worse than no instruction: the next directive from the same channel carries less weight.
Suggested fix
The robust fix is not to correct the prefix — either literal is wrong in one of the two supported installation modes. The hook should not hardcode the tool namespace at all, because the script cannot know which mode it was installed under.
Options, roughly in order of preference:
- Emit both namespaces in the
select: list (mcp__engram__mem_save,mcp__plugin_engram_engram__mem_save,…). Unmatched names are harmless, and it is a one-line change that works in both modes.
- Detect the mode — the plugin hook runs with
CLAUDE_PLUGIN_ROOT set, which distinguishes a plugin invocation from a direct MCP-server install, and pick the prefix accordingly.
- Drop the
select: list entirely and keep only the second half of the message ("call mem_context to check for prior session history"). The core tools are loaded without ToolSearch anyway, so the selector may be buying nothing even when it does match — worth measuring before keeping it.
A note on the underlying shape
The hook maintains a hardcoded parallel list of the MCP server's own tool surface. That list has to agree with the server's actual tool names, and nothing enforces the agreement — so it silently drifts the moment the namespace changes, which is exactly what happened here. If the list stays, deriving it from the server's tool registry (or generating it at build time from the same source the server uses to register them) would make this class of drift impossible rather than merely fixed once.
Environment
- Engram plugin
0.1.1 from the engram marketplace; binary 1.19.0 (1.20.0 available — checked, not fixed there either)
- Claude Code on Windows (
user-prompt-submit.ps1 path); the .sh variants carry the identical list
- Reproduced in a project with
enabledPlugins: {"engram@engram": true} and no mcpServers.engram entry
Summary
plugin/claude-code/scripts/user-prompt-submit.{ps1,sh}emits asystemMessageinstructing the agent, as a "CRITICAL FIRST ACTION", to run aToolSearchwith a hardcodedselect:list ofmcp__engram__*tool names.When Engram is installed as a Claude Code plugin, Claude Code namespaces the plugin's MCP tools as
mcp__plugin_<plugin>_<server>__*— i.e.mcp__plugin_engram_engram__mem_save. The hardcodedmcp__engram__*names therefore match nothing, and the mandated first action is a guaranteed no-op on every single prompt.The names are correct for the other installation mode (Engram registered directly as an MCP server, where the server is literally named
engram). The script is shared by both paths but written for only one.Reproduction
mcpServers.engramentry for the project.mcp__plugin_engram_engram__mem_save,mcp__plugin_engram_engram__mem_search, … so the selector resolves to zero tools.Location
plugin/claude-code/scripts/user-prompt-submit.ps1—Write-ToolSearchMessage, the$messagestring (line ~22)plugin/claude-code/scripts/user-prompt-submit.sh— same hardcoded listplugin/codex/scripts/user-prompt-submit.sh— same hardcoded listVerified present on the default branch as of today, so v1.20.0 does not fix this — I checked the file contents at
HEADvia the GitHub contents API, not just my locally cached0.1.1copy.Why this has probably gone unnoticed
The bug is masked whenever Engram is also registered as a plain MCP server for the project — for example when installed through
gentle-aior a similar meta-installer that runsengram setup <agent>and writes anmcpServers.engramentry. In that configurationmcp__engram__*genuinely exists, the selector matches, and the hook works.So the failure is specific to the plugin-only configuration, which is the marketplace's own primary distribution path.
Impact
Not a correctness bug — the core memory tools are already available without any
ToolSearch, so memory still works. The cost is:usageCount: 6491, that is a lot of turns.Suggested fix
The robust fix is not to correct the prefix — either literal is wrong in one of the two supported installation modes. The hook should not hardcode the tool namespace at all, because the script cannot know which mode it was installed under.
Options, roughly in order of preference:
select:list (mcp__engram__mem_save,mcp__plugin_engram_engram__mem_save,…). Unmatched names are harmless, and it is a one-line change that works in both modes.CLAUDE_PLUGIN_ROOTset, which distinguishes a plugin invocation from a direct MCP-server install, and pick the prefix accordingly.select:list entirely and keep only the second half of the message ("callmem_contextto check for prior session history"). The core tools are loaded withoutToolSearchanyway, so the selector may be buying nothing even when it does match — worth measuring before keeping it.A note on the underlying shape
The hook maintains a hardcoded parallel list of the MCP server's own tool surface. That list has to agree with the server's actual tool names, and nothing enforces the agreement — so it silently drifts the moment the namespace changes, which is exactly what happened here. If the list stays, deriving it from the server's tool registry (or generating it at build time from the same source the server uses to register them) would make this class of drift impossible rather than merely fixed once.
Environment
0.1.1from theengrammarketplace; binary1.19.0(1.20.0 available — checked, not fixed there either)user-prompt-submit.ps1path); the.shvariants carry the identical listenabledPlugins: {"engram@engram": true}and nomcpServers.engramentry