Skip to content

Conversation

@emily-shen
Copy link
Contributor

@emily-shen emily-shen commented Jan 6, 2026

Fixes #11802.

Now that resource provisioning means wrangler dev doesn't need database_ids, local versions of d1 commands also shouldn't need the id.


Link to Devin run: https://app.devin.ai/sessions/92cc6dea3f1543ecb6667ad7d1f85ad0
Devin PR requested by @emily-shen
(rewrote a lot of it though)

  • Tests
    • Tests included/updated
    • Tests not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: This is a bug fix
Screenshot 2026-01-07 at 17 35 27
Open with Devin

@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@changeset-bot
Copy link

changeset-bot bot commented Jan 6, 2026

🦋 Changeset detected

Latest commit: 509a79b

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

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

@claude
Copy link
Contributor

claude bot commented Jan 6, 2026

Claude finished @emily-shen's task —— View job


Changeset Review

✅ All changesets look good

Validation Summary:

  • Version Type: patch is correct for this bug fix
  • Changelog Quality: Clear description of what was fixed and scope (local operations only)
  • Markdown Headers: No h1/h2/h3 headers present
  • Package Coverage: Correctly targets wrangler package

The changeset properly describes the bug fix with appropriate detail. The title is clear and the use of locally (with italics) appropriately emphasizes the scope of the fix.


@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 6, 2026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@11804

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@11804

miniflare

npm i https://pkg.pr.new/miniflare@11804

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@11804

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@11804

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@11804

@cloudflare/vitest-pool-workers

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

@cloudflare/workers-editor-shared

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

@cloudflare/workers-utils

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

wrangler

npm i https://pkg.pr.new/wrangler@11804

commit: 509a79b

@emily-shen emily-shen force-pushed the devin/1767697868-fix-d1-execute-local-without-database-id branch from d60f443 to 2497179 Compare January 7, 2026 17:32
@emily-shen emily-shen marked this pull request as ready for review January 7, 2026 17:36
@emily-shen emily-shen requested review from a team as code owners January 7, 2026 17:36
@alsuren
Copy link
Contributor

alsuren commented Jan 8, 2026

I've not dug into the code yet, but from some local testing with wrangler.js d1 migrations apply on this branch:

1: If you have 2 bindings pointing at the same database name, it will do the migration separately for each binding

to reproduce:

wrangler.jsonc:

{
  "d1_databases": [
    {
      "binding": "dlaban_workers_sdk_5438_on_delete_cascade",
      "database_name": "dlaban-workers-sdk-5438-on-delete-cascade"
    },
    {
      "binding": "dlaban_workers_sdk_5438_on_delete_cascade_2",
      "database_name": "dlaban-workers-sdk-5438-on-delete-cascade"
    }
  ]
}

migrations/0001_test-migration.sql: this can be empty

This works as expected:

$ wrangler.js d1 migrations apply dlaban_workers_sdk_5438_on_delete_cascade

 ⛅️ wrangler 4.56.0 (update available 4.58.0)
─────────────────────────────────────────────
Resource location: local 

Use --remote if you want to access the remote instance.

Migrations to be applied:
┌─────────────────────────┐
│ name                    │
├─────────────────────────┤
│ 0001_test-migration.sql │
└─────────────────────────┘
✔ About to apply 1 migration(s)
Your database may not be available to serve requests during the migration, continue? … yes
🌀 Executing on local database dlaban_workers_sdk_5438_on_delete_cascade (dlaban_workers_sdk_5438_on_delete_cascade) from .wrangler/state/v3/d1:
🌀 To execute on your remote database, add a --remote flag to your wrangler command.
🚣 1 command executed successfully.
┌─────────────────────────┬────────┐
│ name                    │ status │
├─────────────────────────┼────────┤
│ 0001_test-migration.sql │ ✅     │
└─────────────────────────┴────────┘

$ wrangler.js d1 migrations apply dlaban_workers_sdk_5438_on_delete_cascade

 ⛅️ wrangler 4.56.0 (update available 4.58.0)
─────────────────────────────────────────────
Resource location: local 

Use --remote if you want to access the remote instance.

✅ No migrations to apply!

I would expect migrating the second binding to also say ✅ No migrations to apply! but instead it says:

wrangler.js d1 migrations apply dlaban_workers_sdk_5438_on_delete_cascade_2

 ⛅️ wrangler 4.56.0 (update available 4.58.0)
─────────────────────────────────────────────
Resource location: local 

Use --remote if you want to access the remote instance.

Migrations to be applied:
┌─────────────────────────┐
│ name                    │
├─────────────────────────┤
│ 0001_test-migration.sql │
└─────────────────────────┘
✖ About to apply 1 migration(s)
Your database may not be available to serve requests during the migration, continue? … yes

2: If you start without a database id, and then add it in later, it also attempts to migrate the db from scratch

To reproduce:

After doing the migration above, change wrangler.toml to:

{
  "d1_databases": [
    {
      "binding": "dlaban_workers_sdk_5438_on_delete_cascade",
      "database_name": "dlaban-workers-sdk-5438-on-delete-cascade",
      "database_id": "0e34f968-245c-4e25-853b-5e67c05420cf"
    },
    {
      "binding": "dlaban_workers_sdk_5438_on_delete_cascade_2",
      "database_name": "dlaban-workers-sdk-5438-on-delete-cascade",
      "database_id": "0e34f968-245c-4e25-853b-5e67c05420cf"
    }
  ]
}

and then run wrangler.js d1 migrations apply dlaban_workers_sdk_5438_on_delete_cascade - it will prompt you to migrate the db again, even though it should already be migrated.

