[BUG] preview_start cannot verify Docker-managed dev servers — auto-assigns a port that nothing serves on

Resolved 💬 2 comments Opened Apr 6, 2026 by AlexSostin Closed May 17, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When a project uses Docker Compose (or any externally-managed process) to run its dev server on a fixed host port, preview_start cannot perform real verification against that server.

The tool detects the port is in use, falls back to autoPort and assigns a random high port (e.g. 51769), but nothing is actually serving on that new port — the docker compose up command in runtimeArgs just attaches to the already-running container, which keeps binding to its original fixed port (3050). The assigned port stays empty.

Subsequent preview_eval, preview_snapshot, preview_inspect, and preview_screenshot calls either return empty bodies or chrome-error://chromewebdata/ because the headless browser is pointed at the empty auto-assigned port. Navigating the browser manually to the real port (window.location.href = 'http://localhost:3050/il') also fails because the preview sandbox cannot reach arbitrary host ports.

This breaks the <verification_workflow> expected by the built-in Stop hook: the hook insists on preview_start after any code edit, but there is no way for preview_* tools to actually observe the running site, so every turn ends with a noisy "call preview_start and follow verification_workflow" warning even when the user has already verified changes via curl or an external browser.

What Should Happen?

Either of:

  1. Detect externally-managed server on the configured port — if launch.json has port: 3050 and the port is already responding with HTTP 200, treat that as the live preview target instead of starting a new server on a different port. The preview sandbox should route its headless browser to localhost:3050 in that case.
  1. Let autoPort opt-out explicitly — when autoPort: false, fail fast with a clear message and instructions, rather than silently assigning a fake port the user can't interact with.
  1. Support url field in launch.json as an alternative to runtimeExecutable — if the dev server is external (not managed by Claude Code), the user should be able to declare { "name": "storefront", "url": "http://localhost:3050" } and have preview_* tools target that URL directly, skipping runtime spawning entirely.

Any of these would make preview_* tools usable with the Docker Compose workflow that is the default for Medusa, T3 stack, full-stack Next.js apps with separate API containers, etc.

Error Messages/Logs

# preview_start output (showing the port reassignment)
{
  "serverId": "8edd9e34-9682-4f65-b727-b97b254c4601",
  "port": 51769,
  "name": "storefront",
  "reused": false
}
Server started successfully. Configured port 3050 was in use, so port 51769 was assigned instead (autoPort is enabled). The preview is available at http://localhost:51769.

# preview_logs — docker compose detects the container is already running
 Container techstore_redis Running 
 Container techstore_postgres Running 
 Container techstore_backend Running 
 Container techstore_storefront Running 
Attaching to techstore_storefront
 Container techstore_postgres Waiting 
 Container techstore_postgres Healthy 

# preview_eval navigation to the real Docker port returns chrome-error
window.location.href = 'http://localhost:3050/il'; 'navigated'
-> subsequent preview_inspect on body returns empty {"text": "", "boundingBox": {...}}

Steps to Reproduce

  1. Create a minimal Next.js project managed by Docker Compose that binds a storefront container to localhost:3050 (or pull down any project with this pattern — the PuzzlePC repo, any Medusa v2 storefront template, etc.)
  1. Start the stack: docker compose up -d
  1. Create .claude/launch.json with a storefront entry that uses docker as runtimeExecutable:

```json
{
"version": "0.0.1",
"configurations": [
{
"name": "storefront",
"runtimeExecutable": "docker",
"runtimeArgs": ["compose", "up", "storefront"],
"port": 3050,
"autoPort": true
}
]
}

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.86

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

WSL (Windows Subsystem for Linux)

Additional Information

## Why this matters

Docker Compose managed dev servers are the default shape for:
- Medusa v2 projects (backend + storefront + postgres + redis as separate services)
- Most full-stack monorepos with T3, Next.js + Prisma, RedwoodJS, etc.
- Any project where the dev loop spans multiple services that need to be on the same Docker network
- WSL + Windows Docker Desktop workflows (which is the setup in this report)

For these workflows, the current preview_* tooling is unusable and the built-in Stop hook becomes noise rather than signal — it fires after every edit but there's no way to satisfy it, so users either learn to ignore the callback error (bad — dulls attention for real problems) or disable the hook entirely (bad — loses the real benefit).

## What I actually use to verify code changes in this setup

In the current session I verified my edits through:

1. `docker compose restart storefront` + `sleep 15` to pick up the new code
2. `curl -s -b '_medusa_cache_id=test' 'http://localhost:3050/PATH' -o /tmp/out.html` to fetch rendered HTML
3. `grep -oE '<pattern>' /tmp/out.html` to check for expected markup
4. The Claude in Chrome MCP (separate from preview_*) to take real visual screenshots against the Docker-managed server when I needed eyes-on verification

This workflow works well but is entirely external to preview_* and cannot satisfy the Stop hook. Exposing a "use an external URL" mode in launch.json — even as an opt-in — would let the Stop hook actually validate against the real running site instead of insisting on a broken path.

## Related context

- Not a duplicate of #39057 (permission mode revert) — different subsystem, different symptom, same user friction pattern of "built-in verification path doesn't fit Docker/WSL workflow".
- launch.json schema isn't well-documented anywhere I could find — would be helpful to have a reference doc enumerating the supported fields (`port`, `autoPort`, `runtimeExecutable`, `runtimeArgs`, and the proposed `url`).

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