Fix S3 "InvalidArgument" errors when using Google Cloud Storage S3-compatible API with ObjectCannedAcl::PublicRead or x-amz-acl headers. Use when: (1) put_object or upload_blob fails with InvalidArgument on GCS, (2) Using aws-sdk-s3 Rust crate with GCS endpoint (storage.googleapis.com), (3) Bucket has uniform bucket-level access enabled (GCS default since 2023), (4) S3 operations work on MinIO/AWS but fail on GCS. GCS rejects per-object ACLs when uniform bucket-level access is enabled.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add divinevideo/divine-mobile --skill gcs-s3-compat-acl-rejection --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Gcs S3 Compat Acl Rejection?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/divinevideo-gcs-s3-compat-acl-rejection-divine-mobile)More formats (shields.io, HTML) on the badges page.
---
name: gcs-s3-compat-acl-rejection
description: |
Fix S3 "InvalidArgument" errors when using Google Cloud Storage S3-compatible API
with ObjectCannedAcl::PublicRead or x-amz-acl headers. Use when: (1) put_object or
upload_blob fails with InvalidArgument on GCS, (2) Using aws-sdk-s3 Rust crate with
GCS endpoint (storage.googleapis.com), (3) Bucket has uniform bucket-level access enabled
(GCS default since 2023), (4) S3 operations work on MinIO/AWS but fail on GCS.
GCS rejects per-object ACLs when uniform bucket-level access is enabled.
author: Claude Code
version: 1.0.0
date: 2026-03-21
---
# GCS S3-Compatible API ACL Rejection
## Problem
S3 `put_object` calls fail with `InvalidArgument` when targeting Google Cloud Storage
via its S3-compatible API, despite working correctly with MinIO or AWS S3.
## Context / Trigger Conditions
- Using `aws-sdk-s3` (Rust) or any S3 SDK with GCS endpoint
- `AWS_ENDPOINT=https://storage.googleapis.com`
- Error: `Error { code: "InvalidArgument", message: "Invalid argument." }`
- Stack trace shows `S3BlobStore::put_temp` or similar S3 put operation
- The GCS bucket has **uniform bucket-level access** enabled (GCS default)
## Solution
Remove `ObjectCannedAcl::PublicRead` from put_object calls when using GCS:
```rust
// BEFORE (fails on GCS with uniform bucket-level access)
self.client
.put_object()
.body(body)
.bucket(&self.s3_bucket)
.key(key)
.acl(ObjectCannedAcl::PublicRead) // This causes InvalidArgument
.send()
.await?;
// AFTER (works with GCS)
self.client
.put_object()
.body(body)
.bucket(&self.s3_bucket)
.key(key)
// Remove .acl() — use bucket-level IAM instead
.send()
.await?;
```
For public read access, configure the bucket IAM policy instead:
```bash
gsutil iam ch allUsers:objectViewer gs://YOUR_BUCKET
```
Or disable uniform bucket-level access (not recommended):
```bash
gsutil ubla set off gs://YOUR_BUCKET
```
## Verification
After removing the `.acl()` call, upload should succeed:
```bash
curl -X POST "$PDS_URL/xrpc/com.atproto.repo.uploadBlob" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: video/mp4" \
--data-binary @test.mp4
```
## Notes
- GCS uniform bucket-level access has been the default since 2023
- The `copy_object` call in `move_object()` also uses `.acl(PublicRead)` and will fail
- This affects rsky-pds blob storage: `rsky-pds/src/actor_store/aws/s3.rs` lines 69, 97, 212
- MinIO doesn't enforce this restriction, so local dev works but production GCS fails
- The same issue applies to `put_permanent()` and `move_object()` methods
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!