{"record":{"id":"e38a1486009fbdab","repo":"affaan-m/ECC","slug":"invalid-claude-plugin-root-expected-a-string","errorCode":null,"errorMessage":"Invalid Claude plugin root: expected a string","messagePattern":"Invalid Claude plugin root: expected a string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":239,"sourceCode":"  }\n\n  if (Object.prototype.hasOwnProperty.call(settings, 'hooks')) {\n    if (!isJsonObject(settings.hooks)) {\n      throw new Error(`Invalid ${label}: expected \"hooks\" to be a JSON object`);\n    }\n    for (const [event, entries] of Object.entries(settings.hooks)) {\n      if (!Array.isArray(entries)) {\n        throw new Error(`Invalid ${label}: expected hooks.${event} to be an array`);\n      }\n    }\n  }\n\n  return cloneValue(settings);\n}\n\nfunction replacePluginRootPlaceholders(value, pluginRoot) {\n  if (typeof pluginRoot !== 'string') {\n    throw new Error('Invalid Claude plugin root: expected a string');\n  }\n  if (typeof value === 'string') {\n    return value.split(PLUGIN_ROOT_PLACEHOLDER).join(pluginRoot);\n  }\n  if (Array.isArray(value)) {\n    return value.map(item => replacePluginRootPlaceholders(item, pluginRoot));\n  }\n  if (isJsonObject(value)) {\n    return Object.fromEntries(\n      Object.entries(value).map(([key, nestedValue]) => [\n        key,\n        replacePluginRootPlaceholders(nestedValue, pluginRoot),\n      ])\n    );\n  }\n  return value;\n}\n","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L221-L257","documentation":"replacePluginRootPlaceholders substitutes the CLAUDE_PLUGIN_ROOT placeholder in hook commands with a concrete plugin root path. The function requires that root as a non-empty-ish string; when pluginRoot is not a string (undefined, null, number), it throws immediately rather than producing commands with an invalid root baked in.","triggerScenarios":"Calling replacePluginRootPlaceholders (directly or via materializeManagedHooks/resolved) with pluginRoot = undefined/null/non-string — e.g. a missing config value passed straight through.","commonSituations":"An install script reads the plugin root from an unset option or env var, a refactor changed the parameter order, or targetRoot was omitted when calling materializeManagedHooks.","solutions":["Pass the actual plugin root directory string (e.g. the plugin install path)","Resolve the root before calling: if from env, assert it is set and non-empty","Check the call site argument order for materializeManagedHooks(hooksConfig, targetRoot)","Default the root explicitly (e.g. process.cwd()) when appropriate"],"exampleFix":"// before\nreplacePluginRootPlaceholders(hooks, process.env.PLUGIN_ROOT)\n// after\nconst root = process.env.PLUGIN_ROOT;\nif (typeof root !== 'string' || root.length === 0) throw new Error('PLUGIN_ROOT is required');\nreplacePluginRootPlaceholders(hooks, root)","handlingStrategy":"validation","validationCode":"if (typeof pluginRoot !== 'string' || pluginRoot.length === 0) throw new Error('pluginRoot must be a non-empty string');","typeGuard":"const isNonEmptyString = (v) => typeof v === 'string' && v.trim().length > 0;","tryCatchPattern":"try { return replacePluginRootPlaceholders(value, root); } catch (e) { if (e.message.includes('plugin root')) { throw new Error('PLUGIN_ROOT not configured: ' + e.message); } throw e; }","preventionTips":["Resolve pluginRoot from config/env once, centrally, with an assertion","Never pass possibly-undefined options directly into library calls","Use path.resolve to normalize the root before use","Add an argument-order test for materializeManagedHooks"],"tags":["validation","argument","plugin-root"],"backgroundTag":"invalid-argument-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}