can1357/oh-my-pi · error · Error

Failed to start stats dashboard on ${hostname}:${port} after

Error message

Failed to start stats dashboard on ${hostname}:${port} after reclaiming it.

What it means

startServer() tries to bind the dashboard's HTTP port; if the port is taken it attempts to reclaim it (e.g. killing the stale listener or retrying the bind). If the bind still fails after that reclaim attempt, it wraps the retry failure (as `cause`) in this error.

Source

Thrown at packages/stats/src/server.ts:403

			stop: () => server.stop(),
		};
	} catch (error) {
		if (!(error instanceof Error && "code" in error && error.code === "EADDRINUSE")) throw error;

		const recovery = await recoverStatsPort(port, hostname);
		if (recovery === "reuse") {
			return { hostname, port, stop: () => {} };
		}

		try {
			const server = createDashboardServer(port, hostname);
			return {
				hostname,
				port: server.port ?? port,
				stop: () => server.stop(),
			};
		} catch (retryError) {
			throw new Error(`Failed to start stats dashboard on ${hostname}:${port} after reclaiming it.`, {
				cause: retryError,
			});
		}
	}
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Check what holds the port: `lsof -i :<port>` or `ss -ltnp`, and stop the conflicting process
  2. Pick a different port when calling startServer (or pass port 0 to let the OS choose)
  3. If binding a privileged port, run with adequate privileges or use a high port
  4. Inspect the `cause` of this error for the underlying bind failure

Example fix

// before
await startServer({ port: 3000 })
// after: avoid hard-coded ports / handle failure
try {
  await startServer({ port: 3000 })
} catch (err) {
  await startServer({ port: 0 }) // let OS pick a free port
}
Defensive patterns

Strategy: retry

Validate before calling

const inUse = Bun.spawnSync(["sh", "-c", `ss -ltn | grep ':${port} '`]).exitCode === 0;
if (inUse) port = 0; // let OS pick a free port

Try / catch

try {
  return await startServer({ hostname, port });
} catch (err) {
  if (err instanceof Error && err.message.includes("after reclaiming it")) {
    return await startServer({ hostname, port: 0 }); // fallback to ephemeral port
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling startServer({ hostname, port }) where the port is occupied and the reclaim/retry path also fails — e.g. the occupying process can't be killed, permission denied on a privileged port, or an immediate rebind race.

Common situations: Another instance of the stats dashboard (or any process) is already listening on the port and refuses to die; running on port <1024 without privileges; hostname unresolvable or in use; container port conflicts.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/533dae399912820b. Report an issue: GitHub.