thephpleague/flysystem · error · UnableToProvideChecksum

ETag header not available.

Error message

ETag header not available.

What it means

AwsS3V3Adapter::checksum() completed the HeadObject request but the normalized extraMetadata array has no 'ETag' entry, so it throws UnableToProvideChecksum('ETag header not available.'). Genuine AWS S3 always sends an ETag, so in practice this indicates an S3-compatible endpoint, gateway, or a test double that returns HEAD responses without ETag.

Source

Thrown at src/AwsS3V3/AwsS3V3Adapter.php:503

        }
    }

    public function checksum(string $path, Config $config): string
    {
        $algo = $config->get('checksum_algo', 'etag');

        if ($algo !== 'etag') {
            throw new ChecksumAlgoIsNotSupported();
        }

        try {
            $metadata = $this->fetchFileMetadata($path, 'checksum')->extraMetadata();
        } catch (UnableToRetrieveMetadata $exception) {
            throw new UnableToProvideChecksum($exception->reason(), $path, $exception);
        }

        if ( ! isset($metadata['ETag'])) {
            throw new UnableToProvideChecksum('ETag header not available.', $path);
        }

        return trim($metadata['ETag'], '"');
    }

    public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string
    {
        try {
            $options = $config->get('get_object_options', []);
            $command = $this->client->getCommand('GetObject', [
                    'Bucket' => $this->bucket,
                    'Key' => $this->prefixer->prefixPath($path),
                ] + $options);

            $presignedRequestOptions = $config->get('presigned_request_options', []);
            $request = $this->client->createPresignedRequest($command, $expiresAt, $presignedRequestOptions);

            return (string) $request->getUri();

View on GitHub (pinned to b277b5dc3d)

Solutions

  1. Confirm with the AWS CLI (aws s3api head-object --bucket b --key k) whether the endpoint really omits ETag.
  2. Fix or upgrade the S3-compatible server/gateway so HEAD includes ETag.
  3. In tests, set 'ETag' in the stubbed HeadObject result.
  4. As a workaround, call Filesystem::checksum() with an unsupported algo to force the stream-hashing fallback path.

Example fix

// before
$adapter->checksum('k', new Config()); // against gateway without ETag -> throws

// after (force stream fallback)
$filesystem = new Filesystem($adapter);
$md5 = $filesystem->checksum('k', ['checksum_algo' => 'md5']); // adapter rejects -> Filesystem hashes stream
Defensive patterns

Strategy: fallback

Validate before calling

// Probe once per environment: does HEAD on this endpoint include ETag?
$result = $s3Client->headObject(['Bucket' => $bucket, 'Key' => 'healthcheck.txt']);
$endpointProvidesEtag = array_key_exists('ETag', $result->toArray());
// if false, force the stream path: Filesystem::checksum($path, ['checksum_algo' => 'md5'])

Try / catch

try {
    $etag = $filesystem->checksum($path, ['checksum_algo' => 'etag']);
} catch (UnableToProvideChecksum $e) {
    if (str_contains($e->getMessage(), 'ETag header not available.')) {
        // endpoint quirk: compute a real digest from the stream
        $ctx = hash_init('sha256');
        hash_update_stream($ctx, $filesystem->readStream($path));
        return hash_final($ctx);
    }
    throw $e;
}

Prevention

When it happens

Trigger: checksum() against MinIO/Ceph/gateway deployments that omit ETag on HEAD; SDK middleware (e.g. custom middleware stack or decoders) dropping the ETag header; unit tests stubbing HeadObject output without an ETag field.

Common situations: Dev/test parity issues (MinIO locally vs AWS in prod); upgraded SDK middleware that unintentionally strips headers; integration harnesses returning hand-built Result objects.

Related errors


AI-assisted analysis of thephpleague/flysystem@b277b5dc3d (2026-08-17). Data as JSON: /api/errors/7c70aa581734f5a1. Report an issue: GitHub.