thedotmack/claude-mem · critical · Error
Hand-edited shell string detected in ${filePath} (mcp-search
Error message
Hand-edited shell string detected in ${filePath} (mcp-search). It no longer matches src/build/hook-shell-template.ts. Regenerate via `node scripts/build-hooks.js --write-shell-templates` after an intentional generator change. What it means
A build-time guard in verifyShellTemplateCanonical (scripts/build-hooks.js). It checks every settings/config file listed in shellTemplateManifest under kind:'mcp': the mcpServers['mcp-search'].args[1] shell launcher string must byte-match the canonical string produced by src/build/hook-shell-template.ts. A mismatch means someone hand-edited the committed launcher instead of changing the generator, which would silently break hosts that rely on the canonical launcher (Rule A). The error message names the fix command verbatim.
Source
Thrown at scripts/build-hooks.js:180
const moduleSource = bundled.outputFiles[0].text;
const dataUrl = 'data:text/javascript;base64,' + Buffer.from(moduleSource).toString('base64');
const { buildShellCommand, buildCodexWindowsCommand } = await import(dataUrl);
const manifest = shellTemplateManifest(buildShellCommand, buildCodexWindowsCommand);
// The regeneration mode the mismatch errors point at: after an intentional
// generator change, rewrite the committed launcher strings from the same
// manifest the verifier checks, so the two can never drift.
const writeMode = process.argv.includes('--write-shell-templates');
for (const [filePath, spec] of Object.entries(manifest)) {
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
let dirty = false;
if (spec.kind === 'mcp') {
const actual = parsed.mcpServers?.['mcp-search']?.args?.[1] ?? '';
if (actual !== spec.command) {
if (!writeMode) {
throw new Error(
`Hand-edited shell string detected in ${filePath} (mcp-search). It no longer matches src/build/hook-shell-template.ts. ` +
`Regenerate via \`node scripts/build-hooks.js --write-shell-templates\` after an intentional generator change.`
);
}
parsed.mcpServers['mcp-search'].args[1] = spec.command;
dirty = true;
}
} else {
for (const [dottedPath, expected] of Object.entries(spec.commands)) {
const entry = hookEntryByPath(parsed, dottedPath);
const expectedCommand = typeof expected === 'string' ? expected : expected.command;
const actual = entry?.command ?? null;
if (actual !== expectedCommand) {
if (!writeMode || !entry) {
throw new Error(
`Hand-edited shell string detected in ${filePath} (${dottedPath}). It no longer matches src/build/hook-shell-template.ts. ` +
`Regenerate via \`node scripts/build-hooks.js --write-shell-templates\` after an intentional generator change.`
);View on GitHub (pinned to d768ba3643)
Solutions
- If the generator change was intentional, run: node scripts/build-hooks.js --write-shell-templates — this rewrites args[1] across all manifest files from the canonical source and commits cleanly.
- If the generator did NOT change, revert the hand edit in the named file (git checkout -- <filePath>) so it matches the committed canonical string.
- Re-run node scripts/build-hooks.js to confirm the verifier now passes (prints '✓ Rule A shell templates match the canonical generator').
- Going forward, never edit mcp-search launcher strings by hand; change src/build/hook-shell-template.ts and regenerate.
Example fix
# before: plugin/.mcp.json mcp-search args[1] edited by hand "args": ["/usr/bin/env", "bash -c '...hand-tweaked...'"] # regenerate from the canonical generator node scripts/build-hooks.js --write-shell-templates # after: args[1] matches src/build/hook-shell-template.ts output byte-for-byte
Defensive patterns
Strategy: validation
Validate before calling
// CI/pre-commit: never let a hand-edited launcher ship. // Run the verifier in check mode (no --write-shell-templates) and fail the build: // node scripts/build-hooks.js // It throws on drift; exit code non-zero blocks the commit.
Type guard
// manifest entry shape for the mcp kind
function isMcpSpec(spec): spec is { kind: 'mcp'; command: string } {
return spec && spec.kind === 'mcp' && typeof spec.command === 'string';
} Try / catch
// Not a runtime error — build-time guard. Run regenerator on drift: // node scripts/build-hooks.js --write-shell-templates // then commit the regenerated files. Never catch/suppress in CI.
Prevention
- Never edit mcp-search args[1] by hand; change src/build/hook-shell-template.ts and regenerate.
- Add scripts/build-hooks.js to pre-commit and CI so drift never reaches main.
- After merges, run --write-shell-templates if the template file changed.
When it happens
Trigger: Running node scripts/build-hooks.js (or npm run build-and-sync) when a committed file like plugin/.mcp.json or .mcp.json has an mcp-search args[1] value that differs from buildShellCommand(...). Happens after editing the template without regenerating, after a merge conflict resolved by hand, or after a generator change that wasn't propagated with --write-shell-templates.
Common situations: Developer edits the shell launcher in plugin/.mcp.json directly to test a path, then forgets to revert. A PR changes src/build/hook-shell-template.ts but the author only ran the build, not the regenerator. Branch merge brings in an old launcher string.
Related errors
- Hand-edited shell string detected in ${filePath} (${dottedPa
- Hand-edited Windows shell string detected in ${filePath} (${
- plugin/scripts/bun-runner.js is missing fixBrokenScriptPath
- plugin/scripts/bun-runner.js uses optional chaining (?.) or
- mcp-server.cjs contains a Bun-only ${bunRequireMatch[0]} cal
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/6f53dc710be5a8a2.
Report an issue: GitHub.