{"record":{"id":"6f53dc710be5a8a2","repo":"thedotmack/claude-mem","slug":"hand-edited-shell-string-detected-in-filepath","errorCode":null,"errorMessage":"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.","messagePattern":"Hand-edited shell string detected in (.+?) \\(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\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"scripts/build-hooks.js","lineNumber":180,"sourceCode":"  const moduleSource = bundled.outputFiles[0].text;\n  const dataUrl = 'data:text/javascript;base64,' + Buffer.from(moduleSource).toString('base64');\n  const { buildShellCommand, buildCodexWindowsCommand } = await import(dataUrl);\n\n  const manifest = shellTemplateManifest(buildShellCommand, buildCodexWindowsCommand);\n\n  // The regeneration mode the mismatch errors point at: after an intentional\n  // generator change, rewrite the committed launcher strings from the same\n  // manifest the verifier checks, so the two can never drift.\n  const writeMode = process.argv.includes('--write-shell-templates');\n\n  for (const [filePath, spec] of Object.entries(manifest)) {\n    const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n    let dirty = false;\n    if (spec.kind === 'mcp') {\n      const actual = parsed.mcpServers?.['mcp-search']?.args?.[1] ?? '';\n      if (actual !== spec.command) {\n        if (!writeMode) {\n          throw new Error(\n            `Hand-edited shell string detected in ${filePath} (mcp-search). It no longer matches src/build/hook-shell-template.ts. ` +\n            `Regenerate via \\`node scripts/build-hooks.js --write-shell-templates\\` after an intentional generator change.`\n          );\n        }\n        parsed.mcpServers['mcp-search'].args[1] = spec.command;\n        dirty = true;\n      }\n    } else {\n      for (const [dottedPath, expected] of Object.entries(spec.commands)) {\n        const entry = hookEntryByPath(parsed, dottedPath);\n        const expectedCommand = typeof expected === 'string' ? expected : expected.command;\n        const actual = entry?.command ?? null;\n        if (actual !== expectedCommand) {\n          if (!writeMode || !entry) {\n            throw new Error(\n              `Hand-edited shell string detected in ${filePath} (${dottedPath}). It no longer matches src/build/hook-shell-template.ts. ` +\n              `Regenerate via \\`node scripts/build-hooks.js --write-shell-templates\\` after an intentional generator change.`\n            );","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/scripts/build-hooks.js#L162-L198","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: plugin/.mcp.json mcp-search args[1] edited by hand\n\"args\": [\"/usr/bin/env\", \"bash -c '...hand-tweaked...'\"]\n\n# regenerate from the canonical generator\nnode scripts/build-hooks.js --write-shell-templates\n\n# after: args[1] matches src/build/hook-shell-template.ts output byte-for-byte","handlingStrategy":"validation","validationCode":"// CI/pre-commit: never let a hand-edited launcher ship.\n// Run the verifier in check mode (no --write-shell-templates) and fail the build:\n//   node scripts/build-hooks.js\n// It throws on drift; exit code non-zero blocks the commit.","typeGuard":"// manifest entry shape for the mcp kind\nfunction isMcpSpec(spec): spec is { kind: 'mcp'; command: string } {\n  return spec && spec.kind === 'mcp' && typeof spec.command === 'string';\n}","tryCatchPattern":"// Not a runtime error — build-time guard. Run regenerator on drift:\n//   node scripts/build-hooks.js --write-shell-templates\n// then commit the regenerated files. Never catch/suppress in CI.","preventionTips":["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."],"tags":["build","verification","shell-template","rule-a","canonical"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}