paperclipai/paperclip · error
Cloud control claims are incomplete
Error message
Cloud control claims are incomplete
What it means
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.
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
Example fix
// before
{ sub: stackId, action: "restart", requestId }
// after
{ sub: stackId, action: "restart", requestId, iat: Math.floor(Date.now()/1000), exp: Math.floor(Date.now()/1000) + 300 } Defensive patterns
Strategy: validation
Validate before calling
function claimsComplete(payload) {
return typeof payload.sub === "string" && typeof payload.action === "string"
&& typeof payload.requestId === "string"
&& typeof payload.iat === "number" && Number.isInteger(payload.iat)
&& typeof payload.exp === "number" && Number.isInteger(payload.exp);
}
if (!claimsComplete(payload)) throw new Error("assertion payload missing required claims before sending"); Type guard
const hasRequiredClaims = (p: unknown): p is { sub: string; action: string; requestId: string; iat: number; exp: number } =>
!!p && typeof p === "object" &&
typeof (p as any).sub === "string" && typeof (p as any).action === "string" &&
typeof (p as any).requestId === "string" &&
Number.isInteger((p as any).iat) && Number.isInteger((p as any).exp); Try / catch
try {
return verifyCloudControlAssertion({ compactJws: token, expectedAction });
} catch (e) {
if (e.message === "Cloud control claims are incomplete") {
return respond(401, "assertion missing required claims"); // issuer bug: re-mint with full claim set
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cloud control assertion request id is invalid
- Cloud control assertion does not authorize this action
- Cloud control assertion is expired or has an invalid…
- Cloud control assertion is not a compact JWS
- Cloud control protected header is invalid
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/b2cd71f6b8f6b500.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/cloud-runtime-identity.ts:546
throw new Error("Cloud control signature is invalid");
}
const payload = decodeJsonPart(encodedPayload, "payload");
const configuredStackId = nonEmpty(env.PAPERCLIP_CLOUD_STACK_ID);
const nowSeconds = Math.floor(now.getTime() / 1000);
if (
payload.v !== 1
|| payload.iss !== CLOUD_RUNTIME_IDENTITY_ISSUER
|| payload.aud !== CLOUD_CONTROL_AUDIENCE
|| typeof payload.sub !== "string"
|| typeof payload.action !== "string"
|| typeof payload.requestId !== "string"
|| typeof payload.iat !== "number"
|| !Number.isInteger(payload.iat)
|| typeof payload.exp !== "number"
|| !Number.isInteger(payload.exp)
) {
throw new Error("Cloud control claims are incomplete");
}
if (!configuredStackId || payload.sub !== configuredStackId) {
throw new Error("Cloud control assertion stack does not match this instance");
}
if (
!(CLOUD_CONTROL_ACTIONS as readonly string[]).includes(payload.action)
|| payload.action !== input.expectedAction
) {
throw new Error("Cloud control assertion does not authorize this action");
}
if (
!payload.requestId
|| payload.requestId.trim() !== payload.requestId
|| payload.requestId.length > 256
) {
throw new Error("Cloud control assertion request id is invalid");
}
if (View on GitHub (pinned to 3f1d897a7c)