I think these are both symptoms of it not knowing how to map things to the appropriate .wrangler/state/v3/d1/miniflare-D1DatabaseObject/ sqlite db.

  • for 1: I feel like it should use database_name rather than the binding name if it doesn't have a database_id
  • for 2: I'm not sure. I wonder if it should have some kind of fallback behaviour, so if it doesn't see a db with the right database_id, but there is one for the database_name, it should prefer the one for database_name.

There are going to be bunch of horrible edge cases here, where old wrangler versions use one lookup scheme and new ones do something different. We can simulate the upgrade from current wrangler in tests by making bindings with just database_id, and then adding database_name later maybe?

@devin-ai-integration
Copy link
Contributor

Thanks for the thorough testing! You've identified two real edge cases that stem from the choice of "local persistence identity" when database_id is absent.

Current behavior (this PR): Uses binding as the fallback ID, which matches what wrangler dev currently does (see d1DatabaseEntry in dev/miniflare/index.ts line 243: const id = getRemoteId(preview_database_id ?? database_id) ?? binding).

Your suggestions:

  1. Use database_name instead of binding as fallback - this would fix the "multiple bindings to same database" case
  2. Add fallback behavior when database_id is added later

I intentionally matched wrangler dev's current behavior for consistency, but I agree that the edge cases you found show this fallback choice is problematic.

Options:

  • (A) Keep parity with wrangler dev for now - Ship this PR as-is (fixing the original issue D1 execute (local) fails if database_id is not provided #11802) and track the persistence identity improvement as a follow-up issue. This means users hitting your edge cases would need to wait for a separate fix.
  • (B) Change the identity rule to use database_name - This would fix both edge cases but would require also updating wrangler dev's d1DatabaseEntry for consistency. Otherwise we'd have local CLI commands using database_name while wrangler dev uses binding, which would be worse.

What's the preferred approach here? I'm happy to expand the scope of this PR if maintainers want option (B), but wanted to check before making changes that affect wrangler dev behavior too.

Also worth noting: changing the fallback from binding to database_name could affect existing users who already have local state under the binding-based path (their local DB would appear to "disappear" after upgrading). We might need a compatibility migration or at least a clear warning.

Copy link
Contributor

@alsuren alsuren left a comment

Choose a reason for hiding this comment

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

We had a chat internally and decided to go with (A) Keep parity with wrangler dev for now.

Approved with comments.

name: string,
options?: {
/** Local databases might not have a database id, so we don't require it for local-only operations */
requireDatabaseId?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

(nit that you don't necessarily need to do anything about): I am generally not a fan of optional boolean flags that default to true.

It seems that e.g. isLocal({local, remote}, default) defaults to true as well though, so that might be the wrangler code style.

If you're keeping it like this, maybe call out what the default is in the flag's docstring?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i think that's a losing battle to fight in this codebase 😅

but for my own interest, why? consistency?

Copy link
Contributor

@alsuren alsuren Jan 12, 2026

Choose a reason for hiding this comment

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

I can't remember where I picked up the habit. Possibly from writing untyped javascript, when "go to definition" in your editor wasn't so reliable. I have a feeling that being able to provide a default value for js positional function arguments is an ES6 thing, and it was common for people to just pass the resulting undefined into if statements, and relied on it being falsy? (maybe I'm misremembering?)

Probably just me showing my age, and not a thing people care about anymore.

I also have thing about boolean variables that have negative-sounding names. I have a very small brain, and those take me too long to read each time. I think we're okay here though.

if (name === d1Database.database_name || name === d1Database.binding) {
if (requireDatabaseId && !d1Database.database_id) {
throw new UserError(
`Found a database with name or binding ${name} but it is missing a database_id, which is needed for operations on remote resources. Please create the remote D1 database by deploying your project or running 'wrangler d1 create ${name}'.`
Copy link
Contributor

Choose a reason for hiding this comment

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

This means that adding a second binding with a matching binding/database_name but without a database_id might or might not poison future invocations of wrangler commands, or might not, depending on which order we iterate over things.

In the face of a config with some bindings having database_id and some not, Options are to make it consistently permissive (by raising a warning and then continuing?), make it consistently strict (by validating all d1_databases before returning), or leave it inconsistent as it is now.

This is a super edge case, and making things consistent might add unnecessary complexity, so use your judgement here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm i think this is enough of an edgecase to ignore - having duplicate bindings names is already forbidden, so that won't be an issue, and i can't really see why you'd end up having two bindings to the same database_name, and also only have one of them with a database_id.

@github-project-automation github-project-automation bot moved this from Untriaged to Approved in workers-sdk Jan 9, 2026
@emily-shen emily-shen force-pushed the devin/1767697868-fix-d1-execute-local-without-database-id branch from 1c9ef8c to 62812f8 Compare January 12, 2026 17:49
@penalosa penalosa force-pushed the devin/1767697868-fix-d1-execute-local-without-database-id branch from 62812f8 to 509a79b Compare January 27, 2026 12:26
Copy link
Contributor

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

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 potential bugs to report.

View in Devin Review to see 4 additional flags.

Open in Devin Review

@emily-shen emily-shen merged commit 3b06b18 into main Jan 27, 2026
36 of 37 checks passed
@emily-shen emily-shen deleted the devin/1767697868-fix-d1-execute-local-without-database-id branch January 27, 2026 14:16
@github-project-automation github-project-automation bot moved this from Approved to Done in workers-sdk Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

D1 execute (local) fails if database_id is not provided

3 participants