Skip to content

Commit 02518dd

Browse files
committed
chore(cloud): delete orphaned KV McpSessionDO + stop PR builds failing on DO migrations
- v3 deletes the orphaned KV McpSessionDO (the MCP_SESSION binding moved to the SQLite class in v2, so nothing references it now); drop the stub export. - build.mjs strips DO migrations from the generated deploy config on non-main CI branches. Preview deploys use 'wrangler versions upload', which rejects unapplied migrations (error 10211); previews share prod's applied DO state so they neither need nor can apply them. main keeps migrations and applies them on the real deploy. Fail-safe: only strips on a confirmed non-main CI branch.
1 parent 8109db1 commit 02518dd

3 files changed

Lines changed: 33 additions & 16 deletions

File tree

apps/cloud/scripts/build.mjs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { spawnSync } from "node:child_process";
1111
import { randomBytes } from "node:crypto";
12-
import { rmSync } from "node:fs";
12+
import { readFileSync, rmSync, writeFileSync } from "node:fs";
1313

1414
if (!process.env.VITE_PUBLIC_ANALYTICS_PATH) {
1515
process.env.VITE_PUBLIC_ANALYTICS_PATH = randomBytes(4).toString("hex");
@@ -32,3 +32,26 @@ for (const step of steps) {
3232
process.exit(result.status ?? 1);
3333
}
3434
}
35+
36+
// Preview (non-production) Cloudflare Workers Builds deploy via `wrangler versions
37+
// upload`, which REJECTS any config containing an unapplied Durable Object
38+
// migration (error 10211 — "migrations must be applied via a non-versioned
39+
// deployment"). Preview versions share production's already-applied DO state, so
40+
// they neither need nor can apply migrations. Strip `migrations` from the
41+
// generated deploy config on non-`main` CI branches so PR preview builds stop
42+
// failing. Production (`main`) keeps migrations and applies them on the real,
43+
// non-versioned deploy. Fail-safe: only triggers on a confirmed non-`main` CI
44+
// branch, so it can never drop migrations from a production deploy.
45+
const ciBranch = process.env.WORKERS_CI_BRANCH;
46+
if (process.env.WORKERS_CI === "1" && ciBranch && ciBranch !== "main") {
47+
const cfgUrl = new URL("../dist/server/wrangler.json", import.meta.url);
48+
const cfg = JSON.parse(readFileSync(cfgUrl, "utf8"));
49+
if (Array.isArray(cfg.migrations) && cfg.migrations.length > 0) {
50+
delete cfg.migrations;
51+
writeFileSync(cfgUrl, JSON.stringify(cfg));
52+
console.log(
53+
`[build] preview branch '${ciBranch}': stripped Durable Object migrations from ` +
54+
`dist/server/wrangler.json (versions upload cannot apply migrations)`,
55+
);
56+
}
57+
}

apps/cloud/src/server.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { DurableObject } from "cloudflare:workers";
21
import { SpanKind, SpanStatusCode, context, trace } from "@opentelemetry/api";
32
import {
43
ATTR_HTTP_REQUEST_METHOD,
@@ -47,14 +46,6 @@ export const McpSessionDOSqlite = Sentry.instrumentDurableObjectWithSentry(
4746
McpSessionDOBase,
4847
);
4948

50-
// Orphaned placeholder for the original key-value `McpSessionDO` class (migration
51-
// v1). The live MCP session DO is now `McpSessionDOSqlite` (SQLite); the
52-
// `MCP_SESSION` binding moved to it. Cloudflare won't delete `McpSessionDO` in the
53-
// same deploy that moves its binding, so the class is left unbound and is kept
54-
// exported here only to satisfy the migration. It can be removed in a later deploy
55-
// (with a `deleted_classes: ["McpSessionDO"]` migration) now that nothing binds it.
56-
export class McpSessionDO extends DurableObject {}
57-
5849
// ---------------------------------------------------------------------------
5950
// Worker fetch handler
6051
//

apps/cloud/wrangler.jsonc

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@
2727
// The MCP session DO moved to the Cloudflare Agents (`McpAgent`) base, which
2828
// stores state in SQLite. The original `McpSessionDO` was created on the
2929
// key-value backend (`new_classes`) and cannot be converted in place. Cloudflare
30-
// also refuses to delete a class in the same deploy that moves its binding (it
31-
// validates the delete against the live binding), so v2 only CREATES the new
32-
// SQLite class `McpSessionDOSqlite` and the `MCP_SESSION` binding moves to it.
33-
// The old KV `McpSessionDO` is left orphaned (unbound, kept as a stub export in
34-
// server.ts so the migration stays valid); it can be deleted in a later deploy
35-
// now that nothing binds it. Session state is ephemeral, so nothing is lost.
30+
// also refuses to delete a class in the same deploy that moves its binding, so
31+
// the move happened over two deploys: v2 created the new SQLite class
32+
// `McpSessionDOSqlite` and moved the `MCP_SESSION` binding to it; now that
33+
// nothing binds the old KV `McpSessionDO`, v3 deletes it. Session state is
34+
// ephemeral, so nothing is lost.
3635
"migrations": [
3736
{
3837
"tag": "v1",
@@ -42,6 +41,10 @@
4241
"tag": "v2",
4342
"new_sqlite_classes": ["McpSessionDOSqlite"],
4443
},
44+
{
45+
"tag": "v3",
46+
"deleted_classes": ["McpSessionDO"],
47+
},
4548
],
4649
"services": [
4750
{

0 commit comments

Comments
 (0)