{"record":{"id":"a854103f1d64822f","repo":"affaan-m/ECC","slug":"key-must-be-a-non-empty-string-when-provided","errorCode":null,"errorMessage":"${key} must be a non-empty string when provided","messagePattern":"(.+?) must be a non-empty string when provided","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/install-manifests.js","lineNumber":220,"sourceCode":"      description: `Install only the ${skillId} skill directory.`,\n      modules: [moduleId],\n      synthetic: true,\n    });\n    componentIds.add(componentId);\n  }\n}\n\nfunction readOptionalStringOption(options, key) {\n  if (\n    !Object.prototype.hasOwnProperty.call(options, key)\n    || options[key] === null\n    || options[key] === undefined\n  ) {\n    return null;\n  }\n\n  if (typeof options[key] !== 'string' || options[key].trim() === '') {\n    throw new Error(`${key} must be a non-empty string when provided`);\n  }\n\n  return options[key];\n}\n\nfunction readModuleTargetsOrThrow(module) {\n  const moduleId = module && module.id ? module.id : '<unknown>';\n  const targets = module && module.targets;\n\n  if (!Array.isArray(targets)) {\n    throw new Error(`Install module ${moduleId} has invalid targets; expected an array of supported target ids`);\n  }\n\n  const normalizedTargets = targets.map(target => (\n    typeof target === 'string' ? target.trim() : ''\n  ));\n\n  if (normalizedTargets.some(target => target.length === 0)) {","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/install-manifests.js#L202-L238","documentation":"readOptionalStringOption treats null/undefined/absent as 'not provided' (returns null), but if the key IS present it must be a non-empty trimmed string. Thrown when options[key] is a non-string type, an empty string, or whitespace-only.","triggerScenarios":"Passing an option object to a manifest API where a known key is set to '', '   ', a number, a boolean, or an object. The option is opt-in but when supplied it must be a real string value.","commonSituations":"Script forwards CLI flags without coercion (e.g. --profile= passed through as ''); env var unset but default empty string propagated; configuration file with `profile: ''` instead of omitting the key; number accidentally passed where a string id was expected.","solutions":["Check the caller's option construction: only set the key when a real value exists.","Coerce CLI input: skip the key when the flag value is empty (`--profile ''`).","Trim and validate before passing: `if (v && typeof v === 'string') opts.key = v;`.","If the value should legitimately be empty, omit the key entirely rather than passing ''."],"exampleFix":"// before: readOptionalStringOption({ profile: '' }, 'profile')\n// after:\nconst opts = {};\nif (cliProfile && cliProfile.trim()) opts.profile = cliProfile;\nreadOptionalStringOption(opts, 'profile');","handlingStrategy":"validation","validationCode":"function buildOptionalString(opts, key) {\n  const v = opts[key];\n  if (v === undefined || v === null) return null;\n  if (typeof v !== 'string' || v.trim() === '') {\n    throw new Error(`${key} must be a non-empty string when provided`);\n  }\n  return v;\n}","typeGuard":"function isOptionalNonEmptyString(opts, key) {\n  const v = opts[key];\n  return v === undefined || v === null || (typeof v === 'string' && v.trim().length > 0);\n}","tryCatchPattern":"try {\n  profile = readOptionalStringOption(opts, 'profile');\n} catch (err) {\n  if (/must be a non-empty string when provided/.test(err.message)) {\n    // omit the key instead of passing ''\n  } else throw err;\n}","preventionTips":["Omit optional keys rather than passing '' when there is no value.","Coerce CLI flags: only set the option when the flag value is non-empty.","Trim user input before forwarding.","Add a unit test that asserts readOptionalStringOption accepts null/undefined and rejects ''."],"tags":["manifest","validation","api","options"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}