Skip to content

Tell generated apps not to delete the Builder-managed Drizzle scaffold - #396

Closed
armgjoka wants to merge 1 commit into
templatefrom
fix/protect-builder-managed-drizzle-files
Closed

Tell generated apps not to delete the Builder-managed Drizzle scaffold#396
armgjoka wants to merge 1 commit into
templatefrom
fix/protect-builder-managed-drizzle-files

Conversation

@armgjoka

@armgjoka armgjoka commented Sep 8, 2026

Copy link
Copy Markdown

Why

A Fusion project restructured from the single-app starter into an apps/* monorepo. The cleanup commit deleted the root drizzle/ directory and drizzle.config.ts but left the database block in builder.config.json pointing at the old location.

Builder runs a schema-merge resolver on every branch merge for any project with a provisioned database: drizzle-kit generate to reconcile migrations both branches added independently, then a scan of the generated SQL for DROP TABLE / DROP COLUMN / ALTER COLUMN ... TYPE that blocks the merge if it finds any. It locates the schema through database.schemaDir in root builder.config.json.

With the directory gone, that resolver broke and blocked every merge on the project with an unreadable error. That half is fixed in ai-services (packages/dev-tools/cli/resolve-schema-merge.ts) — it now recovers a relocated setup when that is unambiguous, and skips cleanly instead of hard-failing when it cannot.

Which changes the stakes rather than removing them. Deleting the scaffold no longer produces a loud error, it silently disables the destructive-migration gate — a migration that drops a production column can now merge unnoticed. That is what this rule is for.

Nothing in a generated project said those files were load-bearing. The guidance that did exist lived in drizzle/START_HERE.md, inside the directory it was supposed to protect, and was deleted along with it.

What changes

One section added to the generated root AGENTS.md (the file loaded unconditionally each session; CLAUDE.md in a generated project is a one-line pointer to it):

  • names the four Builder-managed paths — builder.config.json, drizzle.config.ts, drizzle/schema.ts, drizzle/migrations/
  • states the silent consequence: the merge still succeeds, the destructive-schema check is skipped
  • requires moving database.schemaDir in the same change when the repo restructures, with the JSON shape
  • notes that schemaDir is a single path, so a monorepo where more than one app has its own drizzle.config.ts gets no check at all — say so rather than silently picking one
  • says to remove the database block if the app genuinely no longer uses a database

The section is folded into the existing ## Source Changes replacement rather than added as a second uniqueReplace. A separate replacement splits that block, making its to non-contiguous, and the patch then fails on re-apply — CI's idempotency check caught that, and there is now a comment recording the constraint.

Also: assertPatched assertions so the section cannot silently drop out of a future sync, plus the overlay README.md / VERIFICATION.md entries.

Verification

Ran the same sequence as verify-starter-patch.yml:

  • apply to a pristine tree of this branch — passes, section present exactly once
  • apply to the currently patched origin/main tree with --source-root — passes, section present exactly once, existing patched text intact
  • re-apply to both, git diff --exit-code and a clean status --porcelain — idempotent

Then the EnableDatabase path against the patched main tree, using scaffoldDrizzleDatabase from ai-services:

ok: true
written: ["scripts/guard-no-drizzle-push.mjs"]
skipped: ["builder.config.json", "drizzle.config.ts", "drizzle/schema.ts",
          "drizzle/migrations/meta/_journal.json", "server/db.ts",
          "scripts/maybe-migrate.mjs"]
packageJsonUpdated: true

The scaffold still writes correctly and skips exactly what the starter already ships, confirming the four paths the rule names are the ones actually present.

Finally, reproduced the incident against resolveDrizzleSetup on that tree: intact it resolves configPath, schemaFilePath, migrationsDir and journalPath; after deleting drizzle/ and drizzle.config.ts with the database block left in place it returns null, which resolveSchemaMerge reports as success with nothing to reconcile. That is the silent skip the section warns about.

Scope

Newly created projects only — Fusion clones the starter at project-create time and there is no retrofit path. Existing projects are covered by the ai-services resolver fix.

A Fusion project restructured from the single-app starter into an apps/*
monorepo and its cleanup commit deleted drizzle/ and drizzle.config.ts while
leaving the database block in builder.config.json pointing at the old
location. Builder's schema-merge resolver locates the schema through
database.schemaDir, so it had nothing to reconcile.

The ai-services resolver now recovers an unambiguously relocated setup and
skips cleanly instead of hard-failing. That turns a blocked merge into a
silently disabled gate: a migration that drops a production column can merge
unnoticed. Nothing in a generated project said the scaffold was load-bearing,
and the guidance that existed lived in drizzle/START_HERE.md, inside the
directory it was meant to protect.

Add a "Builder-managed database files" section to the generated AGENTS.md
naming the four managed paths, the silent consequence of deleting them, and
the requirement to move database.schemaDir in the same change. It also states
that schemaDir is a single path, so a monorepo with more than one app-owned
drizzle.config.ts gets no check at all and should say so rather than pick one.

Reaches newly created projects only; existing projects are covered by the
resolver fix.
@armgjoka armgjoka closed this Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant