paperclipai/paperclip · error
PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is invalid
Error message
PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is invalid
What it means
Thrown by publicKeyForKid when PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is set but its value is not parseable as JSON, or (same message reused at line 246) when the parsed JSON has no 'keys' array. The env var must contain a JWKS document: a JSON object like {"keys":[...]}.
Source
Thrown at server/src/services/cloud-runtime-identity.ts:241
try {
parsed = JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
} catch {
throw new Error(`Cloud runtime identity has an invalid ${label}`);
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error(`Cloud runtime identity has an invalid ${label}`);
}
return parsed as Record<string, unknown>;
}
function publicKeyForKid(env: NodeJS.ProcessEnv, kid: string) {
const raw = nonEmpty(env.PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS);
if (!raw) throw new Error("PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is not configured");
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch {
throw new Error("PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is invalid");
}
const keys = parsed && typeof parsed === "object" && !Array.isArray(parsed)
? (parsed as { keys?: unknown }).keys
: undefined;
if (!Array.isArray(keys)) throw new Error("PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is invalid");
const matches = keys.filter((candidate): candidate is JsonWebKey & { kid: string } => {
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
const key = candidate as JsonWebKey & { kid?: unknown };
return key.kid === kid;
});
if (matches.length !== 1) throw new Error("Cloud runtime identity uses an unknown signing key");
const jwk = matches[0];
if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || jwk.use !== "sig" || jwk.alg !== "EdDSA" || !jwk.x || jwk.d) {
throw new Error("Cloud runtime identity signing key is invalid");
}
return createPublicKey({ key: jwk, format: "jwk" });
}
View on GitHub (pinned to 01ad858492)
Solutions
- Log or echo the env var value and run it through JSON.parse locally to see the parse error
- Ensure the value is a complete JWKS document with a top-level 'keys' array: {"keys":[{"kty":"OKP",...}]}
- Fix shell/manifest quoting so quotes survive (single-quote the value in shell, use a k8s secret not an inline literal)
- Confirm the control-plane JWKS endpoint output is copied verbatim, not re-encoded
Example fix
// before: bare key, no keys wrapper
PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS='{"kty":"OKP","crv":"Ed25519",...}'
// after: proper JWKS
PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS='{"keys":[{"kty":"OKP","crv":"Ed25519","use":"sig","alg":"EdDSA","kid":"...","x":"..."}]}' Defensive patterns
Strategy: validation
Validate before calling
function jwksIsParseable(raw: string | undefined): boolean {
if (!raw) return false;
try {
const parsed = JSON.parse(raw);
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && Array.isArray((parsed as { keys?: unknown }).keys);
} catch { return false; }
}
// run at startup: if (!jwksIsParseable(process.env.PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS)) failFast(); Try / catch
try {
verifyCloudRuntimeIdentityAssertion({ compactJws: assertion, expectedPreviousOrigin: prev });
} catch (e) {
if (String((e as Error).message).includes("JWKS is invalid")) {
logger.error("Configured JWKS is not valid JSON with a keys array; check quoting/encoding of the env var", { error: e });
} else throw e;
} Prevention
- Validate the JWKS parses at process startup, not lazily at first verification
- When storing JSON in shell/env, single-quote the value to protect double quotes
- Store the JWKS in a secret manager and inject it as one raw JSON string, not via shell interpolation
- Never base64-wrap the JWKS in the env var; paste raw JSON
When it happens
Trigger: JSON.parse of the env var value throws (line 241), or the parsed value is not an object, is an array, or its 'keys' property is not an array (line 246). Both raise the identical message.
Common situations: The JWKS was pasted into the env var with shell quoting damage (escaped quotes consumed, JSON truncated); a bare key object {"kty":...} was configured instead of a wrapping JWKS {"keys":[...]}; the value was stored as base64 instead of raw JSON; YAML/k8s stringification mangled newlines or quotes.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- RUNNER_PROTOCOL_EVAL_HISTORY_S3_BUCKET is not a valid bucket
- RUNNER_PROTOCOL_EVAL_HISTORY_PREFIX must be a safe non-empty
- RUNNER_PROTOCOL_EVAL_HISTORY_PUBLIC_BASE_URL must be a crede
- Unknown config key ${warning.path}; did you mean ${warning.s
- Unknown config key ${warning.path}; did you mean ${warning.s
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/6bb234b09a33ebaf.
Report an issue: GitHub.