# Known compatibility issues (/tools/s3-gateway/known-issues)

Places where Shelby's S3 API responds differently from Amazon S3, with a workaround for
each. None of these affect the durability of stored data. We remove entries as fixes ship.

{/*
  GENERATED FRAGMENT. Do not edit by hand.
  Source: data/s3-compat.json, checked against the beta-harness corpus.
  Regenerate: pnpm generate:s3
  */}

<Callout type="warn" title={"If-Match on PUT isn't checked yet"}>
  A PUT with `If-Match` overwrites the object even when the ETag does not match. If you use `If-Match` for optimistic concurrency, two writers can overwrite each other without either seeing an error.

  **Workaround:** Do not rely on `If-Match` for writes yet. Use `If-None-Match: *` for create-only writes, which is enforced, or coordinate writers in your application.
</Callout>

<Callout type="info" title={"Some bucket requests from the AWS SDK for JavaScript aren't recognised"}>
  The AWS SDK for JavaScript sends some bucket-level requests with a slash after the bucket name, for example `/<bucket>/?delete`. Shelby doesn't route that form yet. `DeleteObjects` returns a plain-text `404` and deletes nothing, and `GetBucketLocation`, `GetBucketVersioning`, `GetBucketAcl` and `ListMultipartUploads` return the bucket's object listing, which the SDK reads as an empty answer. MinIO `mc` runs into the same thing when it looks up the bucket's region.

  **Workaround:** In JavaScript, delete keys one at a time with `DeleteObjectCommand`, set `region: "default"` in the client rather than looking it up, and abort the multipart uploads you start rather than relying on `ListMultipartUploads` to find them. For `mc`, set `MC_REGION=default`.
</Callout>

<Callout type="info" title={"New objects can take a few seconds to appear in listings"}>
  GetObject and HeadObject see a new or overwritten object as soon as the write returns. ListObjects can take a few seconds longer to include a new object, so a listing taken straight after an upload may not show it yet.

  **Workaround:** If you list right after writing, retry until the key appears, or keep track of the keys you wrote. To check that one object exists, use `HeadObject`.
</Callout>

<Callout type="info" title={"Reads that overlap an overwrite can briefly fail"}>
  While a PUT is overwriting an object, a GET of the same key can fail with `500` or `503` instead of returning the old or the new version. On S3, a read during an overwrite always gets one version or the other.

  **Workaround:** Retry failed reads with backoff. The AWS SDKs and the AWS CLI retry `500` and `503` by default. If readers use a key while you replace it, write each new version to a new key instead, for example `model/v12/weights.bin`.
</Callout>

<Callout type="info" title={"Authentication errors are plain text, not S3 XML"}>
  Requests with a wrong key, a missing signature or a skewed clock are rejected with `401` and a short plain-text message, before they reach the S3 layer. SDKs report no S3 error code, and the AWS SDK for JavaScript reports an XML parse error instead.

  **Workaround:** Treat any `401` as a credential or clock problem. See [Troubleshooting](/tools/s3-gateway/troubleshooting#authentication-errors).
</Callout>

<Callout type="info" title={"JavaScript SDK presigned links need a client setting"}>
  By default, the AWS SDK for JavaScript adds `x-amz-checksum-mode=ENABLED` to presigned GetObject URLs. Shelby refuses those links with `501 NotImplemented`.

  **Workaround:** Set `responseChecksumValidation: "WHEN_REQUIRED"` on the S3 client you sign with, as `shelby-s3.ts` in the [JavaScript quickstart](/tools/s3-gateway/quickstarts/javascript) does. Shelby returns no response checksums, so no check is lost. See [Presigned URLs](/tools/s3-gateway/presigned-urls).
</Callout>

<Callout type="info" title={"max-keys above 1,000 returns an error instead of being capped"}>
  S3 clamps `max-keys` to 1,000. Shelby returns `400 InvalidArgument` instead, so a paginator configured with a larger page size fails.

  **Workaround:** Use a page size of 1,000 or less.
</Callout>

<Callout type="info" title={"Some bucket-configuration calls return 404 or 200 instead of 501"}>
  Shelby doesn't implement bucket configuration. Most of these calls return `501 NotImplemented`, but some currently return `404` with no S3 error code, or `200` with the object listing (for example `GET ?versions` and `GET ?website`). A tool can read that `200` as an empty configuration.

  **Workaround:** Treat bucket configuration as unavailable. None of these settings exist on Shelby.
</Callout>

<Callout type="info" title={"CompleteMultipartUpload returns an internal Location"}>
  The `Location` element in the `CompleteMultipartUpload` response names an internal address, not the public endpoint.

  **Workaround:** Ignore `Location`. Address the object by bucket and key, as SDKs do by default.
</Callout>

{/*
  GENERATED FRAGMENT. Do not edit by hand.
  Source: data/s3-compat.json, checked against the beta-harness corpus.
  Regenerate: pnpm generate:s3
  */}

*Last verified against the hosted endpoint on 2026-11-06.*
