Fix broken drizzle migration chain and add prevention guidelines

The migration system was broken due to three issues:
1. Snapshot 0067 had an invalid `autoincrement` field (PostgreSQL doesn't
   support this) causing Zod validation to fail with "data is malformed"
2. Migrations 0068 and 0069 were missing snapshot files, breaking the
   snapshot chain required by `drizzle-kit generate`
3. Orphaned file 0048_mean_enchantress.sql existed outside the journal

Fixed by removing the invalid field, regenerating migrations 0068-0069
with proper snapshots via `drizzle-kit generate`, deleting the orphaned
file, and adding a no-op verification migration (0070). Made migration
0069 idempotent with IF EXISTS/IF NOT EXISTS guards for production safety.

Updated CLAUDE.md with rules to prevent manual migration/journal editing
and ensure snapshots are always generated.

https://claude.ai/code/session_01JuVHpRPa974MKSHoXNGkgU
This commit is contained in:
Claude 2026-04-06 14:29:29 +00:00
parent a395f10064
commit 651f7d1141
No known key found for this signature in database
10 changed files with 13446 additions and 41 deletions

View file

@ -205,9 +205,20 @@ Do not consider a feature complete until tests are written and passing.
1. Update `database/schema.ts` with new tables/columns
2. Run `npm run db:generate` to create migration
3. Run `npm run db:migrate` to apply migration
4. Add model functions in `app/models/` for new queries
5. Update TypeScript types if needed
3. **Verify** the generated migration SQL in `drizzle/` is correct
4. **Verify** a corresponding snapshot was created in `drizzle/meta/` (e.g., `0070_snapshot.json`)
5. Run `npm run db:migrate` to apply migration
6. Add model functions in `app/models/` for new queries
7. Update TypeScript types if needed
**Drizzle Migration Rules (CRITICAL):**
- **NEVER** manually create migration SQL files or journal entries. Always use `drizzle-kit generate` or `drizzle-kit generate --custom` so that snapshot files are created automatically.
- **NEVER** edit `drizzle/meta/_journal.json` by hand. The journal and snapshots must stay in sync — drizzle-kit manages both.
- Every migration **must** have a matching snapshot in `drizzle/meta/`. Missing snapshots cause `drizzle-kit generate` to fail with "data is malformed" errors, which blocks all future migration generation.
- Snapshot JSON must only contain PostgreSQL-valid fields. Do not add `"autoincrement"` to column definitions — PostgreSQL uses sequences/serial instead, and invalid fields cause Zod validation failures.
- If `drizzle-kit generate` prompts for column renames interactively, use `drizzle-kit generate --custom` instead and write the migration SQL by hand (include data backfill logic as needed).
- After generating, run `drizzle-kit generate` again to confirm "No schema changes" — this validates the snapshot chain is healthy.
- For destructive migrations (dropping columns, renaming), make the SQL idempotent with `IF EXISTS`/`IF NOT EXISTS` guards where possible.
### Adding Socket.IO Events

View file

@ -1,19 +0,0 @@
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'ncaam_bracket' AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'simulator_type')) THEN
ALTER TYPE "public"."simulator_type" ADD VALUE 'ncaam_bracket';
END IF;
END $$;
--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'ncaaw_bracket' AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'simulator_type')) THEN
ALTER TYPE "public"."simulator_type" ADD VALUE 'ncaaw_bracket';
END IF;
END $$;
--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'nba_bracket' AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'simulator_type')) THEN
ALTER TYPE "public"."simulator_type" ADD VALUE 'nba_bracket';
END IF;
END $$;
--> statement-breakpoint
ALTER TABLE "leagues" ADD COLUMN IF NOT EXISTS "discord_webhook_url" varchar(500);

View file

@ -1,19 +1,18 @@
-- Step 1: Add new columns as nullable
ALTER TABLE "sports_seasons" ADD COLUMN "draft_on" date;--> statement-breakpoint
ALTER TABLE "sports_seasons" ADD COLUMN "draft_off" date;--> statement-breakpoint
-- Step 1: Add new columns as nullable (IF NOT EXISTS for idempotency)
ALTER TABLE "sports_seasons" ADD COLUMN IF NOT EXISTS "draft_on" date;--> statement-breakpoint
ALTER TABLE "sports_seasons" ADD COLUMN IF NOT EXISTS "draft_off" date;--> statement-breakpoint
-- Step 2: Backfill data based on existing is_draftable flag
UPDATE "sports_seasons"
SET "draft_on" = '2026-04-06', "draft_off" = '2099-12-31'
WHERE "is_draftable" = true;--> statement-breakpoint
UPDATE "sports_seasons"
SET "draft_on" = '2020-01-01', "draft_off" = '2020-12-31'
WHERE "is_draftable" = false;--> statement-breakpoint
-- Step 2: Backfill data based on existing is_draftable flag (only if column still exists)
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'sports_seasons' AND column_name = 'is_draftable') THEN
UPDATE "sports_seasons" SET "draft_on" = '2026-04-06', "draft_off" = '2099-12-31' WHERE "is_draftable" = true;
UPDATE "sports_seasons" SET "draft_on" = '2020-01-01', "draft_off" = '2020-12-31' WHERE "is_draftable" = false;
END IF;
END $$;--> statement-breakpoint
-- Step 3: Apply NOT NULL constraints
ALTER TABLE "sports_seasons" ALTER COLUMN "draft_on" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "sports_seasons" ALTER COLUMN "draft_off" SET NOT NULL;--> statement-breakpoint
-- Step 4: Drop old column
ALTER TABLE "sports_seasons" DROP COLUMN "is_draftable";
-- Step 4: Drop old column (IF EXISTS for idempotency)
ALTER TABLE "sports_seasons" DROP COLUMN IF EXISTS "is_draftable";

View file

@ -0,0 +1,3 @@
-- Verification migration: confirms drizzle migration chain is healthy.
-- This is a no-op migration that validates the migration system works end-to-end.
SELECT 1;

View file

@ -1159,8 +1159,7 @@
"name": "world_ranking",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
"notNull": false
}
},
"indexes": {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -481,16 +481,23 @@
{
"idx": 68,
"version": "7",
"when": 1775000000000,
"tag": "0068_cs2_major_simulator",
"when": 1775485575205,
"tag": "0068_soft_silk_fever",
"breakpoints": true
},
{
"idx": 69,
"version": "7",
"when": 1744012800000,
"when": 1775485599229,
"tag": "0069_draft_window",
"breakpoints": true
},
{
"idx": 70,
"version": "7",
"when": 1775485630297,
"tag": "0070_verify_migrations",
"breakpoints": true
}
]
}