{"record":{"id":"6fd9af6ffe8ca9ea","repo":"paperclipai/paperclip","slug":"no-credential-policy-for-config-provider-cod","errorCode":null,"errorMessage":"No credential policy for ${config.provider ?? \"codex\"}/${config.acpxAgent ?? \"default\"}","messagePattern":"No credential policy for (.+?)/(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs","lineNumber":69,"sourceCode":"  if (!rel || rel === \"..\" || rel.startsWith(`..${sep}`)) {\n    throw new Error(`${label} escapes its declared root`);\n  }\n  return resolve(candidate);\n}\n\nexport function credentialForConfig(config) {\n  if (config.provider === \"opencode\") return \"OPENROUTER_API_KEY\";\n  if (config.provider === \"claude_managed\") return \"ANTHROPIC_API_KEY\";\n  if (config.provider === \"aws_agentcore\") return \"AWS_AGENTCORE_OIDC\";\n  if (config.provider === \"codex\" || config.provider === undefined) {\n    return \"OPENAI_API_KEY\";\n  }\n  if (config.provider === \"acpx\") {\n    if (config.acpxAgent === \"pi\") return \"OPENROUTER_API_KEY\";\n    if (config.acpxAgent === \"claude\") return \"ANTHROPIC_API_KEY\";\n    if (config.acpxAgent === \"codex\") return \"OPENAI_API_KEY\";\n  }\n  throw new Error(\n    `No credential policy for ${config.provider ?? \"codex\"}/${config.acpxAgent ?? \"default\"}`,\n  );\n}\n\nfunction parseRosterSelection(value) {\n  if (!value?.trim() || value.trim() === \"all\") return null;\n  const selected = value\n    .split(\",\")\n    .map((entry) => entry.trim())\n    .filter(Boolean);\n  if (selected.length === 0 || new Set(selected).size !== selected.length) {\n    throw new Error(\"Roster selection must contain unique comma-separated IDs\");\n  }\n  return new Set(selected);\n}\n\nasync function maintainedRosterSelection(programRoot) {\n  const campaignPath = resolve(programRoot, \"campaigns/live-direct-full.json\");","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs#L51-L87","documentation":"credentialForConfig maps an eval-case config (provider + acpxAgent) to the environment variable holding the required API credential. If the combination is not covered by the hardcoded policy (opencode→OPENROUTER_API_KEY; acpx pi→OPENROUTER, claude→ANTHROPIC, codex→OPENAI; codex provider default), it throws naming the provider/agent pair. Every live eval lane must have a known credential source.","triggerScenarios":"Adding an eval case with an unknown provider value, or an acpx agent other than pi/claude/codex, or a config missing both provider and acpxAgent that doesn't fall through to the codex default covered by the policy.","commonSituations":"Typo'd provider name (e.g. 'opnrouter'); a newly supported acpx agent added to rosters before updating the credential policy; a case config with provider: 'acpx' but acpxAgent misspelled or unset when policy requires one of the three.","solutions":["Fix the provider/acpxAgent values in the roster case config to a supported combination.","Add a mapping line to credentialForConfig for the new provider or acpx agent and set the corresponding env var.","Export the required credential env var (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY) — note this error names the policy gap, not the missing env var itself."],"exampleFix":"// before\nif (config.acpxAgent === \"codex\") return \"OPENAI_API_KEY\";\n// after\nif (config.acpxAgent === \"codex\") return \"OPENAI_API_KEY\";\nif (config.acpxAgent === \"gemini\") return \"GEMINI_API_KEY\";","handlingStrategy":"validation","validationCode":"const SUPPORTED = { opencode: true, codex: true, acpx: [\"pi\", \"claude\", \"codex\"] };\nconst ok = config.provider === \"opencode\" || config.provider === \"codex\" || (config.provider === \"acpx\" && SUPPORTED.acpx.includes(config.acpxAgent));\nif (!ok) throw new Error(`Unsupported provider/agent: ${config.provider}/${config.acpxAgent}`);","typeGuard":"const hasCredentialPolicy = (c) => c.provider === \"opencode\" || c.provider === \"codex\" || (c.provider === \"acpx\" && [\"pi\", \"claude\", \"codex\"].includes(c.acpxAgent));","tryCatchPattern":"try {\n  const envVar = credentialForConfig(config);\n  if (!process.env[envVar]) throw new Error(`${envVar} is not set`);\n} catch (err) {\n  if (err.message.startsWith(\"No credential policy\")) console.error(\"Fix provider/acpxAgent in the roster case or extend credentialForConfig.\");\n  throw err;\n}","preventionTips":["Keep the set of provider/acpxAgent values in rosters in sync with credentialForConfig.","When adding a new agent, add its credential mapping and required env var in the same PR.","Dry-run catalog building before a live run to catch policy gaps early."],"tags":["credentials","config","mapping"],"backgroundTag":"unsupported-config-value","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}