decolua/9router · warning · Error
MITM server is already starting (lock contention)
Error message
MITM server is already starting (lock contention)
What it means
9Router's MITM manager serializes concurrent starts across processes with a lock file created atomically via fs.writeFileSync(..., { flag: "wx" }) (O_EXCL). When another live process already holds the lock (its PID is readable from the lock file and that PID is still alive), startServer throws 'MITM server is already starting (lock contention)'. Only stale locks (dead PID or unreadable file) are cleaned up and reclaimed.
Source
Thrown at src/mitm/manager.js:504
} catch { /* ignore */ }
}
if (serverProcess && !serverProcess.killed) {
throw new Error("MITM server is already running");
}
// Atomically claim lock to prevent concurrent startServer across processes.
// O_EXCL (flag: "wx") fails with EEXIST if the file already exists.
try {
fs.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
} catch (e) {
if (e.code === "EEXIST") {
let stale = false;
try {
const pid = parseInt(fs.readFileSync(LOCK_FILE, "utf-8").trim(), 10);
stale = !pid || !isProcessAlive(pid);
} catch { stale = true; } // unreadable lock → treat as stale
if (!stale) throw new Error("MITM server is already starting (lock contention)");
try { fs.unlinkSync(LOCK_FILE); } catch { /* ignore */ }
fs.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
} else throw e;
}
try {
await killLeftoverMitm(sudoPassword);
if (!IS_WIN) {
const portStatus = await checkPort443Free();
if (portStatus === "in-use" || portStatus === "no-permission") {
const owner = await getPort443Owner(sudoPassword);
if (owner) {
const shortName = owner.name.includes("/")
? owner.name.split("/").filter(Boolean).pop()
: owner.name;
if (forceKillPort443) {
log(`Killing process on port 443 (PID ${owner.pid}, name=${shortName})...`);View on GitHub (pinned to 90b52e06ff)
Solutions
- Wait a few seconds and retry — the owning process finishes start and releases the lock; meanwhile check its health endpoint on the MITM port instead of starting again
- Find the process holding the lock: read the PID from the lock file under ~/.9router (MITM_DIR) and inspect `ps -p <pid>`; kill it if it is a hung duplicate
- Delete the stale-looking lock file manually if the PID is dead or belongs to nothing, then retry start (the code also self-heals stale locks)
- Ensure only one 9Router instance runs per machine/DATA_DIR; for multi-instance use separate DATA_DIR values
Example fix
// before: blind retry loop hammering start
await startMitm();
// after: check if already running before starting
const health = await fetch('http://127.0.0.1:<MITM_PORT>/health').then(r => r.ok).catch(() => false);
if (!health) await startMitm(); Defensive patterns
Strategy: retry
Validate before calling
const fs = require('fs');
const LOCK = require('os').homedir() + '/.9router/mitm/start.lock'; // match MITM_DIR lock path
function lockHolderAlive() {
try {
const pid = parseInt(fs.readFileSync(LOCK, 'utf-8').trim(), 10);
try { process.kill(pid, 0); return pid; } catch { return null; }
} catch { return null; }
} Try / catch
const backoff = [500, 1500, 4000];
for (const ms of backoff) {
try { await startMitm(apiKey); break; }
catch (e) {
if (!/lock contention/i.test(e.message)) throw e;
await new Promise(r => setTimeout(r, ms));
}
} Prevention
- Only start the MITM proxy through one entry point (dashboard or CLI), not both
- Check the health endpoint first; skip startServer when it already responds
- Use a single 9Router instance per DATA_DIR/machine
- Treat this error as transient — implement bounded retry with backoff
When it happens
Trigger: Calling startServer (or an API/CLI path that starts the MITM proxy) while another 9Router process — dashboard server, CLI instance, or a previous uncrashed spawn — is mid-start and holds the lock; two clients racing the start endpoint simultaneously; a sibling process that wrote the lock then hung before removing it.
Common situations: Running `9router` CLI and the dashboard dev server at once and both triggering MITM start; double-clicking a tray/start action firing two starts; a previous start attempt crashed after writing the lock but its PID is still alive (hung process); Docker/multi-instance setups sharing the same MITM_DIR (~/.9router) on one machine.
Related errors
- MITM server.js not found at ${effectiveServerPath}. Reinstal
- Root CA not found. Start server first to generate it.
- Machine ID is required for Cursor API
- http2 module not available
- HTTP/2 is required for Cursor AgentService (endpoint is h2-o
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/27b4e1403f93a775.
Report an issue: GitHub.