{"record":{"id":"b2cd71f6b8f6b500","repo":"paperclipai/paperclip","slug":"cloud-control-claims-are-incomplete","errorCode":null,"errorMessage":"Cloud control claims are incomplete","messagePattern":"Cloud control claims are incomplete","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/cloud-runtime-identity.ts","lineNumber":546,"sourceCode":"    throw new Error(\"Cloud control signature is invalid\");\n  }\n\n  const payload = decodeJsonPart(encodedPayload, \"payload\");\n  const configuredStackId = nonEmpty(env.PAPERCLIP_CLOUD_STACK_ID);\n  const nowSeconds = Math.floor(now.getTime() / 1000);\n  if (\n    payload.v !== 1\n    || payload.iss !== CLOUD_RUNTIME_IDENTITY_ISSUER\n    || payload.aud !== CLOUD_CONTROL_AUDIENCE\n    || typeof payload.sub !== \"string\"\n    || typeof payload.action !== \"string\"\n    || typeof payload.requestId !== \"string\"\n    || typeof payload.iat !== \"number\"\n    || !Number.isInteger(payload.iat)\n    || typeof payload.exp !== \"number\"\n    || !Number.isInteger(payload.exp)\n  ) {\n    throw new Error(\"Cloud control claims are incomplete\");\n  }\n  if (!configuredStackId || payload.sub !== configuredStackId) {\n    throw new Error(\"Cloud control assertion stack does not match this instance\");\n  }\n  if (\n    !(CLOUD_CONTROL_ACTIONS as readonly string[]).includes(payload.action)\n    || payload.action !== input.expectedAction\n  ) {\n    throw new Error(\"Cloud control assertion does not authorize this action\");\n  }\n  if (\n    !payload.requestId\n    || payload.requestId.trim() !== payload.requestId\n    || payload.requestId.length > 256\n  ) {\n    throw new Error(\"Cloud control assertion request id is invalid\");\n  }\n  if (","sourceCodeStart":528,"sourceCodeEnd":564,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/cloud-runtime-identity.ts#L528-L564","documentation":"After cryptographic verification, verifyCloudControlAssertion validates the decoded payload claims. This error fires when required claims are missing or of the wrong type: sub (string), action (string), requestId (string), iat (integer number), or exp (integer number). The library requires well-typed claims before applying policy checks.","triggerScenarios":"A token payload lacking any of sub/action/requestId/iat/exp; iat or exp serialized as strings (\"1730000000\") or floats; a payload signed by an incompatible issuer version that renamed or dropped claims.","commonSituations":"A custom or older assertion issuer writes timestamps as ISO strings instead of NumericDate epoch integers; a claim was renamed during an API change; a generic JWT library omitted claims the caller never set; test fixtures with hand-written payloads missing fields.","solutions":["Base64url-decode the payload and confirm all of sub (string), action (string), requestId (string), iat (integer), exp (integer) are present with correct types","Fix the assertion issuer to emit epoch-seconds integers for iat/exp (NumericDate), not ISO strings","Update the signer to the current cloud control claim schema and re-issue the token","Update test fixtures/stubs to include every required claim"],"exampleFix":"// before\n{ sub: stackId, action: \"restart\", requestId }\n// after\n{ sub: stackId, action: \"restart\", requestId, iat: Math.floor(Date.now()/1000), exp: Math.floor(Date.now()/1000) + 300 }","handlingStrategy":"validation","validationCode":"function claimsComplete(payload) {\n  return typeof payload.sub === \"string\" && typeof payload.action === \"string\"\n    && typeof payload.requestId === \"string\"\n    && typeof payload.iat === \"number\" && Number.isInteger(payload.iat)\n    && typeof payload.exp === \"number\" && Number.isInteger(payload.exp);\n}\nif (!claimsComplete(payload)) throw new Error(\"assertion payload missing required claims before sending\");","typeGuard":"const hasRequiredClaims = (p: unknown): p is { sub: string; action: string; requestId: string; iat: number; exp: number } =>\n  !!p && typeof p === \"object\" &&\n  typeof (p as any).sub === \"string\" && typeof (p as any).action === \"string\" &&\n  typeof (p as any).requestId === \"string\" &&\n  Number.isInteger((p as any).iat) && Number.isInteger((p as any).exp);","tryCatchPattern":"try {\n  return verifyCloudControlAssertion({ compactJws: token, expectedAction });\n} catch (e) {\n  if (e.message === \"Cloud control claims are incomplete\") {\n    return respond(401, \"assertion missing required claims\"); // issuer bug: re-mint with full claim set\n  }\n  throw e;\n}","preventionTips":["Centralize claim construction in one minting function so claims can never be partially set","Emit iat/exp as epoch-seconds integers (JWT NumericDate), never ISO strings","Version the claim schema; update issuer and verifier together"],"tags":["jws","claims","validation","security"],"backgroundTag":"unexpected-response-shape","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"}