denoland/celldPublic

self-hosted, distributed Durable Objects

R2 list() returns percent-encoded keys for non-ASCII and % characters, and get() with them returns null

NO

`R2Bucket.list()` returns keys that contain non-ASCII characters or `%` in percent-encoded form. A `get()` with the listed key returns `null`. ASCII keys, including keys with spaces, list unchanged, and `get()` with the original key works.

### Reproduction

`wrangler.jsonc`:

```jsonc { "name": "r2-list-repro", "main": "index.js", "compatibility_date": "2026-01-01", "r2_buckets": [{ "binding": "FILES", "bucket_name": "files" }] } ```

`index.js`:

```js export default { async fetch(request, env) { const keys = ["přehled.html", "plain.html", "with space.html", "100%.html"]; for (const key of keys) await env.FILES.put(key, "hello"); const listed = (await env.FILES.list()).objects.map((o) => o.key); const results = []; for (const key of listed) results.push({ listed: key, getWorks: (await env.FILES.get(key)) !== null }); return Response.json(results); }, }; ```

### Result

A node with `--bucket s3://…` against MinIO `RELEASE.2025-04-22T22-12-26Z`, celld 0.5.1:

```json [ {"listed": "100%25.html", "getWorks": false}, {"listed": "p%C5%99ehled.html", "getWorks": false}, {"listed": "plain.html", "getWorks": true}, {"listed": "with space.html", "getWorks": true} ] ```

The bucket holds the objects as `r2/files/p%C5%99ehled.html` and `r2/files/100%25.html`. `list()` seems to return the stored name without reversing the encoding that `put()` applies.

`celld dev`, 0.5.0 and 0.5.1, adds a second layer: `p%25C5%2599ehled.html` and `100%2525.html`.

### Expected

`list()` returns `přehled.html` and `100%.html`, the keys passed to `put()`, as Cloudflare R2 does.

### Environment

celld 0.5.0 and 0.5.1, `aarch64-apple-darwin` build, both `celld dev` and a node on an S3 bucket in MinIO `RELEASE.2025-04-22T22-12-26Z`.

  1. bartlomiejuassigned bartlomiejulast month on GitHub

  2. bartlomiejuclosedlast month on GitHub

  3. BA

    Thanks for the report, this is now fixed and will be released in the next version.

  4. bartlomiejureferenced 81d1c52fb5belast month on GitHub

  5. bartlomiejureferenced 0aa22f091625last month on GitHub

  6. bartlomiejureferenced 46692d514ac3last month on GitHub

  7. ryreferenced def3ad02192dlast month on GitHub

  8. bartlomiejureferenced bab562b22376last month on GitHub

  9. bartlomiejureferenced af69bb4337c5last month on GitHub

  10. bartlomiejureferenced bad4649d01f0last month on GitHub