{"record":{"id":"c5626d62f17832d3","repo":"thedotmack/claude-mem","slug":"hand-edited-shell-string-detected-in-filepath-c5626d","errorCode":null,"errorMessage":"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.","messagePattern":"Hand-edited shell string detected in (.+?) \\((.+?)\\)\\. 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":195,"sourceCode":"      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            );\n          }\n          entry.command = expectedCommand;\n          dirty = true;\n        }\n        if (typeof expected !== 'string') {\n          const actualWindows = entry?.commandWindows ?? null;\n          if (actualWindows !== expected.commandWindows) {\n            if (!writeMode || !entry) {\n              throw new Error(\n                `Hand-edited Windows 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              );\n            }\n            entry.commandWindows = expected.commandWindows;\n            dirty = true;","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/scripts/build-hooks.js#L177-L213","documentation":"Same Rule A canonicality guard as [2], but for hook entries of kind other than 'mcp'. For each dottedPath (event.groupIdx.hookIdx) in the manifest, it resolves the hook via hookEntryByPath and compares entry.command to the expected command string from the generator. A mismatch on the Unix/macOS command field throws this. Fix is identical: regenerate or revert.","triggerScenarios":"A committed hooks JSON (e.g. plugin/hooks/settings.json, .claude/settings.json) has a hooks[event][group].hooks[i].command string that differs from what buildShellCommand produces for that hook. Triggered during build verification. Also thrown when writeMode is on but the hook entry is missing entirely (entry is null), because there's nothing to write into.","commonSituations":"Hand-editing a hook command to point at a different script. Adding a new hook in the manifest generator but forgetting --write-shell-templates. A hook entry deleted from the JSON so hookEntryByPath returns null while the manifest still lists its dottedPath.","solutions":["Run node scripts/build-hooks.js --write-shell-templates to rewrite entry.command from the generator.","If writeMode reports the entry is missing (!entry branch), restore the hook entry in the JSON first (it must exist for the regenerator to populate), then re-run.","If unintentional, git checkout -- <filePath> to restore the canonical committed launcher.","Re-run the build to confirm '✓ Rule A shell templates match the canonical generator'."],"exampleFix":"# before: hook command drifted from generator\n\"command\": \"/old/launcher/path script.js\"\n\nnode scripts/build-hooks.js --write-shell-templates\n\n# after: command rewritten to canonical src/build/hook-shell-template.ts output","handlingStrategy":"validation","validationCode":"// Pre-commit verification: run the same verifier the build runs.\n// node scripts/build-hooks.js  -> exits non-zero if any hook command drifted.","typeGuard":"function hasCommand(entry): entry is { command: string } {\n  return entry != null && typeof entry.command === 'string';\n}","tryCatchPattern":"// Build-time only. On drift either:\n//   node scripts/build-hooks.js --write-shell-templates   (accept generator)\n//   git checkout -- <file>                                 (reject hand edit)\n// Do not suppress in CI.","preventionTips":["Edit src/build/hook-shell-template.ts, never the generated hook JSONs.","When adding a hook, update the manifest generator so it appears in shellTemplateManifest.","Run the build locally before pushing so CI never sees drift."],"tags":["build","verification","shell-template","rule-a","hooks"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}