withastro/astro · error · Error

Another astro dev server is already running. URL: ${exis

Error message

Another astro dev server is already running.

  URL:  ${existingServer.url}
  PID:  ${existingServer.pid}

Run `astro dev stop` to stop it, or use `astro dev --force` to replace it.

What it means

Astro writes a lock file (containing PID, port, URL) to the project root's `.astro` directory when `astro dev` starts. On the next `astro dev` invocation it reads that file, checks whether the recorded PID is still alive, and refuses to start a second dev server against the same root. This prevents port/PID conflicts and ensures only one HMR process owns a project at a time.

Source

Thrown at packages/astro/src/cli/dev/index.ts:221

		const inlineConfig = flagsToAstroInlineConfig(flags);
		return await devServer(inlineConfig);
	}

	const existingServer = checkExistingServer(root);
	if (existingServer) {
		if (flags.force) {
			// --force: kill the existing server and replace it
			await killDevServer(root, existingServer);
		} else {
			const message = [
				'Another astro dev server is already running.',
				'',
				`  URL:  ${existingServer.url}`,
				`  PID:  ${existingServer.pid}`,
				'',
				`Run \`astro dev stop\` to stop it, or use \`astro dev --force\` to replace it.`,
			].join('\n');
			throw new Error(message);
		}
	}

	const inlineConfig = flagsToAstroInlineConfig(flags);
	const server = await devServer(inlineConfig);

	// Use Vite's resolved URL which accounts for host and protocol (http/https).
	const serverUrl = resolveLockFileUrl(server.resolvedUrls);
	if (serverUrl) {
		writeLockFile(root, {
			pid: process.pid,
			port: server.address.port,
			url: serverUrl,
			urls: server.resolvedUrls,
			background: !!process.env.ASTRO_DEV_BACKGROUND,
			startedAt: new Date().toISOString(),
		});

View on GitHub (pinned to d081033d5f)

Solutions

  1. Run `astro dev stop` to stop the existing server (uses the PID from the lock file).
  2. Run `astro dev --force` to kill the existing server and replace it in one step.
  3. If the PID is stale but the process appears gone, manually kill the process holding the port and delete the lock file in `.astro/`.
  4. Check `ps -p <PID>` to confirm whether the recorded PID is actually a live Astro process before killing.

Example fix

// before
$ astro dev
// after
$ astro dev stop && astro dev
// or
$ astro dev --force
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

function isDevServerRunning(root) {
  const lockPath = join(root, '.astro', 'dev.lock');
  if (!existsSync(lockPath)) return null;
  try {
    const { pid, url } = JSON.parse(readFileSync(lockPath, 'utf-8'));
    if (pid && process.kill(pid, 0)) return { pid, url };
  } catch {}
  return null;
}

const existing = isDevServerRunning(process.cwd());
if (existing) console.log(`Already running at ${existing.url} (PID ${existing.pid}) — pass --force.`);

Prevention

When it happens

Trigger: Running `astro dev` when `checkExistingServer(root, 'dev')` returns a non-null result (a live process holding the lock for that root). The `--force` flag bypasses this by calling `killDevServer` first.

Common situations: A previous `astro dev` was left running in another terminal or editor; a crashed session left a stale-but-live process; CI reused a workspace without tearing down the prior server; you switched branches but forgot the old server is still bound to the project root.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/bef60b52ba0a896a. Report an issue: GitHub.