{"record":{"id":"7c70aa581734f5a1","repo":"thephpleague/flysystem","slug":"etag-header-not-available-7c70aa","errorCode":null,"errorMessage":"ETag header not available.","messagePattern":"ETag header not available\\.","errorType":"exception","errorClass":"UnableToProvideChecksum","httpStatus":null,"severity":"error","filePath":"src/AwsS3V3/AwsS3V3Adapter.php","lineNumber":503,"sourceCode":"        }\n    }\n\n    public function checksum(string $path, Config $config): string\n    {\n        $algo = $config->get('checksum_algo', 'etag');\n\n        if ($algo !== 'etag') {\n            throw new ChecksumAlgoIsNotSupported();\n        }\n\n        try {\n            $metadata = $this->fetchFileMetadata($path, 'checksum')->extraMetadata();\n        } catch (UnableToRetrieveMetadata $exception) {\n            throw new UnableToProvideChecksum($exception->reason(), $path, $exception);\n        }\n\n        if ( ! isset($metadata['ETag'])) {\n            throw new UnableToProvideChecksum('ETag header not available.', $path);\n        }\n\n        return trim($metadata['ETag'], '\"');\n    }\n\n    public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string\n    {\n        try {\n            $options = $config->get('get_object_options', []);\n            $command = $this->client->getCommand('GetObject', [\n                    'Bucket' => $this->bucket,\n                    'Key' => $this->prefixer->prefixPath($path),\n                ] + $options);\n\n            $presignedRequestOptions = $config->get('presigned_request_options', []);\n            $request = $this->client->createPresignedRequest($command, $expiresAt, $presignedRequestOptions);\n\n            return (string) $request->getUri();","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/thephpleague/flysystem/blob/b277b5dc3d56650b68904117124e79c851e12376/src/AwsS3V3/AwsS3V3Adapter.php#L485-L521","documentation":"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.","triggerScenarios":"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.","commonSituations":"Dev/test parity issues (MinIO locally vs AWS in prod); upgraded SDK middleware that unintentionally strips headers; integration harnesses returning hand-built Result objects.","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."],"exampleFix":"// before\n$adapter->checksum('k', new Config()); // against gateway without ETag -> throws\n\n// after (force stream fallback)\n$filesystem = new Filesystem($adapter);\n$md5 = $filesystem->checksum('k', ['checksum_algo' => 'md5']); // adapter rejects -> Filesystem hashes stream","handlingStrategy":"fallback","validationCode":"// Probe once per environment: does HEAD on this endpoint include ETag?\n$result = $s3Client->headObject(['Bucket' => $bucket, 'Key' => 'healthcheck.txt']);\n$endpointProvidesEtag = array_key_exists('ETag', $result->toArray());\n// if false, force the stream path: Filesystem::checksum($path, ['checksum_algo' => 'md5'])","typeGuard":null,"tryCatchPattern":"try {\n    $etag = $filesystem->checksum($path, ['checksum_algo' => 'etag']);\n} catch (UnableToProvideChecksum $e) {\n    if (str_contains($e->getMessage(), 'ETag header not available.')) {\n        // endpoint quirk: compute a real digest from the stream\n        $ctx = hash_init('sha256');\n        hash_update_stream($ctx, $filesystem->readStream($path));\n        return hash_final($ctx);\n    }\n    throw $e;\n}","preventionTips":["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."],"tags":["php","flysystem","aws-s3","checksum","etag","s3-compatible"],"backgroundTag":"missing-etag-header","analyzedSha":"b277b5dc3d56650b68904117124e79c851e12376","analyzedAt":"2026-08-17T04:28:35.741Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}