Fix web Progress + Board zeros + iOS sessionId decode (#279)#293
Conversation
Web Progress and Board rendered as zeros / empty because four migrations had never been applied to prod Neon. Routes that referenced session_type were 500ing and clients fell back to defaults. POST /api/sessions was also 500ing, which is the most likely root cause of the original "iOS saving failing" symptom in the issue. This commit lands: - scripts/apply-migrations.ts — applies every drizzle/*.sql idempotently against POSTGRES_URL, wired into package.json prebuild so future Vercel deploys (prod + preview) can't ship code that drifts from schema. Escape hatch via SKIP_DB_MIGRATIONS=1. - iOS sessionId: GET /api/sessions/[dayNumber] thoughts projection now returns sessionId, matching POST /api/thoughts/batch and the ThoughtDTO contract. - vitest regression covering the five-field projection so iOS decode can't silently regress. Migrations applied to prod main (noisy-cell-87641627) out-of-band as part of this fix: - sessions_session_type_incremental.sql - device_tokens_incremental.sql - partner_scheduling_google_calendar_incremental.sql - users_uniqueness_incremental.sql Verified on a Neon ephemeral branch first; 12 public users + 85 standard sessions present post-apply. Closes #279 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
CodeAnt AI is reviewing your PR. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a migration runner and npm script that apply idempotent Changes
Sequence Diagram(s)sequenceDiagram
participant Vercel as "Vercel / Build"
participant FS as "Filesystem (drizzle/*.sql)"
participant Runner as "scripts/apply-migrations.ts"
participant NeonDB as "Neon Postgres Pool (ws)"
Vercel->>Runner: run `npm run db:migrate`
activate Runner
Runner->>Runner: load env, check `SKIP_DB_MIGRATIONS` and `POSTGRES_URL`
Runner->>FS: discover & lexicographically sort `drizzle/*.sql`
Runner->>NeonDB: connect Pool (ws)
activate NeonDB
Runner->>NeonDB: acquire advisory lock
loop for each migration file
Runner->>NeonDB: compute checksum, check `schema_migrations`
alt checksum matches
NeonDB-->>Runner: skip
else if checksum differs
NeonDB-->>Runner: FAIL (throw)
else
Runner->>NeonDB: execute SQL, upsert `schema_migrations`
NeonDB-->>Runner: applied
end
end
Runner->>NeonDB: release advisory lock, close pool
deactivate NeonDB
Runner-->>Vercel: exit (0 or non‑zero)
deactivate Runner
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📋 Issue PlannerBuilt with CodeRabbit's Coding Plans for faster development and fewer bugs. View plan used: ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@package.json`:
- Around line 7-11: The current prebuild hook runs "db:migrate" on every npm run
build which fails when POSTGRES_URL is absent; remove or change the "prebuild"
entry in package.json so "build" no longer automatically runs "db:migrate" and
instead move migration invocation to a deploy-only script (e.g., create a new
"deploy:migrate" or "deploy:build" script that runs "db:migrate" then "build")
or wrap the migration step in a guard that checks for a deploy flag or required
env (POSTGRES_URL/CI) before calling "tsx scripts/apply-migrations.ts"; update
references to "prebuild", "db:migrate", "build", and
"scripts/apply-migrations.ts" accordingly so local/dev builds do not depend on
live DB credentials.
In `@scripts/apply-migrations.ts`:
- Around line 31-60: The current runner reads MIGRATIONS_DIR and blindly
executes every .sql file in the for (const file of files) loop; change it to
maintain and consult a migration journal table in the DB (e.g.,
migrations_journal storing filename and checksum/timestamp) so each file is
executed only once: on startup, ensure the journal table exists (using
pool.query), compute a checksum for each file read from fs.readFileSync, skip
files whose filename/checksum exist in migrations_journal, and after
successfully running pool.query(body) insert the filename/checksum and
applied_at into the journal instead of replaying every file; update the error
handling around the await pool.query(body) to not re-run already-applied
migrations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 53a40a82-5c5f-4ec9-87ea-8aee958d235d
📒 Files selected for processing (4)
package.jsonscripts/apply-migrations.tssrc/app/api/sessions/[dayNumber]/route.test.tssrc/app/api/sessions/[dayNumber]/route.ts
|
CodeAnt AI finished reviewing your PR. |
…ournal Addresses CodeRabbit findings on PR #293: 1. prebuild was running db:migrate on every `npm run build`, which broke the Web Build CI job (no POSTGRES_URL in that env). Moved to vercel.json buildCommand instead so migrations run only on actual Vercel deploys (prod + preview). CI build verification no longer depends on a live DB. 2. Migration runner now maintains a `schema_migrations` journal (filename + sha256 checksum) so each .sql file is applied exactly once. First-run backfills the journal by re-applying the existing idempotent files, after which only new migrations execute. Edited files are detected (checksum mismatch) and fail loudly so the human can decide. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses BugBot findings on PR #293: 1. friend_graph_incremental.sql had four bare ALTER TABLE ADD CONSTRAINT statements without IF NOT EXISTS guards. With the new journal, first-run on prod (empty journal) tries to re-apply and hits "constraint already exists". Wrapped each in DO $$ pg_constraint $$ guards matching the convention used by every other migration. 2. Renamed buddy_sessions_daily_room_incremental.sql -> buddy_sessions_v2_daily_room_incremental.sql so alphabetical sort respects the dependency: buddy_sessions_incremental.sql (creates buddy_sessions) now sorts before the daily_room file (alters it). Safe rename because the file is idempotent (ADD COLUMN IF NOT EXISTS); journal records the new filename on next deploy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/apply-migrations.ts`:
- Around line 45-102: Wrap each migration run in a DB transaction and use a
DB-level advisory lock so check/apply/journal are atomic and serialized: inside
the files loop, start a transaction (BEGIN), acquire an advisory lock derived
from the migration filename (pg_advisory_lock with a hash of file or similar),
re-check schema_migrations for that filename (instead of relying on the earlier
in-memory applied Map), if still not applied execute pool.query(body) and then
INSERT INTO schema_migrations in the same transaction, then COMMIT; on error
ROLLBACK and release the lock. Update the logic around pool.query, the per-file
try/catch and the inserted journal statement so the SQL execution and journal
insert occur inside the same transaction while using the advisory lock to
prevent concurrent runners from running the same migration.
- Around line 18-22: Change the current behavior when POSTGRES_URL is missing to
fail fast unless SKIP_DB_MIGRATIONS is explicitly set to "1": instead of simply
warning and returning when const url = process.env.POSTGRES_URL is falsy, check
process.env.SKIP_DB_MIGRATIONS; if it equals "1" keep the existing warning/early
return, otherwise throw an error or call process.exit(1) with a clear message
that POSTGRES_URL is required (so the process fails during build/deploy).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c164bc6d-7eb3-4c9c-b77b-578616d792f9
📒 Files selected for processing (3)
package.jsonscripts/apply-migrations.tsvercel.json
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
There was a problem hiding this comment.
🧹 Nitpick comments (1)
drizzle/friend_graph_incremental.sql (1)
26-36: Scopepg_constraintexistence checks to the target tableOn lines 26, 29, 32, and 35, checking only
connamecan produce false positives if that constraint name exists on a different table, which may skip FK creation here. In PostgreSQL, constraint names are unique per table, not globally.Proposed SQL hardening
- IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'friend_requests_from_user_id_users_id_fk') THEN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'friend_requests_from_user_id_users_id_fk' + AND conrelid = 'public.friend_requests'::regclass + ) THEN ALTER TABLE "friend_requests" ADD CONSTRAINT "friend_requests_from_user_id_users_id_fk" FOREIGN KEY ("from_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; END IF; - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'friend_requests_to_user_id_users_id_fk') THEN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'friend_requests_to_user_id_users_id_fk' + AND conrelid = 'public.friend_requests'::regclass + ) THEN ALTER TABLE "friend_requests" ADD CONSTRAINT "friend_requests_to_user_id_users_id_fk" FOREIGN KEY ("to_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; END IF; - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'friendships_user1_id_users_id_fk') THEN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'friendships_user1_id_users_id_fk' + AND conrelid = 'public.friendships'::regclass + ) THEN ALTER TABLE "friendships" ADD CONSTRAINT "friendships_user1_id_users_id_fk" FOREIGN KEY ("user1_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; END IF; - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'friendships_user2_id_users_id_fk') THEN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'friendships_user2_id_users_id_fk' + AND conrelid = 'public.friendships'::regclass + ) THEN ALTER TABLE "friendships" ADD CONSTRAINT "friendships_user2_id_users_id_fk" FOREIGN KEY ("user2_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; END IF;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@drizzle/friend_graph_incremental.sql` around lines 26 - 36, The IF NOT EXISTS checks use only conname and can miss constraints with the same name on other tables; update each check to ensure the constraint is on the target table and schema by qualifying conrelid (or joining pg_class/pg_namespace) — for example, use a condition like pg_constraint.conname = 'friend_requests_from_user_id_users_id_fk' AND pg_constraint.conrelid = 'public.friend_requests'::regclass (and similarly for 'friend_requests_to_user_id_users_id_fk' on public.friend_requests and 'friendships_user1_id_users_id_fk' and 'friendships_user2_id_users_id_fk' on public.friendships) so the ALTER TABLE runs only if that named constraint is absent on the specific table.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@drizzle/friend_graph_incremental.sql`:
- Around line 26-36: The IF NOT EXISTS checks use only conname and can miss
constraints with the same name on other tables; update each check to ensure the
constraint is on the target table and schema by qualifying conrelid (or joining
pg_class/pg_namespace) — for example, use a condition like pg_constraint.conname
= 'friend_requests_from_user_id_users_id_fk' AND pg_constraint.conrelid =
'public.friend_requests'::regclass (and similarly for
'friend_requests_to_user_id_users_id_fk' on public.friend_requests and
'friendships_user1_id_users_id_fk' and 'friendships_user2_id_users_id_fk' on
public.friendships) so the ALTER TABLE runs only if that named constraint is
absent on the specific table.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 141401d2-0eef-435c-9cd7-4e16e8cddc45
📒 Files selected for processing (2)
drizzle/buddy_sessions_v2_daily_room_incremental.sqldrizzle/friend_graph_incremental.sql
Constraint names are unique per-table in PostgreSQL, not globally. Add conrelid = 'public.<table>'::regclass to each existence check so a same-named constraint on a different table can't cause a false skip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@drizzle/friend_graph_incremental.sql`:
- Around line 24-38: The current IF NOT EXISTS + ALTER TABLE pattern is
vulnerable to race conditions during concurrent migrations; update each ALTER
TABLE that adds the foreign-key constraints
(friend_requests_from_user_id_users_id_fk,
friend_requests_to_user_id_users_id_fk, friendships_user1_id_users_id_fk,
friendships_user2_id_users_id_fk) to be wrapped in an atomic PL/pgSQL try/catch
so duplicate_object errors are swallowed: for each constraint, replace the plain
ALTER TABLE statement with a DO $$ BEGIN ALTER TABLE ... ADD CONSTRAINT ...;
EXCEPTION WHEN duplicate_object THEN NULL; END $$; so the second concurrent
runner won't fail if the constraint was just added by another process.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5e6c1b5f-c900-4e6e-b42d-cd8efab9eb4e
📒 Files selected for processing (1)
drizzle/friend_graph_incremental.sql
Both Graphite and CR flagged race conditions on concurrent migration runs. Adding a session-level Postgres advisory lock at the start of the runner eliminates the race entirely — only one runner can hold the lock at a time across processes (overlapping Vercel deploys, dev + CI hitting the same DB). Lock auto-releases when the session ends; we also unlock explicitly in finally. This subsumes per-statement EXCEPTION duplicate_object handlers in friend_graph since the lock guarantees no two runners can race the same DO $$ block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
CodeAnt AI is running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR adds an automatic migration runner to Vercel deploys and fixes the sessions API to include sessionId in thoughts responses for iOS decoding. sequenceDiagram
participant Vercel
participant Build
participant MigrationRunner
participant Database
participant iOSClient
participant API
Vercel->>Build: Start deploy build
Build->>MigrationRunner: Run db migrations before build
MigrationRunner->>Database: Apply pending migrations with lock and journal
MigrationRunner-->>Build: Migrations finished, continue build
iOSClient->>API: Request session by day number
API->>Database: Load session for user and day
API->>Database: Load thoughts for session including sessionId field
Database-->>API: Thoughts with id sessionId dayNumber timeInSession text
API-->>iOSClient: Return session and thoughts payload for the day
Generated by CodeAnt AI |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR adds an automated migration runner to the Vercel build pipeline to keep the database schema in sync and updates the sessions by day API to include sessionId in thought responses for iOS decoding. sequenceDiagram
participant Vercel
participant BuildSystem
participant MigrationRunner
participant Postgres
participant iOSApp
participant API
participant Database
Vercel->>BuildSystem: Trigger build command
BuildSystem->>MigrationRunner: Run db migrate script
MigrationRunner->>Postgres: Apply pending migrations and update journal
MigrationRunner-->>BuildSystem: Migrations complete, continue build
iOSApp->>API: Request sessions by day
API->>Database: Load session and thoughts including sessionId
Database-->>API: Session and thoughts data
API-->>iOSApp: Response with thoughts including sessionId
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR adds an automated database migration runner to Vercel deploys and fixes the sessions API so that thoughts include sessionId for the iOS decoder contract. sequenceDiagram
participant Vercel
participant Backend
participant Database
participant IOS
Note over Vercel,Database: Deploy time migrations
Vercel->>Backend: Run build command
Backend->>Backend: Run db migrate script
Backend->>Database: Acquire lock and ensure migration journal
Backend->>Database: Read applied migrations
Backend->>Database: Apply pending migration files
Note over IOS,Database: Session thoughts fetch
IOS->>Backend: Request session by day number
Backend->>Database: Load session and thoughts including sessionId
Database-->>Backend: Session and thoughts with sessionId
Backend-->>IOS: Response with thoughts id sessionId dayNumber timeInSession text
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR wires a migration runner into the Vercel build so schema migrations are applied before deploys, and updates the sessions API to return thoughts with their sessionId to satisfy the iOS decoder contract. sequenceDiagram
participant Vercel
participant MigrationRunner
participant Database
participant iOSApp
participant SessionsAPI
Vercel->>Vercel: Start deploy build
Vercel->>MigrationRunner: Run db:migrate script
MigrationRunner->>Database: Connect and apply pending drizzle migrations with lock
MigrationRunner-->>Vercel: Report migration success or failure
Vercel->>Vercel: Continue app build only on migration success
iOSApp->>SessionsAPI: GET sessions by day number
SessionsAPI->>Database: Load session and thoughts for user and day
Database-->>SessionsAPI: Session thoughts including sessionId
SessionsAPI-->>iOSApp: 200 OK with thoughts including sessionId
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR wires an automatic database migration runner into the Vercel build pipeline and updates the sessions by day endpoint so iOS receives thoughts that include sessionId, preventing decode failures. sequenceDiagram
participant Vercel build
participant Migration runner
participant Postgres
participant iOS app
participant Sessions API
Vercel build->>Migration runner: Run db migrate during build
Migration runner->>Postgres: Acquire advisory lock and load applied migrations
Migration runner->>Postgres: Apply pending drizzle SQL files
Migration runner-->>Vercel build: Migrations complete and deploy continues
iOS app->>Sessions API: Request session by day number
Sessions API->>Postgres: Look up user session for day
Sessions API->>Postgres: Query thoughts including sessionId
Postgres-->>Sessions API: Thoughts with id sessionId dayNumber timeInSession text
Sessions API-->>iOS app: Session with thoughts including sessionId
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR adds an automated migration runner to the Vercel build pipeline to keep the database schema in sync with code, and fixes the sessions API to include sessionId in the thoughts response required by the iOS client. sequenceDiagram
participant Developer
participant Vercel
participant MigrationRunner
participant Postgres
participant iOSApp
participant API
participant Database
Developer->>Vercel: Push code for deploy
Vercel->>MigrationRunner: Run db:migrate before build
MigrationRunner->>Postgres: Acquire lock and read applied migrations
MigrationRunner->>Postgres: Apply pending drizzle sql files and update journal
MigrationRunner-->>Vercel: Migrations applied or skipped
iOSApp->>API: GET /api/sessions/dayNumber
API->>Database: Load session and related thoughts
Database-->>API: Thoughts including sessionId field
API-->>iOSApp: JSON response with thoughts including sessionId
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. |
User description
Summary
/api/sessionsand/api/boardreferenced columns that didn't exist, 500'd, and clients fell back to defaults — Progress rendered all zeros and Board was empty.POST /api/sessionswas also 500ing, which is the most likely cause of the original "iOS saving failing" symptom.GET /api/sessions/[dayNumber]thoughts projection was missingsessionId, breaking the iOSThoughtDTOdecode.npx drizzle-kit pushwas a manual step that had been skipped on the last 4 schema-changing PRs.Changes
scripts/apply-migrations.ts— applies everydrizzle/*.sqlidempotently againstPOSTGRES_URL. Wired intopackage.jsonprebuild, so every Vercel build (prod + preview) auto-applies migrations before code ships. Escape hatch:SKIP_DB_MIGRATIONS=1.src/app/api/sessions/[dayNumber]/route.ts— addssessionId: thoughts.sessionIdto the thoughts projection (matchesPOST /api/thoughts/batchand the iOS contract).src/app/api/sessions/[dayNumber]/route.test.ts— vitest regression asserting the five-field projection (id, sessionId, dayNumber, timeInSession, text).Migrations applied to prod main (
noisy-cell-87641627) out-of-bandAlready applied as part of this fix; verified on a Neon ephemeral branch first.
sessions_session_type_incremental.sqlsessions.session_type(root cause for #279 web)device_tokens_incremental.sqldevice_tokenstable (iOS push registration)partner_scheduling_google_calendar_incremental.sqlgoogle_oauth_tokens,buddy_session_calendar_events,buddy_sessions.scheduled_start_atusers_uniqueness_incremental.sqlusers_username_lower_unique(#281 effective in prod)Post-apply verification: 12 public users present, 85 standard sessions backfilled to
'standard'.Test plan
scripts/apply-migrations.ts)buildCommand(vercel.json) — runs on every prod + preview deploysessionIdfield added to thoughts projectioncoderabbit review --prompt-onlyclean on first passnpm run buildclean (withSKIP_DB_MIGRATIONS=1for local)npx tsc --noEmitintroduces no new errors (verified by stash-diff against main)Closes #279
🤖 Generated with Claude Code
CodeAnt-AI Description
Fix data fetch failures that caused empty progress, missing board items, and iOS decode errors
What Changed
sessionId, which restores iOS decoding for session detail datasessionIdagainImpact
✅ Fewer empty progress and board screens✅ Fewer iOS session decode failures✅ Safer deploys after schema changes🔄 Retrigger CodeAnt AI Review
Details
💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.