denoland/deno · error · AnyError
No certificate chain returned
Error message
No certificate chain returned
What it means
To sign provenance, Deno requests a short-lived certificate from Fulcio; the response must contain either `signedCertificateEmbeddedSct` or `signedCertificateDetachedSct`. When the JSON body parses but both fields are missing, there is no certificate chain to build the attestation with, and this error aborts the publish. It indicates an unexpected/empty payload from the CA endpoint rather than misuse on your side.
Solutions
- Re-run `deno publish` — the certificate request is per-run and transient upstream issues resolve themselves; the failed publish left no partial state on jsr.io.
- Check Sigstore status (status.sigstore.dev) for active Fulcio incidents before retrying in a loop.
- Bypass TLS-intercepting corporate proxies for api.sigstore.dev / fulcio.sigstore.dev, which can corrupt JSON bodies.
- If reproducible, drop `--provenance` to unblock the release and investigate separately.
Defensive patterns
Strategy: retry
Try / catch
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await runDeno(['publish', '--provenance']);
break;
} catch (e) {
if (String(e.message).includes('No certificate chain returned') && attempt < 3) {
await delay(15_000);
continue; // Fulcio hiccup; new signing attempt on the next run
}
throw e;
}
} Prevention
- Retry provenance publishes on signing-service errors; they are per-run and transient.
- Check status.sigstore.dev before forcing retries in a tight loop.
- Avoid TLS-inspecting proxies on the Sigstore endpoints.
- If release urgency demands, fall back to publishing without --provenance temporarily.
When it happens
Trigger: `deno publish --provenance` where Fulcio responds with a structurally valid but empty signing response (outage or API change); a proxy/GHE mirror returning an error page that deserialized into default-empty fields; transient infrastructure hiccup during the signing leg.
Common situations: One-off CI publish failures that pass on retry; Sigstore/Fulcio incidents; environments with TLS-intercepting proxies altering responses. Local misconfiguration cannot cause this — only the network path and upstream.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Rekor transparency log returned no entries
- Failed to fetch package manifest from
- Automatic provenance is only available in GitHub Actions
- Checksum mismatch for
- Export mismatch: expected , got
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/9e340d2a3e4aeeb2.
Report an issue: GitHub.
Appendix: source
Thrown at cli/tools/publish/provenance.rs:531
content: public_key,
},
proof_of_possession: BASE64_STANDARD.encode(challenge.as_ref()),
},
};
let response = self
.http_client
.post_json(url.parse()?, &request_body)?
.send()
.await?;
let body: SigningCertificateResponse =
http_util::body_to_json(response).await?;
let key = body
.signed_certificate_embedded_sct
.or(body.signed_certificate_detached_sct)
.ok_or_else(|| anyhow::anyhow!("No certificate chain returned"))?;
Ok(key.chain.certificates)
}
async fn gha_request_token(&self, aud: &str) -> Result<String, AnyError> {
let Ok(req_url) = env::var("ACTIONS_ID_TOKEN_REQUEST_URL") else {
bail!("Not running in GitHub Actions");
};
let Some(token) = gha_oidc_token() else {
bail!("No OIDC token available");
};
let mut url = req_url.parse::<Url>()?;
url.query_pairs_mut().append_pair("audience", aud);
let res_bytes = self
.http_client
.get(url)?
.header(View on GitHub (pinned to 9ad36f7a2c)