{"record":{"id":"6e9bcab0d88516f2","repo":"thephpleague/flysystem","slug":"etag-header-not-available","errorCode":null,"errorMessage":"ETag header not available.","messagePattern":"ETag header not available\\.","errorType":"exception","errorClass":"UnableToProvideChecksum","httpStatus":null,"severity":"error","filePath":"src/AsyncAwsS3/AsyncAwsS3Adapter.php","lineNumber":569,"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            $request = new GetObjectRequest([\n                'Bucket' => $this->bucket,\n                'Key' => $this->prefixer->prefixPath($path),\n            ] + $config->get('get_object_options', []));\n\n            return $this->client->presign($request, DateTimeImmutable::createFromInterface($expiresAt));\n        } catch (Throwable $exception) {\n            throw UnableToGenerateTemporaryUrl::dueToError($path, $exception);\n        }\n    }","sourceCodeStart":551,"sourceCodeEnd":587,"githubUrl":"https://github.com/thephpleague/flysystem/blob/b277b5dc3d56650b68904117124e79c851e12376/src/AsyncAwsS3/AsyncAwsS3Adapter.php#L551-L587","documentation":"The HeadObject call in AsyncAwsS3Adapter::checksum() succeeded, but the returned extraMetadata array contains no 'ETag' key, so no checksum can be produced. Real AWS S3 always returns an ETag on HEAD, so hitting this usually means the response came from an S3-compatible service or an intermediary that omits it.","triggerScenarios":"Running checksum() against an S3-compatible server (MinIO, Ceph RGW, storage gateways) that does not include ETag in HEAD responses; using a mocked/stubbed AsyncAws result object in tests that was built without setting the ETag header; a proxy or signed-URL layer stripping object metadata.","commonSituations":"Local/dev environments use MinIO while production uses AWS and behavior differs; test suites with hand-constructed HeadObjectResponse mocks; exotic object stores fronted by custom HTTP middleware.","solutions":["Reproduce with a direct HeadObject (aws s3api head-object or the SDK) to confirm the server actually omits ETag.","If the server is at fault, upgrade/reconfigure it, or switch the checksum path to stream hashing via Filesystem::checksum() with an algorithm the adapter does not support (falls back to hashing the read stream).","In tests, build the mocked HeadObjectResponse with a realistic ETag (e.g. '\"d41d8cd98f00b204e9800998ecf8427e\"').","Catch UnableToProvideChecksum and check the message for 'ETag header not available.' to distinguish it from network/auth causes."],"exampleFix":"// before (mock without ETag)\n$client->headObject(['Bucket' => 'b', 'Key' => 'k'])->resolve(); // result has no ETag\n$adapter->checksum('k', new Config()); // throws 'ETag header not available.'\n\n// after (realistic mock)\n$result = new HeadObjectResponse();\n$result->initialize(['ETag' => '\"9b2cf535f27731c974343645a3985328\"']);","handlingStrategy":"fallback","validationCode":"// Smoke-test the endpoint once at boot: HEAD an object and confirm ETag presence\n$response = $s3Client->headObject(['Bucket' => $bucket, 'Key' => $probeKey])->resolve();\nif ($response->getETag() === null) {\n    // endpoint omits ETag: route checksums to the stream-hashing path from now on\n    $useStreamChecksums = true;\n}","typeGuard":null,"tryCatchPattern":"try {\n    $checksum = $filesystem->checksum($path, ['checksum_algo' => 'etag']);\n} catch (UnableToProvideChecksum $e) {\n    if (str_contains($e->getMessage(), 'ETag header not available.')) {\n        $ctx = hash_init('md5');\n        hash_update_stream($ctx, $filesystem->readStream($path));\n        $checksum = hash_final($ctx);\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Test checksum behavior against your actual S3-compatible endpoint, not only AWS.","Build test doubles with realistic HeadObject responses including ETag.","Track which endpoints omit ETag and route those workloads to stream hashing."],"tags":["php","flysystem","s3","async-aws","checksum","etag","metadata"],"backgroundTag":"missing-etag-header","analyzedSha":"b277b5dc3d56650b68904117124e79c851e12376","analyzedAt":"2026-08-17T04:28:35.741Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}