{"record":{"id":"506a43b8986619b8","repo":"paperclipai/paperclip","slug":"credentials","errorCode":"credentials","errorMessage":"Enter a Photon project secret","messagePattern":"Enter a Photon project secret","errorType":"error_code","errorClass":"PhotonError","httpStatus":null,"severity":"error","filePath":"server/src/services/photon/cloud.ts","lineNumber":120,"sourceCode":"  tokens: ReadonlyMap<string, string>;\n  expiresIn: number;\n}\nfunction record(value: unknown): value is Record<string, unknown> {\n  return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/** Only this module ever sees a Cloud project secret or minted line tokens. */\nexport class PhotonCloudClient {\n  constructor(private readonly fetchImpl: typeof fetch = fetch) {}\n  private async request(\n    projectId: string,\n    projectSecret: string,\n    suffix: string,\n    method: string,\n  ): Promise<unknown> {\n    photonProjectIdSchema.parse(projectId);\n    if (!projectSecret || projectSecret.length > 4096)\n      throw new PhotonError(\"credentials\", \"Enter a Photon project secret\");\n    let response: Response;\n    try {\n      response = await this.fetchImpl(\n        `${CLOUD_ORIGIN}/projects/${encodeURIComponent(projectId)}/${suffix}`,\n        {\n          method,\n          redirect: \"error\",\n          signal: AbortSignal.timeout(15_000),\n          headers: {\n            authorization: `Basic ${Buffer.from(`${projectId}:${projectSecret}`).toString(\"base64\")}`,\n            accept: \"application/json\",\n          },\n        },\n      );\n    } catch {\n      throw new PhotonError(\n        \"network\",\n        \"Photon Cloud could not be reached; retry the connection\",","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/photon/cloud.ts#L102-L138","documentation":"Thrown by PhotonCloudClient.request in server/src/services/photon/cloud.ts before any HTTP call is made when the project secret is empty or longer than 4096 characters. The client refuses to issue a Basic-auth request to Photon Cloud without a plausible secret, so misconfigured credentials fail fast with code 'credentials'.","triggerScenarios":"Calling allocation(), inspect(), or any request() path with an empty string, undefined-coerced secret, or a secret exceeding 4096 characters for the Photon project.","commonSituations":"The Photon project secret env var or stored credential was never set; a paste captured whitespace/placeholder instead of the secret; a corrupted or truncated database row yields a garbage oversized secret.","solutions":["Set the Photon project secret (PHOTON_PROJECT_SECRET or your stored credential) to the value from the Photon Cloud console","Trim whitespace and confirm the secret is a non-empty string before constructing PhotonCloudClient or calling allocation/inspect","Check length: the secret must be 1-4096 characters; regenerate the secret if the stored value is corrupted","If the credential comes from a DB/config layer, log (redacted) its presence and length to confirm it is loaded"],"exampleFix":"// before\nawait cloud.allocation(projectId, process.env.PHOTON_SECRET as string);\n// after\nconst secret = (process.env.PHOTON_SECRET ?? \"\").trim();\nif (!secret || secret.length > 4096) throw new Error(\"Enter a Photon project secret\");\nawait cloud.allocation(projectId, secret);","handlingStrategy":"validation","validationCode":"const secret = (config.photonProjectSecret ?? \"\").trim();\nif (!secret) throw new Error(\"Enter a Photon project secret\");\nif (secret.length > 4096) throw new Error(\"Photon project secret is too long\");","typeGuard":"function hasValidSecret(s: unknown): s is string {\n  return typeof s === \"string\" && s.length > 0 && s.length <= 4096;\n}","tryCatchPattern":"try {\n  return await cloud.allocation(projectId, secret);\n} catch (e) {\n  if (e instanceof PhotonError && e.code === \"credentials\")\n    throw new ConfigError(\"Photon project secret missing or invalid — set it in settings\");\n  throw e;\n}","preventionTips":["Validate secret presence and length at startup, before any network call","Trim whitespace when loading secrets from env or config files","Surface a clear UI/config error instead of attempting the request with an empty secret","Never persist placeholder values like \"changeme\" as project secrets"],"tags":["credentials","config","validation","fail-fast"],"backgroundTag":"missing-credentials","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}