chatboxai/chatbox · error · Error
Failed to start preview server
Error message
Failed to start preview server
What it means
Thrown by the preview server's start routine in two spots: (1) inside the listen callback if server.address() is missing or not an object (reject), and (2) after the await if port is still null (the listen callback never resolved). '127.0.0.1' binding with port 0 (OS-assigned) is the contract; failure means no port could be bound or the address wasn't returned in time.
Source
Thrown at src/main/sandbox/preview-server.ts:173
server = createServer((req, res) => {
void handleRequest(req, res)
})
await new Promise<void>((resolve, reject) => {
server?.once('error', reject)
server?.listen(0, '127.0.0.1', () => {
const address = server?.address()
if (address && typeof address === 'object') {
port = address.port
resolve()
} else {
reject(new Error('Failed to start preview server'))
}
})
})
if (port === null) throw new Error('Failed to start preview server')
return port
}
export async function createSandboxHtmlPreviewUrl(
filePath: string
): Promise<{ success: boolean; url?: string; error?: string }> {
try {
const sandboxRoots = getSandboxRoots()
const fileStat = await lstat(filePath)
if (fileStat.isSymbolicLink()) {
return { success: false, error: 'Access denied: symlinks not allowed' }
}
const resolvedPath = await realpath(filePath)
const sandboxRoot = sandboxRoots.find((root) => isInside(root, resolvedPath))
if (!sandboxRoot) {
return { success: false, error: 'Access denied: path outside sandbox directory' }
}
if (!['.html', '.htm'].includes(path.extname(resolvedPath).toLowerCase())) {View on GitHub (pinned to 81571269ad)
Solutions
- Attach a listener for the 'error' event on the server BEFORE listen — EADDRINUSE/EACCES surface there and currently reject (the first reject wins). Surface that error specifically rather than the generic 'Failed to start preview server'.
- Retry start() a few times with backoff to absorb transient bind races when port 0 collides.
- In sandboxed runtimes, confirm 127.0.0.1 is available; if not, the preview feature must be disabled for that environment.
- Ensure the server instance is freshly created per start attempt (a closed server will not emit listen).
Example fix
// before
server?.once('error', reject)
server?.listen(0, '127.0.0.1', () => { ... else reject(new Error('Failed to start preview server')) })
// after: include the underlying error cause
server?.once('error', (err) => reject(new Error('Failed to start preview server', { cause: err }))) Defensive patterns
Strategy: retry
Validate before calling
if (server.listening) throw new Error('Preview server already started') Type guard
function isPreviewStartFailure(e: unknown): e is Error { return e instanceof Error && e.message === 'Failed to start preview server' } Try / catch
for (let attempt = 0; attempt < 3; attempt++) {
try { return await startPreviewServer() } catch (e) { if (!isPreviewStartFailure(e) || attempt === 2) throw e; await delay(200 * 2 ** attempt) }
} Prevention
- Create a fresh http.Server per start attempt; a closed server will not emit listen.
- In sandboxed runtimes, verify 127.0.0.1 availability before enabling previews.
- Attach the 'error' listener before listen so EADDRINUSE surfaces with a cause.
When it happens
Trigger: server.listen(0,'127.0.0.1',cb) never calls cb with a valid AddressInfo (address undefined / not typeof 'object'), or the cb branch that resolves never ran so port stays null after the promise settles. Common when an 'error' event rejects the promise first, but also when listen silently fails.
Common situations: Another process already holds the randomly assigned port (rare with port 0 but possible during a race); the loopback interface is unavailable in a sandboxed/containerized env; the http.Server was already closed before listen fired; system fd/socket exhaustion.
Related errors
- Not found
- Knowledge base name cannot be empty
- ${response.status} ${response.statusText}: ${text}
- Qwen OAuth failed: ${payload.error_description || payload.er
- Token refresh failed: ${text}
AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12).
Data as JSON: /api/errors/a8eb74d9d2e2d6e5.
Report an issue: GitHub.