Drizzle D1: migrations do not match any local migration
Why drizzle-kit migrate on Cloudflare D1 works once and then rejects every migration it applied, with id and created_at undefined, and the version pair that makes repeat runs pass again.
TL;DR#
On the drizzle-kit and drizzle-orm 1.0.0-rc.4 pre-releases, drizzle-kit migrate with driver: 'd1-http' succeeds on an empty Cloudflare D1 database and then fails on every later run. The message counts every row of the migration log as unknown, each with [id: undefined, created_at: undefined]. The migrator reads those rows by position, but in rc.4 the d1-http driver fetches them from D1's /query endpoint, which returns objects. Upgrade both packages to 1.0.0-rc.5: in a reproduction against a local stub of the D1 HTTP API, that single change let the pending migration apply and repeat runs pass. A Drizzle maintainer's interim advice was to stay on rc.3.
The error, as drizzle-kit migrate prints it on the second run:
[✗] Error during migration:
While upgrading your database migrations table we found 1 ([id: undefined, created_at: undefined]) migrations in the database that do not match any local migration. This means that some migrations were applied to the database but are missing from the local environmentThe report is drizzle-orm issue #5995. It was filed on 9 July 2026 against drizzle-orm 1.0.0-rc.4-5d5b77c and drizzle-kit 1.0.0-rc.4-ca0f029, using d1-http with the sqlite dialect. The reporter's database held 25 rows, so the message listed 25 identical [id: undefined, created_at: undefined] pairs. A second user then gave the steps: create a D1 database, apply migrations with rc.4, then either reapply them or change the schema and migrate again. Either path ends in this error.
What the undefined values are telling you#
Read the message literally. It describes a drifted database: rows in __drizzle_migrations with no matching folder under drizzle/. The values give that reading away. A real orphan row still has an id and a created_at. Here every row has neither. Your migration folders are not missing. The rows are being read in a shape the migrator does not expect.
A Drizzle maintainer reproduced the report on 14 September 2026. The next day, on the issue, they wrote: "Everything works correctly on the rc.3 version". They added: "There was a session logic rework in rc.4 that introduced a bug." The code shows where.
Why it happens#
Cloudflare's REST API has two endpoints for running SQL against a D1 database. /query is described as "Execute a SQL query against a D1 database and return results as objects." /raw returns "result rows as arrays rather than objects": its results field is a { columns, rows } pair. The quotes come from Cloudflare's OpenAPI schema.
drizzle-kit's d1-http driver wraps these endpoints in a callback for drizzle-orm's sqlite-proxy driver. In drizzle-kit's connection code at the v1.0.0-rc.4 tag, the callback picks /raw only when it receives the method name values. Anything else goes to /query.
The drizzle-orm side changed in rc.4. In sqlite-proxy/session.ts at v1.0.0-rc.4, the session's values executor, the one used for array-mode reads, calls the callback as this.client(query.sql, params, 'all'). The same file at v1.0.0-rc.3 passes 'values'. On rc.4, every read that should return arrays reaches /query and comes back as objects.
The migration-log upgrade step in up-migrations/sqlite-proxy.ts at v1.0.0-rc.4 reads those results by position. It reads the log's column list as r[0] and each log row as row[0], row[1] and row[2]. Indexing an object by number gives undefined. That causes two misreadings in a row:
- Every column name reads as
undefined. The migrator finds nonamecolumn, decides the log is in the old layout and starts an upgrade. In the reproduction the table already hadid, hash, created_at, name, applied_at, written by the same rc.4 CLI on the first run. - No row matches a local migration. The upgrade then matches each row against your local folders by
created_at, then byhash, and both areundefined. Every row lands in the unmatched list, and the error counts them. That is why the reporter saw 25 and a one-migration project sees 1.
The first run succeeds because it never reaches this step. The existence check only counts the rows returned for sqlite_master, and objects count as well as arrays do.
A community pull request, #6340, described the same mechanism: "journal reads receive object rows from Cloudflare's /query endpoint, while the SQLite proxy migrator reads columns by position." It was closed without being merged. What changed the routing is the broader rc.5 rework below. Its release notes do not mention this issue or D1.
Fix: move drizzle-kit and drizzle-orm to rc.5 together#
The v1.0.0-rc.5 release notes list a breaking "Reworked sqlite-proxy". The callback is replaced by an executors object with run, all and get handlers, and a rowMode of 'array' or 'object'. The values method is dropped "due to it being duplicate of array-mode all". drizzle-kit rc.5's d1-http driver, in its connection code at the v1.0.0-rc.5 tag, sends object mode to /query and array mode to /raw. The npm registry dates 1.0.0-rc.5 to 21 October 2026, under the rc dist-tag. The latest tag still points at a stable release, not at the pre-releases. On 22 October a reporter confirmed on the issue: "Fixed on rc.5 version".
- Pin both packages to the same pre-release:
npm install drizzle-orm@1.0.0-rc.5
npm install -D drizzle-kit@1.0.0-rc.5- Check that the CLI loads the pair you expect:
npx drizzle-kit --versiondrizzle-kit: v1.0.0-rc.5
drizzle-orm: v1.0.0-rc.5- Run the migration again, with no change to
drizzle.config.tsor to thedrizzle/folder:
npx drizzle-kit migrateReproduced on 30 October 2026 in node:24-alpine, with api.cloudflare.com pointed at a local stub that answers /query and /raw in the shapes of Cloudflare's schema. This was not a real D1 database. On rc.4, the second migrate failed with exit code 1. The run after that, with a new migration that adds a users.email column, failed with the same count. After upgrading both packages to rc.5, the same command against the same database applied the pending migration and printed [✓] migrations applied successfully!. The stub's request log shows the change: on rc.4 the three log reads went to query, and on rc.5 they went to raw.
Verify the fix#
Run npx drizzle-kit migrate twice in a row. On rc.5 both runs should end with [✓] migrations applied successfully! and exit code 0, as they did in the reproduction. Then, on the real D1 database (the stub-based reproduction did not cover this step), read the log with Wrangler's d1 execute, described in Cloudflare's D1 getting-started guide:
npx wrangler d1 execute <your-database-name> --remote --command="SELECT id, name, created_at FROM __drizzle_migrations ORDER BY id"You should see one row per migration folder, each with a numeric id and a filled name. In the reproduction the log held two rows after the rc.5 run, 20261030212317_init and 20261030212415_add-email, and users had gained email.
The failed rc.4 runs did not damage anything in the reproduction. The error is thrown before the upgrade's ALTER TABLE and before any pending migration runs. After a failed run the log still had its single row and users still had only id, name. Do not empty or drop __drizzle_migrations to get past the message. Both were tried on rc.4 against the stub, which runs SQLite:
- Rows deleted, table kept. The log still looks like the old layout, because the column list is still misread. The upgrade finds nothing to match and goes on to
ALTER TABLE … ADD COLUMN name, which stops on SQLite'sduplicate column name: name. - Table dropped. The migrator treats the database as new and replays the first migration folder, whose
CREATE TABLEstops ontable `users` already exists. Therelation "users" already existswalkthrough explains the same replay on PostgreSQL, where the log is empty becausedrizzle-kit pushnever filled it.
In both runs the stub prefixed the message with its own error code. A real D1 database returns its own code.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Half-upgraded pairs fail with a different message#
drizzle-kit 1.0.0-rc.5 declares drizzle-orm >=1.0.0-rc.5 as an optional peer dependency, and npm only warns when it is not met. Because the sqlite-proxy interface changed between the two releases, a mixed pair breaks in a new way. Both combinations were reproduced against the same database:
$ npx drizzle-kit --version
drizzle-kit: v1.0.0-rc.4
drizzle-orm: v1.0.0-rc.5
$ npx drizzle-kit migrate
[✗] Error during migration:
this.client.all is not a function$ npm install drizzle-kit@1.0.0-rc.5 drizzle-orm@1.0.0-rc.4 # kit upgraded, orm left behind
npm warn peerOptional drizzle-orm@">=1.0.0-rc.5" from drizzle-kit@1.0.0-rc.5
$ npx drizzle-kit --version
drizzle-kit: v1.0.0-rc.5
drizzle-orm: v1.0.0-rc.4
$ npx drizzle-kit migrate
[✗] Error during migration:
this.client is not a functionIf one of these replaces the do not match any local migration error after an upgrade, check npx drizzle-kit --version. In a monorepo, also check which drizzle-orm the CLI resolves. The two versions must match. The same pattern, a CLI and an ORM disagreeing on the client they hand each other, is behind the Cannot destructure property 'resourceArn' error on the AWS Data API driver.
If you cannot move to rc.5 yet#
- Stay on rc.3. In the control run, the drizzle-kit and drizzle-orm
1.0.0-rc.3pair on a fresh database ranmigratetwice, and both runs succeeded. That run did not cover moving an existing rc.4 database back to rc.3. - Use the migrator from code. The maintainer's other suggestion was: "Alternatively, you can use the programmatic
migratefunction from the code". That path does not go through the CLI'sd1-httpcallback. Drizzle's D1 connection guide covers the Worker-side driver. The migrate-hang article shows the same idea on PostgreSQL, running the drizzle-orm migrator yourself when the CLI misbehaves.
drizzle-kit push is not a workaround for a migrate workflow. It has its own SQLite-family failure, covered in the push This statement does not return data article.
When the D1 message lists only undefined ids and dates, it does not mean your migration history has drifted. It means rc.4 is reading the log through the wrong endpoint. A genuine mismatch, such as a deleted or renamed migration folder, would print real ids and timestamps that you can look up. The fix is a matched rc.5 pair, or a matched rc.3 pair until you can upgrade.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix: drizzle-kit push — statement does not return data
drizzle-kit push failing with a driver-level TypeError on SQLite/libSQL/Turso setups. Why .run() and .get() aren't interchangeable, and the two safe ways around it.
drizzle-kit migrate: relation "users" already exists
Get drizzle-kit migrate working on a database that drizzle-kit push created: why the first CREATE TABLE runs again, how to mark the old migrations as applied, and why IF NOT EXISTS only hides the problem.
Fix drizzle-kit migrate Hanging After Using 'pg' Driver
Get a real error message, or a finished migration, out of a drizzle-kit migrate that sits on its last line: two causes you can rule out in a minute, and two ways to apply the migrations anyway.


