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

  1. Base64url-decode the payload and confirm all of sub (string), action (string), requestId (string), iat (integer), exp (integer) are present with correct types
  2. Fix the assertion issuer to emit epoch-seconds integers for iat/exp (NumericDate), not ISO strings
  3. Update the signer to the current cloud control claim schema and re-issue the token
  4. 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

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


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)