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
- Confirm with the AWS CLI (aws s3api head-object --bucket b --key k) whether the endpoint really omits ETag.
- Fix or upgrade the S3-compatible server/gateway so HEAD includes ETag.
- In tests, set 'ETag' in the stubbed HeadObject result.
- 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
- Include a checksum smoke test in your CI against the real S3-compatible endpoint.
- Review custom SDK middleware for headers it strips from HEAD responses.
- Keep a registry of endpoint capabilities when supporting multiple S3 providers.
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
- ETag header not available.
- Unable to get checksum for $path: $reason
- Unable to get checksum for $path: $reason
- Unable to get checksum for $path: $reason
- No checksum provided in metadata
AI-assisted analysis of thephpleague/flysystem@b277b5dc3d (2026-08-17).
Data as JSON: /api/errors/7c70aa581734f5a1.
Report an issue: GitHub.