Skip to content

[wrangler] Fix r2 object put and r2 bulk put storing a different key in local mode - #15261

Open
ondraulehla wants to merge 1 commit into
cloudflare:mainfrom
ondraulehla:fix/r2-local-object-key-encoding
Open

[wrangler] Fix r2 object put and r2 bulk put storing a different key in local mode#15261
ondraulehla wants to merge 1 commit into
cloudflare:mainfrom
ondraulehla:fix/r2-local-object-key-encoding

Conversation

@ondraulehla

@ondraulehla ondraulehla commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

After #15192 I went looking for the same shape of bug elsewhere in the local-mode commands, and r2 object put has one.

The key travels to the local bucket worker inside a URL, dispatchFetch(`http://localhost/${key}`) in src/r2/object.ts, and the worker pulls it back out with url.pathname.substring(1). Nothing decodes it, so storage ends up holding whatever URL parsing made of the key. get and delete never take that hop. They hand the key straight to the binding, so put writes one name and get looks for another.

Six uploads on wrangler 4.124.0, then the keys sitting in .wrangler/state:

my report.pdf          →  my%20report.pdf
héllo.txt              →  h%C3%A9llo.txt
archive#2026-01.zip    →  archive
archive#2026-02.zip    →  archive
invoices/2026/q1.pdf   →  invoices/2026/q1.pdf
plain.txt              →  plain.txt

Six uploads and five objects. Each one printed "Upload complete.", and the two archives quietly became one. Getting my report.pdf back afterwards fails with "The specified key does not exist." A % goes one of two ways. 50%off.pdf stops on "Invalid URL string.", while %41.txt reports success and lands as A.txt. kv key put "my report.pdf" on the same CLI keeps the space, because the local KV path calls namespace.put(key, ...) with no URL in the middle.

The fix encodes the key going in and decodes it coming out. Keys that work today keep working, invoices/2026/q1.pdf and my%20report.pdf both still round trip. Objects already in local state are left where they are, since nothing gets renamed, so an object an older wrangler stored as my%20report.pdf is still reachable only under that name. Two keys don't survive either way, . and .., because URL normalisation eats dot segments before any encoding can help. That's the same before and after, and I've left it alone. Bulk put builds the same URL, so it gets the same treatment.

Tests in src/__tests__/r2/local.test.ts cover a round trip over seven awkward keys, two keys that differ only after a #, and one bulk put. Without the fix those three fail on "The specified key does not exist." With it, src/__tests__/r2 runs 209 of 209. oxfmt --check, oxlint --deny-warnings --type-aware, tsc -p ./tsconfig.json and tsc -p ./src/__tests__/tsconfig.json are all clean.

I've left the remote path alone. It builds the same URL shape in src/r2/helpers/object.ts, and fetch() drops the fragment before the request goes out. Pointing wrangler at a local server with CLOUDFLARE_API_BASE_URL shows what it sends:

put "bucket/archive#2026-01.zip"     PUT /accounts/.../objects/archive
put "bucket/sale?50.pdf"             PUT /accounts/.../objects/sale?50.pdf
delete "bucket/archive#2026-01.zip"  DELETE /accounts/.../objects/archive

Encoding there would send invoices%2F2026%2Fq1.pdf where today it sends invoices/2026/q1.pdf, and I have no account to check whether the API reads those the same way. Say which shape you want and I'll add it here.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: this is a bug fix to local r2 object put and r2 bulk put, no command or option changes

Open in Devin Review

@changeset-bot

changeset-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dc25a99

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
wrangler Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk Aug 19, 2026
@workers-devprod
workers-devprod requested review from a team and penalosa and removed request for a team August 19, 2026 09:01
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/r2-local-object-key-encoding.md: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/r2/local.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/r2/helpers/object.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/r2/object.ts: [@cloudflare/wrangler]

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@ondraulehla ondraulehla changed the title [wrangler] Fix r2 object put and r2 bulk put storing a URL-encoded key in local mode [wrangler] Fix r2 object put and r2 bulk put storing a different key in local mode Aug 19, 2026
@ondraulehla
ondraulehla force-pushed the fix/r2-local-object-key-encoding branch from 588b357 to dc25a99 Compare August 19, 2026 09:04
@pkg-pr-new

pkg-pr-new Bot commented Aug 19, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/autoconfig@15261

@cloudflare/build-output-utils

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/build-output-utils@15261

@cloudflare/codemods

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/codemods@15261

@cloudflare/config

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/config@15261

create-cloudflare

npm i https://pkg.pr.new/cloudflare/workers-sdk/create-cloudflare@15261

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/deploy-helpers@15261

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/kv-asset-handler@15261

miniflare

npm i https://pkg.pr.new/cloudflare/workers-sdk/miniflare@15261

@cloudflare/pages-functions

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/pages-functions@15261

@cloudflare/pages-shared

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/pages-shared@15261

@cloudflare/unenv-preset

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/unenv-preset@15261

@cloudflare/vite-plugin

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/vite-plugin@15261

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/vitest-pool-workers@15261

@cloudflare/workers-auth

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/workers-auth@15261

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/workers-editor-shared@15261

@cloudflare/workers-utils

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/workers-utils@15261

wrangler

npm i https://pkg.pr.new/cloudflare/workers-sdk/wrangler@15261

commit: dc25a99

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants