When the ephemeral build image is loaded into docker it can fail, and we discard the result, so we never know. From DockerApi$ImageApi:

try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) {
    jsonStream().get(response.getContent(), LoadImageUpdateEvent.class, listener::onUpdate);
}

The json is discarded and docker is actually sending an error message (with a 200 OK response status, which is admittedly unhelpful). It would suffice in this case to look at the unmarshalled response and check that it had some content (non null "stream" property).

This code in LayerId is responsible for the failure that we found:

public static LayerId ofSha256Digest(byte[] digest) {
    Assert.notNull(digest, "Digest must not be null");
    Assert.isTrue(digest.length == 32, "Digest must be exactly 32 bytes");
    String algorithm = "sha256";
    String hash = String.format("%32x", new BigInteger(1, digest));
    return new LayerId(algorithm + ":" + hash, algorithm, hash);
}

%32x is actually only guaranteed to be at least 32 characters. We need 64. So sometimes we get 63 because the content of the digest has a low leading byte value. This would probably work (because Java Formatter pads with spaces not zeros, and docker wants zeros):

String hash = String.format("%64x", new BigInteger(1, digest)).replace(" ", "0");

Comment From: scottfrederick

Splitting this into separate issues, with the layer hash covered in #23132.