{"record":{"id":"68f7e17d75564e50","repo":"affaan-m/ECC","slug":"invalid-claude-target-root-expected-a-non-empty-string","errorCode":null,"errorMessage":"Invalid Claude target root: expected a non-empty string","messagePattern":"Invalid Claude target root: expected a non-empty string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings.js","lineNumber":302,"sourceCode":"          ...(typeof hook.command === 'string' || Array.isArray(hook.command)\n            ? {\n              command: Array.isArray(hook.command)\n                ? hook.command.map(resolveCommand)\n                : resolveCommand(hook.command),\n            }\n            : {}),\n        })),\n      })),\n    ])\n  );\n}\n\nfunction materializeManagedHooks(hooksConfig, targetRoot) {\n  if (!isJsonObject(hooksConfig) || !isJsonObject(hooksConfig.hooks)) {\n    throw new Error('Invalid hooks config: expected a JSON object with a hooks object');\n  }\n  if (!isNonEmptyString(targetRoot)) {\n    throw new Error('Invalid Claude target root: expected a non-empty string');\n  }\n  return validateManagedHooks(resolveManagedHookCommands(\n    replacePluginRootPlaceholders(hooksConfig.hooks, targetRoot),\n    targetRoot\n  ));\n}\n\nfunction parseSettings(rawSettings, label = 'Claude settings') {\n  let settings;\n  try {\n    settings = JSON.parse(rawSettings);\n  } catch (error) {\n    throw new Error(`Failed to parse ${label}: ${error.message}`, { cause: error });\n  }\n  return validateSettings(settings, label);\n}\n\nfunction readSettings(settingsPath, fileSystem = fs) {","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings.js#L284-L320","documentation":"After validating the hooks config shape, materializeManagedHooks requires targetRoot — the directory into which plugin root placeholders are substituted — to be a non-empty string. A missing, empty, or non-string targetRoot would produce hook commands with an invalid install path, so it throws before any materialization occurs.","triggerScenarios":"Calling materializeManagedHooks (via hydrateRecordedOperations/managedHooks/materialize/command/materialized/runTests) with targetRoot = undefined, \"\", null, or a non-string value.","commonSituations":"An install script with an unset target directory option, empty string from a failed path join, or arguments swapped so the root lands in the wrong parameter.","solutions":["Pass the absolute target installation directory as targetRoot","Assert the value before calling: if (!root) throw ... or use path.resolve(...)","Check argument order: materializeManagedHooks(hooksConfig, targetRoot)","If deriving from CLI flags, provide a default and validate non-empty"],"exampleFix":"// before\nmaterializeManagedHooks(cfg, opts.target)\n// after\nconst root = opts.target || process.cwd();\nif (typeof root !== 'string' || root.length === 0) throw new Error('target root required');\nmaterializeManagedHooks(cfg, root)","handlingStrategy":"validation","validationCode":"if (typeof targetRoot !== 'string' || targetRoot.length === 0) throw new Error('targetRoot must be a non-empty string');","typeGuard":"const isNonEmptyString = (v) => typeof v === 'string' && v.length > 0;","tryCatchPattern":"try { return materializeManagedHooks(cfg, root); } catch (e) { if (e.message.includes('target root')) { root = path.resolve('install-target'); return materializeManagedHooks(cfg, root); } throw e; }","preventionTips":["Resolve and assert the target directory before install calls","Never pass empty-string CLI option defaults straight through","Fix argument order with a named-options object where possible","Log targetRoot at debug level during installs"],"tags":["validation","argument","config"],"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"}