Common Issues

Startup, health, licensing, and migration failure modes, framed as symptom → cause → resolution. Simcha DB Studio is two processes: a Next.js frontend (port 3000) that proxies every browser call to an Express backend (port 5002), backed by an internal PostgreSQL metadata database named changeops. Most "the app is broken" reports resolve to one of a handful of product-specific states.

Startup Is Gated on changeops DB Readiness

Symptom: GET /api/health returns { status: "STARTING" } and stays there; the UI renders but every data panel is empty; login fails.

Cause: The backend does not consider itself ready until the changeops pool is reachable and migrations have run. Startup retries the connection 10 times with a 3s delay (~30s window). If the DB is unreachable for the whole window, the server stays in STARTING and auto-migrations never complete, so schema-dependent routes (auth, connections, scripts) fail.

Resolution: Read the backend stdout for the connection error emitted on each retry, then verify the target the app is actually using — not the one you assume:

# The metadata database target the process resolved (only DB_DEVOPS_DB_* is read)
docker exec simcha-db-studio env | grep -E "DB_DEVOPS_DB_"

# Prove reachability from the app's network namespace, not your host
docker exec simcha-db-studio node -e "const{Pool}=require('pg');new Pool({host:process.env.DB_DEVOPS_DB_HOST,port:process.env.DB_DEVOPS_DB_PORT,database:process.env.DB_DEVOPS_DB_NAME,user:process.env.DB_DEVOPS_DB_USER,password:process.env.DB_DEVOPS_DB_PASSWORD}).query('select 1').then(()=>console.log('OK')).catch(e=>console.log('FAIL',e.message))"

A classic footgun: the app pointing at the wrong Postgres (e.g. :5432 instead of a sidecar on a non-default port), or DB_DEVOPS_DB_PASSWORD not matching the DB role. An unset or blank value is fatal at startup in every environment; the process exits with status 78 and lists each missing variable.

Reading the Health Signals

Two endpoints are the authoritative liveness/readiness signals and require no auth:

curl -s http://localhost:5002/api/health | python3 -m json.tool
curl -s http://localhost:5002/api/health/migrations | python3 -m json.tool

If /api/health is healthy but a specific table is missing, compare /api/health/migrations against the migration set — a partially applied migration (non-idempotent edit, or a manual DB change that collided with an IF NOT EXISTS guard) is the usual cause.

Migrations Did Not Apply

Symptom: relation "users" does not exist, relation "scripts" does not exist, or a column added by migration 010/011 (target_connection_id, environment) is missing.

Cause: Auto-migration on startup did not complete — the DB role lacks DDL privileges, the database did not exist when the pool first connected, or startup never left STARTING.

Resolution: Confirm the role can DDL (CREATE on the schema), ensure the changeops database exists before the app boots, then restart to re-run migrations. Migration files live at server/database/migrations/NNN_*.sql and are written idempotently (IF NOT EXISTS / ADD COLUMN IF NOT EXISTS); re-running is safe. Verify with /api/health/migrations afterward.

A Backend Route Returns 404 Through the Browser but Works via curl

Symptom: A feature 404s in the UI, yet curl http://localhost:5002/api/<route> against Express succeeds.

Cause: The browser never calls Express directly. The path is browser → pages/api/*.ts (Next.js) → http://localhost:5002/api/* (Express). Every backend route needs a matching Next.js proxy under pages/api/. A missing or misnamed proxy 404s at the Next.js layer before Express is ever reached.

Resolution: Confirm a proxy file exists for the route and that it forwards correctly. The shared helper forwardToBackend() (lib/apiProxy.ts) relays Authorization, X-Request-Id, and X-QA-Bypass-Key. The proxy forwards to BACKEND_URL, which must be set; if it targets the wrong host, correct it and restart the web server. Distinguish the layers by watching both process logs and matching the X-Request-Id across them.

Schema Ops Errors

Notification Errors

Schema ERD Errors

Seed Data Errors

Compliance Rules Errors

Environments Errors

Metrics Errors

AI Agent Errors

DB DevOps Page Load Errors

Users and Sign-in Errors

Migration Scripts Errors

Approvals Errors

Executions Errors

Releases Errors

Audit Log Errors

Roles Errors

License Activation Fails or Reverts

Symptom: "Invalid key format" or "Failed to validate license key" on activation; or a license change does not reach every app container.

Cause and resolution:

A User Cannot Be Added or Cannot Sign In: License Seats

Symptom: creating, inviting or reactivating a user answers 409 NO_FREE_SEAT (Every seat on the license is taken); or a user's sign-in answers 403 SEAT_REQUIRED (This account has no seat) and their open sessions end with SESSION_ENDED.

Cause and resolution: every active user holds one of the license's seats; Settings shows how many are in use. Deactivate users who no longer need access, or apply a license with more seats. SEAT_REQUIRED appears only after a license with fewer seats than active users is applied: administrators keep their seats, and the users given a seat most recently are the ones refused until enough users are deactivated.

Secrets Are Being Stored in Plaintext

Symptom: Some stored connection passwords or MFA secrets are readable as cleartext in the metadata database.

Cause: The rows were written by an older release, before encryption became mandatory. Current releases refuse to start without ENCRYPTION_KEY and encrypt every new write; reading tolerates old plaintext values so existing connections keep working. AI provider keys and extra header values stored in the clear by an older release are encrypted when the server starts.

Detection: Encrypted values carry the wire-format prefix enc: (followed by base64 of salt‖iv‖tag‖ciphertext, AES-256-GCM, PBKDF2-SHA512 at 100k iterations). Any stored password/secret without the enc: prefix was written in the clear:

psql -U <user> -d changeops -c "select id, left(password,4) from connections where password not like 'enc:%';"

Resolution: Set a high-entropy ENCRYPTION_KEY before first write and never run production without it. Rotating in an existing deployment requires re-encrypting rows already written under the old key (or in the clear); changing the key does not retroactively re-wrap existing ciphertext.

Port Binding on Startup

Symptom: EADDRINUSE on :3000 or :5002; or Electron dev hangs waiting on the frontend.

Note the Electron dev inconsistency: electron:dev waits on port 3001 while next dev binds 3000. Either start Next.js on 3001 explicitly or point the wait-on URL at 3000 — otherwise Electron never launches. Reassign the backend with PORT and keep BACKEND_URL pointing at it. Running Electron against the dev server requires DEV_SERVER_HOST and DEV_SERVER_PORT; it refuses to start without them.