floci-io/floci · error · IllegalStateException

Failed to write self-signed TLS certificate

Error message

Failed to write self-signed TLS certificate

What it means

Thrown by AcknowledgeJob when the supplied nonce does not match the nonce stored on the job. Every job returned by PollForJobs carries a one-time nonce; AcknowledgeJob must echo it back verbatim to prove the caller received the job from a poll. A mismatch means the nonce is stale, mistyped, or belongs to a different job.

Source

Thrown at src/main/java/io/github/hectorvent/floci/config/TlsConfigSource.java:205

            List<String> allSans = new ArrayList<>();
            allSans.addAll(DEFAULT_SAN_HOSTNAMES);
            allSans.addAll(customHostnames);

            CertificateGenerator gen = new CertificateGenerator();
            CertificateGenerator.GeneratedCertificate generated = gen.generateSelfSignedCertificate(
                    "localhost",
                    allSans,
                    KeyAlgorithm.RSA_2048);

            Files.writeString(certFile, generated.certificatePem());
            Files.writeString(keyFile, generated.privateKeyPem());

            LOG.infov("TLS: generated self-signed certificate: {0}", certFile);

            // Persist metadata for change detection on restart
            persistMetadata(tlsDir, allSans);
        } catch (IOException e) {
            throw new IllegalStateException("Failed to write self-signed TLS certificate", e);
        }
    }

    private static void validateFileExists(String path, String description) {
        if (!Files.isReadable(Path.of(path))) {
            throw new IllegalStateException(
                    description + " file not found or not readable: " + path);
        }
    }

    /**
     * Returns {@code true} if the certificate at {@code certFile} is genuinely self-signed
     * (issuer == subject) and therefore usable as a trust anchor. Legacy Floci certs carried a
     * cosmetic Amazon issuer DN and return {@code false} here, triggering regeneration on upgrade.
     */
    private boolean isSelfSigned(Path certFile) {
        try {
            X509Certificate cert = new CertificateGenerator().parseCertificate(Files.readString(certFile));

View on GitHub (pinned to 62ff490619)

Solutions

  1. Always take the nonce from job.nonce of the exact job object returned by PollForJobs, keyed by the same jobId
  2. If the job may already be acknowledged, expect InvalidNonce/InvalidJobState on retry and treat it as idempotent success
  3. Verify no other worker instance has already consumed and acknowledged the polled job

Example fix

// before
client.acknowledgeJob(r -> r.jobId(jobId).nonce(nonceFromConfig));

// after
Job job = polledJobs.get(0);
client.acknowledgeJob(r -> r.jobId(job.getId()).nonce(job.getNonce()));
Defensive patterns

Strategy: validation

Validate before calling

Job job = polled.getJobs().get(0);
if (job.getNonce() == null || job.getNonce().isBlank()) {
    throw new IllegalStateException("polled job missing nonce; do not acknowledge");
}
client.acknowledgeJob(r -> r.jobId(job.getId()).nonce(job.getNonce()));

Prevention

When it happens

Trigger: Calling AcknowledgeJob with a nonce from a different jobId; re-acknowledging after the job's nonce was rotated; manually constructing the nonce instead of reading job.nonce from the polled Job object; string truncation or whitespace when copying the nonce.

Common situations: Worker restarts that replay an old poll response; multiple worker processes consuming the same queue and mixing up job/nonce pairs; logging frameworks that trim or mask the nonce before it is reused.

Understand the failure class

Related errors


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/c0f2aac1a7a13171. Report an issue: GitHub.