Error Patterns
When something goes wrong during development or deployment, this page covers how to identify the failure, fix it, and verify the fix. Every pattern here is specific to the crmbuilder-dev stack: Next.js, Neon PostgreSQL, and Vercel.
TypeScript build failures
Symptom
Vercel build fails immediately. Logs show:
Type error: ...
Failed to compile.
Error: Command "next build" exited with 1
Fix process
- Run
check_typesvia MCP before every deploy — catch this locally - Read the exact error line and file from the build log
- Fix the type error, run
check_typesagain until clean - Never suppress with
@ts-ignoreunless the type is genuinely wrong (third-party library mismatch) — document why if you do
Common causes
- Missing
awaiton an async DB call returningPromise<T>instead ofT - A new DB column added to the query but not to the TypeScript interface
session.useraccessed without a null check aftergetServerSession
Database column does not exist
Symptom
error: column "email" does not exist
error: column "id_customer" does not exist
Fix process
- Run
explain_tablevia MCP for the table in question - Check the actual column name — do not assume
- Common column name gotchas:
pb_usersauth identifier islogin, notemailcampaigns_itemscontact FK iscustomer_id, notid_customersmslogmessage content istext(BLOB), requiresCONVERT(text, CHAR)for string operations
- Update the query to use the correct column name
Query returns ghost records
Symptom
Deleted records appear in the UI. A record the user deleted keeps showing up.
Cause
Missing AND deleted_at IS NULL on a SELECT query.
Fix
-- Add to every SELECT on a soft-delete table
WHERE id_company = :id_company
AND deleted_at IS NULL -- ← this line
Also check JOINs — both sides need the clause:
JOIN deals d ON d.id_contact = c.id
AND d.deleted_at IS NULL -- ← easy to miss on the JOIN side
Query returns data from wrong tenant
Symptom
A user sees records belonging to a different account. Or a query returns more rows than expected.
Cause
Missing WHERE id_company = :id_company — or id_company was taken from user input instead of the session.
Fix
// Always pull id_company from the verified session JWT
const session = await getServerSession(authOptions);
const { id_company } = session.user; // ← only valid source
Never trust req.body.id_company, req.query.id_company, or any URL parameter for tenant scoping.
pb_settings query fails with "column not found"
Symptom
error: column "id_company" does not exist
on a query against pb_settings.
Cause
pb_settings is a global table with no id_company column. It must not be tenant-scoped.
Fix
-- Wrong
SELECT * FROM pb_settings WHERE id_company = :id_company;
-- Correct
SELECT * FROM pb_settings;
Neon connection error on cold start
Symptom
Error: Connection terminated unexpectedly
NeonDbError: connection timeout
Typically on the first request after a period of inactivity.
Cause
Neon serverless instances pause after inactivity. The first connection after a pause takes longer than a standard timeout.
Fix
This is handled by the Neon serverless driver's connection pooling. If using raw pg, switch to @neondatabase/serverless:
import { neon } from '@neondatabase/serverless';
const db = neon(process.env.DATABASE_URL!);
Do not increase the Vercel function timeout as a workaround — fix the driver instead.
API route returns 401 unexpectedly
Symptom
A valid logged-in user gets a 401 from an API route they should have access to.
Common causes and fixes
1. Session not checked correctly
// Wrong — getServerSession called without authOptions
const session = await getServerSession();
// Correct
const session = await getServerSession(authOptions);
2. Auth cookie not sent from client
Ensure fetch calls include credentials:
fetch('/api/your-route', {
credentials: 'include', // ← required for cookie-based auth
method: 'POST',
...
})
3. Admin-only route hit by a non-admin user
Check session.user.role === 'admin' before returning 401 — make sure the error message distinguishes between unauthenticated (401) and unauthorized (403) so debugging is easier.
Migration runs but table already exists
Symptom
error: relation "your_table" already exists
Fix
Always write migrations with CREATE TABLE IF NOT EXISTS:
-- Wrong
CREATE TABLE lead_scores (...);
-- Correct
CREATE TABLE IF NOT EXISTS lead_scores (...);
Same for indexes:
CREATE INDEX IF NOT EXISTS idx_lead_scores_company
ON lead_scores(id_company);
This makes every migration idempotent — safe to run multiple times.
Vercel deployment succeeds but changes don't appear
Symptom
Deploy shows as successful but the UI still shows old behavior.
Causes and fixes
1. Wrong branch deployed Check the Vercel dashboard — confirm the correct branch is linked to the production or preview environment.
2. Migration not applied
A new table or column was added in code but the migration file was never run against the Neon DB. Connect to Neon and run the migration SQL manually, or add auto-migration on boot (the pattern used in src/lib/init-docs.ts).
3. Environment variable missing
A new process.env.VAR was added to code but not added to Vercel environment variables. Check:
- Vercel Dashboard → Project → Settings → Environment Variables
- Variable is set for the correct environment (Production, Preview, Development)
- Redeploy after adding — env var changes require a new deploy
Rollback procedure
If a deploy introduces a critical bug:
1. Revert via Vercel (fastest) Vercel Dashboard → Deployments → find the last good deploy → "Redeploy". This takes ~60 seconds.
2. Revert a bad migration If a DB migration caused the problem, undo it manually:
-- Drop a bad table
DROP TABLE IF EXISTS bad_table;
-- Undo a bad column add
ALTER TABLE contacts DROP COLUMN IF EXISTS bad_column;
-- Restore a soft delete accidentally hard-deleted
UPDATE contacts SET deleted_at = NULL WHERE id = :id;
Always write the rollback SQL before applying a migration — think through the undo before the do.