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

  1. Run check_types via MCP before every deploy — catch this locally
  2. Read the exact error line and file from the build log
  3. Fix the type error, run check_types again until clean
  4. Never suppress with @ts-ignore unless the type is genuinely wrong (third-party library mismatch) — document why if you do

Common causes

  • Missing await on an async DB call returning Promise<T> instead of T
  • A new DB column added to the query but not to the TypeScript interface
  • session.user accessed without a null check after getServerSession

Database column does not exist

Symptom

error: column "email" does not exist
error: column "id_customer" does not exist

Fix process

  1. Run explain_table via MCP for the table in question
  2. Check the actual column name — do not assume
  3. Common column name gotchas:
    • pb_users auth identifier is login, not email
    • campaigns_items contact FK is customer_id, not id_customer
    • smslog message content is text (BLOB), requires CONVERT(text, CHAR) for string operations
  4. 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.