Skip to content

wrangler d1 migrations apply fails on trigger migration while d1 execute --file succeeds #15178

Description

wrangler d1 migrations apply fails where d1 execute --file succeeds

Status: root cause isolated, not yet filed with Cloudflare.
Impact: staging cannot be brought up to date. Any future migration
containing triggers will not deploy to production either.
Investigated: 2026-08-11, in a disposable D1 database created and deleted in
the same session. Staging and production were never touched.

Summary

npx wrangler d1 migrations apply <db> --remote fails with:

A request to the Cloudflare API (/accounts/…/d1/database/…/query) failed.
  incomplete input: SQLITE_ERROR [code: 7500]

The identical migration file applies successfully through
wrangler d1 execute <db> --remote --file <same file> — 98 queries, no error.

The SQL is therefore valid and the remote API accepts it. The fault is in the
migrations apply code path, which appends its own ledger INSERT to the
migration text and re-splits the combined statement list differently than
--file does.

Minimal reproduction

Failing file: migrations/0043_partner_trust.sql (23,753 bytes, 21 triggers).

# Fails — incomplete input: SQLITE_ERROR [code: 7500]
npx wrangler d1 migrations apply <db> --remote

# Succeeds — "Executed 98 queries in 77.67ms"
npx wrangler d1 execute <db> --remote --file migrations/0043_partner_trust.sql

Both run against the same database, at the same migration position (42 prior
migrations applied), with the same wrangler version.

What was ruled out, and how

Every hypothesis below was tested and disproven. Recorded so nobody re-walks
them.

Hypothesis Test Result
wrangler version regression Retried on 4.121.0 Same failure
…specifically a regression since production migrated Retried on 4.113.0, the version pinned in the lockfile at the commit that authored these migrations Same failure
Invalid SQL scripts/check-migrations-replay.mjs replays all 106 into empty SQLite 106/106 clean
Invalid SQL (D1 semantics) Fresh local D1, migrations apply --local All applied
Triggers are unsupported remotely Single CREATE TRIGGER via --file to a remote DB Succeeded
Production never really had them SELECT COUNT(*) FROM sqlite_master WHERE type='trigger' on production 93 triggers, incl. partner_payout_* from 0058
Batch size / payload limit 60 trivial non-trigger migrations via migrations apply All applied; wrangler applies one file per request
BEGIN … ; END; on a single line breaks the splitter Two triggers, identical semantics, one multi-line and one single-line Both succeeded
The file itself is bad d1 execute --file on 0043 Succeeded, 98 queries

The last two rows together are the finding: same file, same database, same
version — fails through one command, succeeds through the other.

Why staging reports a different first-failure than the replay

Staging has 00010057 applied and reports 49 pending from 0058. The
disposable replay started empty and stopped after 0042, i.e. on 0043. Both
are the same defect surfacing at whichever trigger-bearing migration each
database reaches first.

Proposed workaround — NOT YET IMPLEMENTED

Design only. Requires review before it is written or run.

For each pending migration, in ledger order:

  1. wrangler d1 execute <db> --remote --file <migration> — the path proven to
    work.
  2. Insert the ledger row that migrations apply would have written, so the
    migration is not re-applied later.
  3. Re-read the ledger and assert it advanced by exactly one, and that the
    expected name is present, before moving to the next file.

Requirements this must meet:

  • Ordered. Never skip or reorder; a data migration may depend on a schema
    migration before it.
  • Resumable. Re-running after an interruption must continue, not restart.
  • Ledger-verified before and after every step, not just at the end.
  • Aborts on the first failure rather than continuing.
  • Must not run against production without a separate, explicit decision.

The ledger table name and row shape must be read from a database wrangler
itself has written, not assumed.

Filing

Not yet filed. When filed, against cloudflare/workers-sdk, include: the
summary, the two commands, the ruled-out table, wrangler versions 4.113.0 /
4.120.0 / 4.121.0 all reproducing, and the observation that --file succeeds
on identical input.

Metadata

Metadata

Assignees

No one assigned

    Labels

    product:d1Relating to Cloudflare D1: https://developers.cloudflare.com/d1/

    Type

    No type

    Projects

    Status
    Untriaged

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions