Skip to content

Fix agent chat 500, backfill missing cloud migrations, fix offline app schema drift#89

Merged
zinkh merged 5 commits into
mainfrom
claude/settings-save-api-error-0krhoy
Jul 19, 2026
Merged

Fix agent chat 500, backfill missing cloud migrations, fix offline app schema drift#89
zinkh merged 5 commits into
mainfrom
claude/settings-save-api-error-0krhoy

Conversation

@zinkh

@zinkh zinkh commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • Surfaced real API errors: apiFetch/baseFetchJson discarded the server's JSON error body on any failure and threw a bare "Failed to fetch ... 500" — the actual reason (which every server route already returns) never reached the user or console. Now the real message surfaces, with the status code attached.
  • Fixed a real bug in the AI agent context builder: buildAgentContext ordered the projects scope by a created_at column that doesn't exist on that table (only updated_at does), 400'ing silently on every agent chat call since none of the context fetches checked the error before defaulting to an empty list. Added logging to all of them.
  • Backfilled 7 previously-unapplied cloud migrations found via a full audit of every table/column referenced in server.ts against the actual production schema: Chorus Pro (settings/invoices/situations/marches_entreprises), meetings/meeting_photos/meeting_attendees (a whole feature backed by a table that didn't exist), join_requests, documents qualité columns + document_diffusions, tenants Stancer billing columns, ordres_de_service MPRO columns, receptions MPRO columns, and situations Super PDP columns. Re-ran the full audit afterward — comes back clean.
  • Fixed the same class of drift in the Windows offline app, for a different reason: its embedded local Postgres only ever applied migrate_*.sql files on a database's very first launch — any migration added to the repo after a user's first install would never reach their existing local database. Added a _local_applied_migrations bookkeeping table so new migrations get picked up incrementally on every launch, and made a migration-catch-up failure on an existing install non-fatal (never blocks app startup, never deletes user data, retries automatically next launch) instead of the previous behavior of stopping Postgres and refusing to start.
  • Found and fixed a real bug this last change would otherwise have triggered: migrate_add_agents.sql's 13-row system-template seed relied on ON CONFLICT DO NOTHING, but tenant_id IS NULL on every row and Postgres never treats two NULLs as conflicting for a unique constraint (verified empirically against production in a rolled-back transaction) — every existing offline install would have ended up with 26 duplicate template agents. Now guarded with WHERE NOT EXISTS.

Test plan

  • npm run lint (tsc --noEmit) passes
  • Verified via Supabase logs and direct queries that all previously-missing tables/columns now exist in production
  • Verified the migrate_add_agents.sql idempotency fix against production in a rolled-back transaction (count stayed at 13, not 26)
  • Manually retest agent chat once deployed
  • Manually verify the Windows build still bootstraps a fresh install correctly, and that an existing install picks up new migrations on next launch

Generated by Claude Code

claude added 5 commits July 19, 2026 05:57
apiFetch/baseFetchJson discarded the JSON error body on any non-2xx
response and threw a bare "Failed to fetch ... : 500" — the actual
reason (which every server route already returns as { error: "..." })
never reached the user or the console. This has been masking the real
cause of several bugs investigated in this session. Now the thrown
Error carries the server's actual message (falling back to the generic
one if the body isn't JSON), with the status code attached.

Also fixed two real bugs found while chasing a 500 on
POST /api/agents/:id/chat: buildAgentContext ordered the `projects`
scope by a `created_at` column that doesn't exist on that table (only
`updated_at` does), which 400'd on every single call — silently, since
none of the context fetches checked r.error before assigning `r.data ||
[]`. Added error logging to all of them so a broken scope is visible in
server logs instead of just silently degrading to an empty list.

Note: the `meetings` scope query still fails — the `meetings` table
does not exist in the production database at all, despite being a
documented table and having a full CRUD API in server.ts. That's a
separate, larger issue (a whole feature backed by a missing table) that
needs a decision on whether to create the table or retire the feature.
The meetings table never existed in production despite a full CRUD API
in server.ts (/api/meetings*) and a documented Meeting type — every
call 404'd. Columns match exactly what server.ts reads/writes: type
constrained to the three MeetingType values, project_id/proposal_id/
tender_id as nullable TEXT FKs (matching those tables' TEXT ids), plus
meeting_photos and meeting_attendees for the endpoints that join on
them. RLS enabled with the same tenant_isolation policy pattern used
everywhere else in schema.sql.

Applied directly to the production Supabase project; this file commits
it to the migration history for anyone provisioning a new instance.
Two more tables/columns referenced in server.ts were missing from
production, found via a full audit of every .from('...') call against
the actual deployed schema:

- join_requests: existed only in schema.sql/migrate_add_join_requests.sql,
  never applied — broke the whole "request to join an existing agency"
  flow (/api/agency-setup/join, /api/team/join-requests*).
- document_diffusions + 6 documents columns (indice, doc_statut,
  emetteur, approbateur, date_approbation, doc_type) from
  migrate_documents_qualite.sql — also never applied, breaking
  /api/documents/:id/diffusions*. That migration's own source also
  never enabled RLS on the new table (every other table in this schema
  has a tenant_isolation policy) — added it here.

Applied both directly to the production Supabase project.
Investigated whether the Windows desktop build (its own embedded
Postgres, bootstrapped by electron/pgBootstrap.cjs) suffers the same
class of schema drift just fixed on the cloud Supabase project. It
does, structurally: applyLocalSchema() only ever ran on a database's
very first launch (isFirstRun, gated on pgdata/ not existing yet) —
any migrate_*.sql file added to the repo after a user's first launch
would never be applied to their existing local install, since there
was no other trigger for it. New cloud migrations (meetings,
join_requests, etc.) would silently never reach already-installed
Windows users, forever.

- electron/applySchema.cjs: track applied files in a new
  _local_applied_migrations bookkeeping table (same local-only pattern
  as server/localPendingPush.ts) and only apply files not yet
  recorded. Runs the same on a brand new database (applies everything)
  and an existing one (applies just what's new).
- electron/pgBootstrap.cjs: call it on every launch, not just the
  first. Critically, the existing "delete pgdata/ and retry" failure
  path is now scoped to isFirstRun only — a migration failure on an
  existing, populated install must never wipe the user's real data;
  it now just fails to start and leaves the local DB untouched.
- supabase/migrate_add_agents.sql: its 13-row system-template seed
  INSERT relied on ON CONFLICT DO NOTHING against UNIQUE(tenant_id,
  slug), but tenant_id IS NULL on every seed row and Postgres never
  considers two NULLs equal for a unique constraint — confirmed
  empirically against production (a duplicate insert was silently
  accepted, not rejected). Every existing offline install would have
  replayed this file once under the new tracking and ended up with 26
  duplicate template agents. Now guarded with WHERE NOT EXISTS so the
  whole seed is skipped once any system template already exists.
The previous version re-threw (and even stopped Postgres) when
applying pending migrations failed on an already-initialized local
install — that propagates through startOfflineDataStack() ->
startServer() -> createWindow() in electron/main.cjs, which shows a
hard error screen instead of loading the app at all. That's wrong for
an offline-first app: a transient failure (no network, disk hiccup,
whatever) while catching up on new migrations must never block access
to an existing, already-working local database.

Now that failure is swallowed: Postgres keeps running untouched at
whatever schema it already has, the app starts normally, and the next
launch retries automatically (unapplied migrations simply stay
unrecorded in _local_applied_migrations, same as before).
@zinkh
zinkh merged commit 03a3939 into main Jul 19, 2026
1 check passed
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.

2 participants