{"record":{"id":"f04107df767e6eac","repo":"JuliusBrussee/caveman","slug":"optional-profile-credentials-cannot-be-array-eleme","errorCode":null,"errorMessage":"optional profile credentials cannot be array elements","messagePattern":"optional profile credentials cannot be array elements","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":5806,"sourceCode":"\n// renderDeep applies renderTemplate to every string leaf of a JSON value — used to\n// render an agent's inline-config template before it is stringified into an env var.\n// Optional credential references disappear as whole object properties when their\n// source variable is unavailable. Secrets never enter generated JSON: the retained\n// value is the agent-native `$OPENAI_API_KEY` reference, not its expansion.\nfunction renderDeep(v: unknown, gw = gatewayURL(), env: NodeJS.ProcessEnv = process.env, options: RenderDeepOptions = {}): unknown {\n  if (v === OPTIONAL_OPENAI_KEY_ENV_TEMPLATE) {\n    const key = env.OPENAI_API_KEY;\n    const inherited = typeof key === \"string\" && !!key.trim() && !/[\\r\\n]/.test(key);\n    return (options.optionalOpenAIKeyEnvAvailable ?? inherited)\n      ? options.optionalOpenAIKeyReference ?? \"$OPENAI_API_KEY\"\n      : undefined;\n  }\n  if (typeof v === \"string\") return renderTemplate(v, gw);\n  if (Array.isArray(v)) {\n    return v.map((item) => {\n      const rendered = renderDeep(item, gw, env, options);\n      if (rendered === undefined) throw new Error(\"optional profile credentials cannot be array elements\");\n      return rendered;\n    });\n  }\n  if (v && typeof v === \"object\") {\n    const out: Record<string, unknown> = {};\n    for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n      const rendered = renderDeep(val, gw, env, options);\n      if (rendered !== undefined) out[k] = rendered;\n    }\n    return out;\n  }\n  return v;\n}\n\nfunction stripJson5Comments(s: string): string {\n  let out = \"\";\n  let inString = false;\n  let escaped = false;","sourceCodeStart":5788,"sourceCodeEnd":5824,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L5788-L5824","documentation":"When rendering profile credential templates, renderDeep walks the config value recursively. Inside objects, a key whose template renders to undefined (optional credential absent) is simply omitted from the output; but inside arrays there is no way to omit an element, so an array item that renders to undefined would silently become a hole. The library throws instead of producing a corrupted array.","triggerScenarios":"Calling renderDeep (via profile/env template rendering) on a config value that is an array whose element is a template string or nested value that resolves to undefined — e.g. an array element referencing an optional credential like \"${GW_API_KEY}\" when that credential is not set, or an element that is itself an object whose required rendering yields undefined.","commonSituations":"A user puts an optional credential reference inside a JSON array in a profile config (e.g. env value arrays or command argument arrays) and the referenced optional secret is missing in the current environment; the config worked before because the key was at object level where omission is allowed.","solutions":["Move the optional credential reference out of the array and into an object position where an undefined render can be omitted.","Provide the missing credential so the template renders to a defined value (set the env var / profile credential).","Replace the array element with a concrete default value (e.g. empty string) instead of an optional template.","Restructure the config so conditional values are handled by objects/conditional keys rather than array membership."],"exampleFix":"// before\n\"args\": [\"--token\", \"${OPTIONAL_API_TOKEN}\"]  // OPTIONAL_API_TOKEN unset -> throws\n\n// after\n\"args\": [\"--token\", \"${OPTIONAL_API_TOKEN:-}\"]  // or supply the credential, or drop the element","handlingStrategy":"validation","validationCode":"function hasUndefinedRenderingArrayItems(value, gw, env, options) {\n  if (Array.isArray(value)) {\n    return value.some((item) => renderDeep(item, gw, env, options) === undefined);\n  }\n  return false;\n}\n// call before applying the profile config\nif (hasUndefinedRenderingArrayItems(profileValue, gw, env, options)) {\n  throw new Error('profile array element references a missing optional credential');\n}","typeGuard":"function isRenderableArray(v, gw, env, options): v is unknown[] {\n  return Array.isArray(v) && v.every((item) => renderDeep(item, gw, env, options) !== undefined);\n}","tryCatchPattern":"try {\n  renderConfig(profile, gw, env, options);\n} catch (err) {\n  if (err.message.includes('optional profile credentials cannot be array elements')) {\n    console.error('Move optional credential references out of arrays or supply them.');\n    process.exitCode = 1;\n  } else throw err;\n}","preventionTips":["Keep optional credential templates at object-key positions, never inside arrays.","Validate profile configs against the environment before applying them in scripts/CI.","Use template defaults (e.g. ${VAR:-fallback}) so values always render.","Document which credentials are required for array-valued config fields."],"tags":["mcp","config","template-rendering","optional-credentials"],"backgroundTag":"invalid-config-value","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-09-06T12:00:26.372Z","contentChangedAt":"2026-09-06T12:00:26.372Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}