{"record":{"id":"542909cc444e549c","repo":"can1357/oh-my-pi","slug":"anthropic-files-api-credential-is-required","errorCode":null,"errorMessage":"Anthropic Files API credential is required","messagePattern":"Anthropic Files API credential is required","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/coding-agent/src/blob-broker/provider-files-anthropic.ts","lineNumber":105,"sourceCode":"}\n\n/**\n * Create a native Anthropic Files API client for an official Anthropic Messages model.\n * Unsupported providers, APIs, and non-Anthropic endpoints return `null` without making a request.\n */\nexport function createAnthropicFileClient(\n\tmodel: Model,\n\tcredential: string,\n\tfetchImpl: FetchImpl = globalThis.fetch,\n): ProviderFileClient | null {\n\tif (\n\t\tmodel.provider !== \"anthropic\" ||\n\t\tmodel.api !== \"anthropic-messages\" ||\n\t\t!isOfficialAnthropicBaseUrl(model.baseUrl)\n\t) {\n\t\treturn null;\n\t}\n\tif (credential.length === 0) throw new Error(\"Anthropic Files API credential is required\");\n\n\tconst headers = requestHeaders(credential);\n\treturn {\n\t\tprovider: \"anthropic\",\n\t\tasync upload(request: ProviderFileUploadRequest): Promise<ProviderFileHandle> {\n\t\t\tconst form = new FormData();\n\t\t\tform.append(\"file\", uploadedFile(request));\n\t\t\tconst response = await expectAnthropicOk(\n\t\t\t\tawait fetchImpl(ANTHROPIC_FILES_URL, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders,\n\t\t\t\t\tbody: form,\n\t\t\t\t\tsignal: request.signal,\n\t\t\t\t}),\n\t\t\t\t\"upload\",\n\t\t\t);\n\t\t\tconst metadata = parseMetadata(await response.json());\n\t\t\tconst deleteUrl = `${ANTHROPIC_FILES_URL}/${encodeURIComponent(metadata.id)}`;","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-files-anthropic.ts#L87-L123","documentation":"`createAnthropicFileClient` only builds an Anthropic Files API client when the model is an official Anthropic endpoint; once that gate passes, it requires a non-empty credential (API key). An empty credential means the caller asked for Anthropic file storage but supplied no key, so the client cannot construct the required auth headers and refuses to create a client that would make doomed requests.","triggerScenarios":"`createAnthropicFileClient(credential, model)` is invoked with `credential` as an empty string (or zero-length value) while `model.provider === \"anthropic\"`, `model.api === \"anthropic-messages\"`, and the base URL is an official Anthropic one — i.e. the anthropic path was selected but no API key was resolved.","commonSituations":"ANTHROPIC_API_KEY environment variable unset or set to \"\" in CI or a fresh shell; config file has an empty `apiKey` field; key is read from a secret manager that returned an empty secret; using OAuth/login-based auth instead of an API key (the Files API path requires the real key, not an OAuth token); the credential variable was declared but never assigned before client creation.","solutions":["Set a valid Anthropic API key: export ANTHROPIC_API_KEY=sk-ant-... or supply the credential explicitly in the client/config options.","Verify the resolved credential at startup — log its length (never the value) and fail early if it is empty before attempting uploads.","If you authenticate via OAuth/login, switch to an API key: the Anthropic Files API client built here requires a raw key credential.","Check for config-loading bugs: empty string vs undefined, wrong env var name, dotenv file not loaded, or the secret manager returning an empty value."],"exampleFix":"// before — empty key sneaks through config\nconst client = createAnthropicFileClient(config.anthropicApiKey ?? \"\", model);\n\n// after — fail fast with a clear message\nconst apiKey = config.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY;\nif (!apiKey) throw new Error(\"Anthropic API key is required for file uploads (set ANTHROPIC_API_KEY)\");\nconst client = createAnthropicFileClient(apiKey, model);","handlingStrategy":"validation","validationCode":"// fail fast before creating the client\nconst apiKey = process.env.ANTHROPIC_API_KEY ?? config.anthropicApiKey;\nif (typeof apiKey !== \"string\" || apiKey.length === 0) {\n  throw new Error(\"Anthropic Files API requires a non-empty credential (set ANTHROPIC_API_KEY)\");\n}","typeGuard":"const hasCredential = (c: string | undefined | null): c is string => typeof c === \"string\" && c.length > 0;","tryCatchPattern":"try {\n  const client = createAnthropicFileClient(credential, model);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"credential is required\")) {\n    logger.error(\"No Anthropic API key configured for file uploads\", {});\n    // disable file-upload features or prompt for configuration\n  } else throw err;\n}","preventionTips":["Check ANTHROPIC_API_KEY at startup and fail with a clear message before any upload is attempted.","Distinguish empty string from unset when loading config — treat both as 'not configured'.","Remember the Files API path needs a real API key; OAuth/login credentials do not satisfy it.","If secrets come from a secret manager, verify the fetched secret is non-empty before caching it."],"tags":["anthropic","files-api","authentication","configuration","missing-credential"],"backgroundTag":"missing-api-key","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}