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:
GET /api/health— overall status plus changeops readiness.STARTINGmeans the DB is not yet reachable (see above); anything other than a healthy status is a hard "do not route traffic here" for a load balancer.GET /api/health/migrations— lists applied migrations. Use it to confirm the schema is at the expected revision (currently 001–011) rather than inferring from arelation ... does not existerror downstream.
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
- The connection could not be opened (502,
CONNECTION_FAILED): Schema Ops could not open the connection. The engine's message follows; test the connection under Connections, and check that the database accepts more connections. - Could not read the schema from this connection (502,
SNAPSHOT_FAILED): the connection could be opened but a catalog query failed. The engine's message follows; the usual causes are a password or network change, or a user without read access to the catalog views. - This connection has no baseline snapshot (
NO_BASELINE): capture a snapshot of the connection (the first one becomes the baseline) or choose Set as Baseline on an existing one. - ... does not match it (
DIALECT_MISMATCH) or ... cannot be compared with ...: the request named another engine than the connection's, or compared schemas from two engines. Leave the dialect out, and compare snapshots from the same engine. - ... is not supported for ... connections (
UNSUPPORTED_DIALECT): the message lists the engines that action supports; the view only offers connections of those engines. - A pre-flight check fails with "read-only transaction": checks cannot change the database. Rewrite the check as a read, for example compare
last_valueof a sequence instead of callingnextval().
Notification Errors
- The notifications could not be loaded: … in the bell's list: the server did not answer; the reason follows. Choose Retry.
- The notification was not updated: …: marking, dismissing or clearing failed, and the list is left as it was.
- Your notification settings were not saved: …: the box goes back to how it was; try again once the reason is fixed.
Schema ERD Errors
- The engines the Schema ERD reads could not be loaded: …: the server did not answer; the reason follows, and the connection picker waits for the engines. Check
GET /api/health, then choose Retry. - The Schema ERD cannot read … connections: the connection's engine is not one the ERD reads; the message names those it does.
Seed Data Errors
- ... is inactive. Turn on Active to plan it (
SEED_INACTIVE): edit the seed and tick Active. - ... applies to ... only, and this connection is ... (
SEED_ENGINE_MISMATCH): the seed is limited to other engines. Plan it against one of those, or change its Engines. - ... has N rows with the key ... (
SEED_DUPLICATE_KEYS): the table holds that key more than once, so an update could not tell the rows apart. Remove the duplicates, add a unique key, or choose key columns that identify one row. - The target table does not exist on this connection (
SEED_TABLE_MISSING): check the seed's schema and table, or create the table first. - Nothing to apply (
SEED_NOTHING_TO_DO): the table already matches the seed, so no script is created. - A sync seed needs at least one row: a Sync seed with no rows would delete every row in the table; add the rows, or use Upsert.
- The engines seeds support could not be loaded: … in Plan or the seed editor, or The seeds could not be loaded: … on the page: the server did not answer; the reason follows. Check
GET /api/health, then choose Retry. - "…" was not deleted: …: the server refused to delete the seed; the reason follows the colon.
Compliance Rules Errors
- the pattern does not compile: Invalid regular expression ... (
INVALID_RULE): patterns are JavaScript regular expressions. Remove inline flags such as(?i)and tick Ignore case instead, and escape a literal(,[or.with a backslash. - … no longer exists. Remove it to save the rule. in the rule editor: the environment was deleted after the rule was saved. Click the struck-through environment to remove it, then save. Through the API the same rule is refused with appliesToEnvironments can only name existing environments (
INVALID_RULE). - The engines could not be loaded: … or The environments could not be loaded: … in the rule editor, or The compliance rules could not be loaded: … on the page: the server did not answer; the reason follows. Check
GET /api/health, then choose Retry. - The compliance rules could not be checked: … on a script: its result could not be read; choose Retry.
- A compliance rule named "..." already exists (
RULE_NAME_TAKEN): names are unique regardless of case. Edit the existing rule, or choose another name. - Not evaluated: the pattern took longer than 250 ms on this script: the pattern backtracks too much, usually from nested repeats such as
(a+)+or(.*)*. Make it more specific; until then the rule fails on that script. - ... is refused because it fails "..." (
COMPLIANCE_FAILED): open the script in Migration Scripts; its Compliance section shows the matching line and how to fix it. Change the script, or ask a Release Manager to change, limit or turn off the rule. - A rule shows as not applicable: it is limited to other engines or environments. A script with no target connection skips rules limited to engines; the Compliance Check skips rules limited to environments when no environment is chosen.
- AI is not configured in the Compliance Check: a rule checked by the AI model applies to the script. Set up a provider under Settings → AI, or limit that rule to the engines and environments it is meant for.
Environments Errors
- An environment named "..." already exists: names are unique regardless of case, so "staging" and "Staging" are the same environment. Open the existing one, or choose another name.
- "..." targets PostgreSQL. A script is deployed to every target in an environment, so it cannot mix database types.: an environment holds one database type. Put connections of another engine in their own environment, for example "Staging MySQL".
- "..." mixes MySQL and PostgreSQL. Split it into one grouping per database type before assigning it to an environment.: open the grouping under Groupings, remove the connections of the other engine (removing is always allowed), and put them in a grouping of their own.
- "..." still has N connections; take them out before deleting it: connections added directly have Remove; one marked through a grouping leaves when you remove that grouping from the environment.
- Cannot delete "..." — it is used by N script(s). Reassign those first.: scripts keep the environment they were written for. Give those scripts another environment in Migration Scripts, then delete.
- Cannot delete "..." — Production runs only after it. Change that first.: open the environment named in the message and change its Runs Only After.
- That would make a loop: ...: Runs Only After cannot point back, directly or through other environments, to the environment you are changing. The message lists the chain.
- "..." is assigned to "...", which targets PostgreSQL. A script is deployed to every target in an environment, so it cannot mix database types.: the grouping belongs to an environment of another database type. Put the connection in a grouping of its own engine, or take the grouping off that environment first.
- "..." is assigned to "Staging" and "..." to "Production". A connection belongs to one environment, so it cannot join both.: choose one of the two groupings, or assign them to the same environment.
- "..." is assigned to "Staging", so a connection in it belongs to "Staging", not "...": a connection in a grouping that is assigned to an environment lives in that environment. Leave the environment as it is, or take the connection out of the grouping first.
- A connection moved to another environment after joining a grouping: that is expected when the grouping is assigned to one environment. Assign Connections asks before moving a connection out of another environment.
- A connection disappeared from an environment after a grouping was removed: that is expected. A grouping's connections join the environment while it is assigned and leave when it is removed, unless another grouping assigned to the environment still covers them; the message after removing says how many left.
Metrics Errors
- days must be one of 7, 30, 90, 365 (
INVALID_METRICS_WINDOW): the API was asked for another window. Use one of those values, or leavedaysout for 30. - tz must be a time zone name such as America/New_York or The database does not know the time zone … (
INVALID_TIME_ZONE): the time zone sent with the request is not one the database can count days in. Through the API, pass a name such asEurope/London, or leavetzout to count on the database server's date. On the Metrics screen it is the browser's time zone; set the computer's time zone to a named zone. - Metrics could not be read from the database (
METRICS_FAILED): the metadata database did not answer. CheckGET /api/healthand the server log, then choose Retry. - A card says there is nothing to measure: nothing of that kind happened in the window, for example no run finished. Choose a longer window.
- The numbers differ from Executions or Approvals: Metrics counts only the chosen window, dates runs by when they started and approvals by when they were requested, and counts days in your browser's time zone; the lists show everything.
- A failure cause ends early: causes are cut at 160 characters. Open the run in Executions for the full message.
- Deleted connection in a chart: those runs were on a connection that has since been deleted; their history is kept.
AI Agent Errors
- AI is not set up on the AI Agents page, or AI is not configured (
AI_NOT_CONFIGURED, 503): no provider is chosen. An administrator sets one under Settings → AI Configuration. - AI agents are disabled by an administrator. (
AGENTS_DISABLED, 403): the AI agents enabled switch is off. It also stops the IDE assistant, the AI review on approvals and blast radius narratives. - Rate limit reached for ... Try again in N s. (
RATE_LIMITED, 429): wait the time shown, or ask an administrator to raise the limit or turn rate limiting off. - ... is required: ... or ... must be ... (
INVALID_AGENT_INPUT, 400): fill in or correct the field the message names. Without a connection, choose the engine. - Query Optimization takes one statement; this text has N.: paste only the query to tune, without other statements.
- The connection could not be opened: ... (
CONNECTION_FAILED): the agent could not reach the database. Test the connection under Connections; the message carries the database's own error. - The schema of this connection could not be read: ... or The relationships of this connection could not be read: ... or The dependencies of this connection could not be read: ... (
SCHEMA_UNREADABLE): the connection's user may lack permission to read the catalog. Grant read access to the system catalog orinformation_schema. - The AI model's answer ... Run the agent again. (
MODEL_ANSWER_UNUSABLE): the model left out part of the answer or did not answer in the expected form. Run again; if it keeps happening, try another model. - The AI model's answer was cut off at the Max Tokens limit of N. Raise Max Tokens in AI Configuration, then run the agent again. (
MODEL_ANSWER_CUT_OFF): the model reached the Max Tokens limit before finishing, so the answer could not be used. Running again with the same limit stops at the same place; raise Max Tokens in AI Configuration first. - The AI model's answer was cut off at the provider's length limit. Shorten the input or choose another model, then run the agent again. (
MODEL_ANSWER_CUT_OFF): the provider stopped the answer at its own limit, which it does not let you change (Augment). Review a shorter script, or choose a model that answers in fewer words. - The run history could not be read: ... (
HISTORY_UNREADABLE): Anomaly Detection could not read script runs from the metadata database. CheckGET /api/healthand the server log. - This release has no scripts to summarise: add scripts to the release first.
- An advisory says tables were excluded or the schema was cut: an administrator excluded tables or set a table cap, so the answer cannot mention those tables. Ask an administrator if the answer needs them.
- Relationships are not read for ... connections or Dependencies are not read for ... connections: the connection's engine is not one of the thirteen whose relationships and dependencies DB Studio reads, so Schema Explorer and Change Impact answer from the schema alone.
- The agent did not finish within 5 minutes, so it was cancelled.: with async mode set to Always, the run was still queued or running. Check that the server is not overloaded and that the provider's timeout is shorter than 5 minutes.
- This run could not be added to the run history: ...: the answer is complete but was not recorded. Check the metadata database and the server log.
DB DevOps Page Load Errors
- The migration scripts could not be loaded: …, or the same for releases, approval requests, repo sources or connections: the server did not return that list, and the reason it gave follows the colon. The page shows the reason instead of an empty list, so a failure never looks like missing data. Fix the cause the reason names (check
GET /api/healthand the server log when it names none), then choose Retry; the other pages keep working.
Users and Sign-in Errors
- Invalid email or password at sign-in: the email has no account, or the password is wrong. Both get the same answer so that the sign-in page does not reveal which addresses have accounts. Each wrong password counts toward Max Login Attempts.
- Your session has ended because the account was deactivated / no longer exists / the password was changed. Sign in again. (
SESSION_ENDED): an administrator changed the account. Sign in again; a deactivated account cannot sign in until it is reactivated. - password must have at least N characters, with the character classes when complexity is on (
WEAK_PASSWORD): choose a password that meets the rule the message states, which an administrator sets under Settings → Session & Security; it applies to account setup, reset links and administrator resets. - You cannot deactivate your own account or delete your own account (
OWN_ACCOUNT): ask another administrator. - You cannot ... the only active admin (
LAST_ADMIN): make another user an administrator first, then change this one. - A user with this email already exists (
EMAIL_TAKEN): each account needs its own email. Reactivate the existing account instead of inviting again. - email must be a valid email address or role must be one of ... (
INVALID_USER): correct the field the message names. - No pending invite for this email on Resend invite: the person has already set a password, or the account was created with a password. They can use Forgot password on the sign-in page.
- The users could not be loaded on the Users page: the metadata database did not answer. Check
GET /api/healthand the server log, then choose Retry. - Account locked. Try again in N minute(s). at sign-in: the account reached Max Login Attempts wrong passwords or codes in a row and is locked for Lockout Duration (Settings → Session & Security; 5 and 15 minutes by default). Only the right password gets this answer; a wrong one gets Invalid email or password and does not extend the lock.
- This account signs in with Okta., or another provider: the account was created through single sign-on. Use that provider's button on the sign-in page.
- Password sign-in is turned off for your account: sign in with single sign-on. at sign-in: an administrator turned Allow Local Authentication off while single sign-on has a provider on. Use the SSO button; administrators can still sign in with a password.
- Could not read the sign-in options: HTTP 503, or another reason, on the sign-in form: the server did not say which single sign-on providers are enabled or who may sign in with a password, so their buttons and notes are missing. The server is usually still starting; reload the page once it answers. Accounts allowed to use a password can still sign in with one.
- minPasswordLength must be a whole number of characters from 12 to 200, or a similar message for another Session & Security value: the value is outside its range, and nothing was saved.
Migration Scripts Errors
- Pull Scripts is greyed out: choose the Environment of the new scripts first; every script needs one. Through the API a pull without one is
400 INVALID_SCRIPT_TARGETwith the environments to choose from. - environment is required: choose one of: … or Unknown environment "…"; choose one of: … (
INVALID_SCRIPT_TARGET): a script needs an environment that exists. Choose one of those listed, or create it on the Environments page (add an environment first means none exists yet). - … is in Staging, not Development: choose an instance in Development, or set the script's environment to Staging (
TARGET_IN_OTHER_ENVIRONMENT): the chosen instance belongs to another environment, usually because its connection was moved after the script was written. Open the script, choose Edit, and either pick an instance in the script's environment or change the environment to the instance's; the card's Instance is in … line and the note in the detail go away once they agree. - No instances in … or No Redis instances in … when uploading: no connection of the script's engine is in that environment. Add a connection to the environment, or choose another environment.
- Could not connect to … or Could not list the databases of … under Database: the instance did not answer; the reason follows. Check that it is running and reachable from the server, then choose Retry.
- … has no list of databases: that engine's scripts run against the database set on the connection; no database needs to be chosen.
- Not a script file: …: only script files are uploaded; rename the file with the engine's extension.
- … can describe a …, not a … in Compare with Target: that engine has no definitions for the chosen object type; choose one of the types it lists.
- The definition could not be read: … in Compare with Target: the instance refused or failed the lookup; the database's reason follows (often a missing permission to read the catalog). Choose Retry once it is fixed.
Approvals Errors
- "…" is in Staging, so its approval request is for Staging, not …: a script request is for the script's own environment. Move the script to the other environment first if that is where it should run.
- … was not rejected: a rejection needs a reason.: type the reason the requester should read, then Reject.
- Approval of … was not requested: enter a whole number from 1 to 10.: Request Approval asks how many approvals the script needs; enter 1 to 10.
- The AI review of … did not finish: …: the AI model answered without a risk score or outside the review format, or the provider refused the request; the reason follows. Choose Review Again, and check the AI provider under Settings if it keeps failing.
Executions Errors
- This run cannot be rolled back: its rollback has no statement to run.: the change cannot be undone automatically (for example a dropped schema and its data); the notes shown say why. Restore from a backup, or write a script that recreates what you need.
- … was not rolled back: a rollback needs a reason.: type why you are rolling the run back, then Roll Back.
- The rollback of … failed and the run stays completed: …: the rollback SQL failed against the database; the database's reason follows. The failed rollback is recorded as its own run.
Releases Errors
- Other environment on a row in Add Scripts, or A release runs in one environment …: the script targets a connection in another environment than the release's targeted scripts, and a run stays in one environment. Add it to a release for that environment, or give the script a target in the release's environment.
- Other database system on a row in Add Scripts, or A release can only contain one database system …: the script targets another database system than the release's scripts. Put it in its own release.
- No release can take these scripts when uploading or editing a script: every release runs in another environment or on another database system. Create a release for the script's environment, or choose another environment or instance.
- Delete is greyed out on a release: it has run or has approval requests, and that history is kept.
- A run must stay in one environment … when running an older release: its scripts target connections in two environments. Take out the scripts of one environment, or give them targets in the other, and sign it off again.
Audit Log Errors
- Every entry shows the same IP, usually a private address: a reverse proxy or load balancer sits in front of DB Studio and is not listed in
TRUST_PROXY, so the proxy's address is recorded. List its address or network there (for Docker Compose, the compose network's range) and have it sendX-Forwarded-For. - No license is active.: entries are still recorded; they appear once a license is activated in Settings.
- Audit Log is not in the menu: it is shown only to administrators. The audit API answers other accounts with
403 Insufficient permissions. - days must be a whole number of 1 or more. from
DELETE /api/db-devops/audit/cleanup: pass a whole number of days, such as?days=365.
Roles Errors
- Your roles do not include ..., so you cannot make changes there. (
SECTION_REQUIRED): none of your roles includes that DB DevOps section. Ask an administrator to add the section to your built-in role or give you a custom role that has it. - Built-in roles cannot be renamed or deleted (
BUILT_IN_ROLE): create a custom role instead. The admin role always has every section is the same refusal for the admin role’s sections. - "..." is the name of a built-in role or A role named "..." already exists (
ROLE_NAME_TAKEN): role names are unique in any letter case; choose another. - Only the admin role can include Administration (
INVALID_ROLE): Administration pages need the admin role, so give the person the Admin role on the Users page instead. - Assign built-in roles on the Users page, not here (
INVALID_ROLE_ASSIGNMENT): change the person’s role on the Users page; Assign Roles takes custom roles only. - A single sign-on user did not get a mapped role: group names are matched against the claim values the identity provider sends, ignoring letter case; Entra sends group object ids unless configured otherwise. Built-in roles do not take groups.
- The roles could not be loaded, The members could not be loaded or This user’s roles could not be loaded on the Roles page: the metadata database did not answer. Check
GET /api/healthand the server log, then choose Retry.
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:
- Format: Keys are
SDB-{base64url payload}.{base64url RSA-SHA256 signature}— aSDB-prefix and exactly one.separating payload and signature. A mangled copy/paste (whitespace, line wrap) breaks the signature check. - Signature: Verification uses
keys/license-public.pem. A key signed against a different keypair fails validation. Re-issue against the deployment's keypair. - Expiry: A key whose expiration date has passed is refused at activation. An active license keeps working for 7 days after it expires, then every container answers
403 LICENSE_REQUIREDwithexpired: trueuntil a renewed key is applied; with no usable license, activation needs no sign-in. - "This license key is already in use by another setup": the key is registered to another metadata database. Deactivate the license there (Settings) to free it, or ask Simcha to release it if that setup is gone. App containers sharing one database are one setup and need no extra key. A key issued for an installation ID works only on that installation.
- Check for an Updated License fails:
LICENSE_SERVICE_UNREACHABLEmeans the server could not reachLICENSE_SERVICE_URL(allow egress to it, or paste the key instead); no record of this license means the key was not issued through the license service; paste the new key you were sent. - "This key was issued on … after the current time on this server": the key's issue date is more than a day ahead of the database server's clock, or of the latest time the database has recorded. Correct the clock of the app and database servers; setting a clock back does not extend a license.
- A container keeps the old license: every container listens for license changes on the metadata database and reads the license again when one is announced, and again every 60 seconds in any case. A container whose listener lost its connection logs Lost the license change listener; reconnecting and reads the license again as soon as it reconnects; check that the containers reach the same database.
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.