Claude Code's Preview Won't Start? autoPort Isn't Enough

autoPort: true should pick a free port when 3000 is busy. In a monorepo it silently does not, because a hardcoded --port flag beats the PORT env. Here is why and the fix.

Dieser Inhalt ist noch nicht auf Deutsch verfügbar. Die English Originalversion wird angezeigt.

Claude Code's desktop app can now start your dev server and preview the running app in an embedded browser. The first time it failed for me, the error looked trivial:

Port 3000 is in use by another process (not a preview server).

Of course it was in use. I always have my dev servers running on 3000, 3001, and 3002. The documented fix is autoPort: true in .claude/launch.json, so the preview grabs a free port instead. I set it. It still failed on the exact same port.

Quick answer: autoPort: true only works if your start command lets Next.js read the PORT environment variable. If your dev script hardcodes --port 3001, that flag wins over PORT, and autoPort silently does nothing. Remove the hardcoded flag and the feature works as advertised.

Here is the full story, because the silent part is what costs you the afternoon.

The setup: a monorepo with fixed ports

The preview reads its config from .claude/launch.json. Claude generates one for you, and in a monorepo it points each app at its dev script:

{
  "configurations": [
    { "name": "marketing", "runtimeExecutable": "pnpm", "runtimeArgs": ["dev:marketing"], "port": 3001 }
  ]
}

pnpm dev:marketing runs turbo run dev, which runs the app's own dev script. And that script, like most monorepo setups that need stable ports for cross-app calls, pins the port:

// apps/marketing/package.json
{ "scripts": { "dev": "next dev --turbo --port 3001" } }

That --port 3001 is the whole problem, and it is three layers deep from the config you are editing.

What autoPort actually does

Anthropic's docs are clear about the behavior. autoPort: true means: when the preferred port is busy, Claude finds a free one and passes it to your server through the PORT environment variable. autoPort: false means: fail loudly. When the field is unset, Claude asks you once and remembers your answer.

The key phrase is through the PORT environment variable. autoPort does not rewrite your command. It does not inject a --port flag. It sets an env var and trusts your server to read it.

Why it still failed: the flag beats the env

Next.js resolves its port in a fixed order, and a CLI flag outranks the environment. next dev --port 3001 binds 3001 no matter what PORT says. So the sequence was:

  1. autoPort sees 3001 is busy and assigns a free port, say 54502, via PORT=54502.
  2. The command runs next dev --turbo --port 3001.
  3. The explicit --port 3001 overrides PORT. Next.js tries 3001.
  4. 3001 is busy. The server fails on the same port autoPort just tried to route around.

autoPort did its job. The flag quietly undid it. There is no warning, because from Next.js's point of view nothing is wrong: you asked for 3001, it tried 3001. This is the same reason PORT in a .env file is ignored for next dev, the HTTP server boots before that file loads, and the same reason teams pin ports in package.json in the first place. The flag is reliable precisely because it overrides everything, including the thing you now want to win.

The fix: let next dev read PORT

Point the preview config at next dev directly, with no --port, and turn on autoPort:

{
  "configurations": [
    {
      "name": "marketing",
      "runtimeExecutable": "pnpm",
      "runtimeArgs": ["-C", "apps/marketing", "exec", "next", "dev", "--turbo"],
      "port": 3001,
      "autoPort": true
    }
  ]
}

Bypassing the dev:marketing script drops the hardcoded --port. Now next dev falls back to the PORT env var, autoPort sets it, and the preview lands on a free port:

Configured port 3001 was in use, so port 54502 was assigned instead.

One detail worth internalizing: the launch.json config is only used by Claude's preview tool. Your manual pnpm dev:marketing in the terminal still binds 3001. So you can keep your own servers running on their fixed ports and let the preview pick free ports beside them. No conflict, no choosing.

The caveat: when to keep autoPort off

autoPort is not always right. Anthropic calls this out, and it matters: if your app needs an exact port, OAuth redirect URIs registered for localhost:3000, a CORS allowlist, a webhook target, a random port breaks those flows. For those apps, set autoPort: false and free the port instead.

In a typical monorepo this splits cleanly. The marketing site or docs app has no fixed-port dependency, so autoPort: true is ideal. The app with auth callbacks might genuinely need 3000, so leave it explicit. You can mix per configuration in the same launch.json.

Quick answers

I set autoPort: true and it still fails. Why? Your start command almost certainly hardcodes the port (a --port/-p flag in the dev script, often hidden behind turbo run dev). The flag overrides the PORT env var that autoPort sets. Remove it.

Will this change how my dev server runs in the terminal? No. .claude/launch.json only drives Claude Code's preview. Your package.json scripts are untouched.

Should every app use autoPort? Use it for apps with no fixed-port dependency. Keep autoPort: false for anything wired to an exact port, like OAuth callbacks or CORS origins.

Takeaway

When autoPort: true does not pick a free port, the feature is not broken, something is overriding the PORT env var it sets. In a monorepo that something is almost always a hardcoded --port flag buried in your dev script. Call next dev directly so the flag is gone and the env var wins. Then your local servers and Claude Code's preview can run side by side, each on its own port, and you stop choosing between them.