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:
autoPortsees 3001 is busy and assigns a free port, say 54502, viaPORT=54502.- The command runs
next dev --turbo --port 3001. - The explicit
--port 3001overridesPORT. Next.js tries 3001. - 3001 is busy. The server fails on the same port
autoPortjust 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.