jenkinsci/jenkins · error · IOException

Could not decode input

Error message

Could not decode input

What it means

IOException thrown by AnnotatedLargeText.createAnnotator when decoding the `X-ConsoleAnnotator` HTTP header (the browser→server progressive-console transport) raises a RuntimeException — bad base64, a cipher/GZIP failure, or a key mismatch on the PASSING_ANNOTATOR secret. The original exception is wrapped and propagated (it does not silently fall back to a fresh annotator).

Source

Thrown at core/src/main/java/hudson/console/AnnotatedLargeText.java:194

    private void setContentTypeImpl(StaplerResponse2 rsp) {
        rsp.setContentType(isHtml() ? "text/html;charset=UTF-8" : "text/plain;charset=UTF-8");
    }

    private ConsoleAnnotator<T> createAnnotator(StaplerRequest2 req) throws IOException {
        try {
            String base64 = req != null ? req.getHeader("X-ConsoleAnnotator") : null;
            if (base64 != null) {
                Cipher sym = PASSING_ANNOTATOR.decrypt();

                try (ObjectInputStream ois = new ObjectInputStreamEx(new GZIPInputStream(
                        new CipherInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8))), sym)),
                        Jenkins.get().pluginManager.uberClassLoader)) {
                    long timestamp = ois.readLong();
                    if (TimeUnit.HOURS.toMillis(1) > abs(System.currentTimeMillis() - timestamp))
                        // don't deserialize something too old to prevent a replay attack
                        return getConsoleAnnotator(ois);
                } catch (RuntimeException ex) {
                    throw new IOException("Could not decode input", ex);
                }
            }
        } catch (ClassNotFoundException e) {
            throw new IOException(e);
        }
        // start from scratch
        return ConsoleAnnotator.initial(context);
    }

    @SuppressFBWarnings(value = "OBJECT_DESERIALIZATION", justification = "Deserialization is protected by logic.")
    private ConsoleAnnotator getConsoleAnnotator(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        return (ConsoleAnnotator) ois.readObject();
    }

    @CheckReturnValue
    @Override
    public long writeLogTo(long start, Writer w) throws IOException {
        if (isHtml())

View on GitHub (pinned to 2e228ff40b)

Solutions

  1. Refresh the console page in the browser so a fresh annotator is issued.
  2. If proxied, ensure the reverse proxy forwards X-ConsoleAnnotator verbatim.
  3. For programmatic clients, omit the header (or send a freshly obtained one) rather than reusing an old value.
Defensive patterns

Strategy: try-catch

Try / catch

try {
    annotator = createAnnotator(req);
} catch (IOException e) {
    // header undecodable: start fresh instead of failing the page
    annotator = ConsoleAnnotator.initial(context);
}

Prevention

When it happens

Trigger: A console-output poll sends a malformed/stale X-ConsoleAnnotator header; or the Jenkins PASSING_ANNOTATOR secret changed (restart, resharded key, or restored backup) so the cipher stream cannot be decrypted.

Common situations: Browser tab left open across a Jenkins restart; a reverse proxy altering/stripping the header; replay/tampering; instance restored from backup with different secrets.

Related errors


AI-assisted analysis of jenkinsci/jenkins@2e228ff40b (2026-08-14). Data as JSON: /api/errors/b8fe31ca5775b4a4. Report an issue: GitHub.