Problem
Claude preview owns the browser surface and uses .claude/launch.json autoPort to choose the port for a launcher-managed dev server. Claude passes that selected port to the child process through the PORT environment variable.
storybook dev does not currently read PORT. It only receives an explicit CLI --port value or the existing SBCONFIG_PORT env mapping. The current Claude launch skill guidance also tells agents to pass --port $PORT, but Claude launches runtimeArgs without shell expansion, so Storybook receives the literal string $PORT instead of a number.
The result is a broken Claude preview flow: Claude can report one port while Storybook binds another, and Storybook may also open an extra browser window even though Claude preview already provides the browser surface.
Related tracker: #34826
Delivery shape
This requires two coordinated PRs:
- A
storybookjs/storybook PR for the core storybook dev behavior and CLI docs.
- A
storybookjs/mcp PR for the Claude launch skill guidance that writes .claude/launch.json.
The Storybook core PR should land first or be available as a canary before the mcp/plugin skill PR is considered complete, because the new launch guidance depends on Storybook reading PORT and suppressing the extra browser in Claude preview.
Intended behavior
- When
storybook dev is launched by Claude preview, Storybook should not open an additional browser window.
- This should be scoped to Claude preview, not all detected agents and not generic CI.
- Claude preview detection should reuse the existing runtime provenance signal:
CLAUDE_AGENT_SDK_VERSION is present and AI_AGENT is absent.
- The existing
claude-preview runtime record behavior should keep working.
- Existing env-backed dev options should keep the same behavior as
getEnvConfig: truthy env values override the corresponding Commander option for SBCONFIG_HOSTNAME, SBCONFIG_STATIC_DIR, SBCONFIG_CONFIG_DIR, and CI.
storybook dev should resolve and validate the dev options object with Valibot before starting the dev server.
- Port resolution should use strict valid-port parsing for the selected value.
- Outside Claude preview, port precedence should be: explicit CLI
--port > SBCONFIG_PORT > PORT.
- In Claude preview, port precedence should be: launcher-provided
PORT > explicit CLI --port > SBCONFIG_PORT.
- A hardcoded numeric project script port should therefore keep winning outside Claude preview, but a valid Claude preview
PORT should override it.
- Literal shell placeholders such as
--port '$PORT' are not supported interpolation. If such a value is the selected port, Valibot should reject it.
- Invalid selected port values should fail before dev server startup using Valibot's validation error.
- Claude launch config guidance should stop generating
--port $PORT and should not need --ci just to avoid a browser window.
Storybook core PR
Implementation notes:
- Extract the existing Claude preview launcher detection into a reusable helper.
- Reuse that helper from the runtime instance registry so records still write
agent: "claude-preview" for Claude preview-launched Storybooks.
- In the
storybook dev CLI path, make options.open default to false for Claude preview-launched processes.
- Do not set
process.env.CI and do not treat Claude preview as CI.
- Replace the dev command's existing
getEnvConfig mutation with an inline resolved options object that preserves the same truthy env override behavior for existing env-backed options.
- Add Valibot parsing for the resolved dev options object, including strict port parsing.
- Implement port precedence as:
- Claude preview:
PORT ?? options.port ?? SBCONFIG_PORT
- non-Claude preview:
options.port ?? SBCONFIG_PORT ?? PORT
- Update
docs/api/cli-options.mdx with a concise note that storybook dev also reads PORT for launcher/platform integrations, and that Claude preview PORT overrides hardcoded script ports.
Acceptance criteria:
CLAUDE_AGENT_SDK_VERSION=1 with no AI_AGENT causes storybook dev to skip automatic browser opening by default.
- The same Claude preview detection still records runtime provenance as
agent: "claude-preview".
- Truthy
SBCONFIG_HOSTNAME, SBCONFIG_STATIC_DIR, SBCONFIG_CONFIG_DIR, and CI values keep overriding their corresponding dev command options as they did through getEnvConfig.
PORT=6123 storybook dev starts Storybook on port 6123 when no --port or SBCONFIG_PORT is provided.
- Outside Claude preview,
PORT=6123 storybook dev --port 7007 starts Storybook on port 7007.
- Outside Claude preview,
PORT=6123 SBCONFIG_PORT=7008 storybook dev starts Storybook on port 7008.
- Outside Claude preview,
SBCONFIG_PORT=7008 storybook dev --port 7007 starts Storybook on port 7007.
- In Claude preview,
CLAUDE_AGENT_SDK_VERSION=1 PORT=6123 storybook dev --port 7007 starts Storybook on port 6123.
- In Claude preview without
PORT, CLAUDE_AGENT_SDK_VERSION=1 SBCONFIG_PORT=7008 storybook dev --port 7007 starts Storybook on port 7007.
- In Claude preview without
PORT or --port, CLAUDE_AGENT_SDK_VERSION=1 SBCONFIG_PORT=7008 storybook dev starts Storybook on port 7008.
PORT=not-a-port storybook dev fails before startup with a Valibot validation error for the selected port value.
SBCONFIG_PORT=7007abc storybook dev fails before startup with a Valibot validation error for the selected port value.
storybook dev --port '$PORT' is not treated as a supported interpolation mechanism and fails when that value is selected as the port.
- Non-Claude-preview launches keep the existing browser-open default.
- Docs mention the
PORT fallback behavior near the --port CLI option.
Verification:
- Add focused unit coverage for the extracted Claude preview detection helper.
- Add or update runtime registry coverage proving
agent: "claude-preview" still uses the helper.
- Verify the resolved dev option behavior with targeted local checks or focused tests for normal port precedence, Claude-preview port precedence,
SBCONFIG_PORT, invalid selected port values, and Claude-preview open: false.
- Run formatting from
code/ with yarn fmt:write after code changes.
- Run targeted lint/check commands for changed files.
MCP plugin PR
Implementation notes:
- Update the Claude launch skill in
storybookjs/mcp so it preserves the existing behavior of discovering the project's Storybook script, preferred package manager, and invocation directory, but no longer requires --port $PORT / %PORT% or --ci.
- Do not replace the working script-discovery behavior with a universal
storybook dev command.
- Update any related Claude plugin README, QA guide, tests, or snapshots in
storybookjs/mcp that still describe --port $PORT or --ci as required for Claude preview.
- Keep the skill focused on launching Storybook through Claude preview, not on ad hoc background shell commands.
Acceptance criteria:
- The Claude launch skill no longer instructs agents to generate
--port $PORT / %PORT% or add --ci for Claude preview.
- The skill still instructs agents to inspect and use the project's existing Storybook script, preferred package manager, and Storybook invocation directory.
- The launch guidance still requires
autoPort: true and relies on Claude providing PORT while Storybook core consumes it.
- Related tests/snapshots/docs in
storybookjs/mcp match the new launch guidance.
Verification:
- Run formatting and targeted tests/checks for the changed
storybookjs/mcp package files.
- Manually inspect the generated or documented Claude launch guidance to confirm it:
- uses
autoPort: true
- keeps the existing project-script/package-manager launch shape and
cwd behavior
- does not include
--port $PORT, %PORT%, or --ci
- does not require a universal
runtimeArgs shape such as ["storybook", "dev"]
Out of scope
- Do not remove, merge, or broadly rewrite the Claude plugin skills as part of this work.
- Do not slim down the
stories skill beyond removing or updating wording that directly conflicts with this issue.
- Keep the MCP plugin PR to the minimal launch guidance changes required by the Storybook core behavior.
End-to-end verification
After both PRs are available, manually smoke-test the intended Claude preview flow where feasible:
- Use a Claude launch entry with the project's existing Storybook script,
autoPort: true, and no --port $PORT / %PORT% / --ci runtime args.
- Confirm Claude preview and Storybook agree on the selected port, including when the project script hardcodes a numeric Storybook port.
- Confirm Storybook does not open an additional external browser window.
Problem
Claude preview owns the browser surface and uses
.claude/launch.jsonautoPortto choose the port for a launcher-managed dev server. Claude passes that selected port to the child process through thePORTenvironment variable.storybook devdoes not currently readPORT. It only receives an explicit CLI--portvalue or the existingSBCONFIG_PORTenv mapping. The current Claude launch skill guidance also tells agents to pass--port $PORT, but Claude launchesruntimeArgswithout shell expansion, so Storybook receives the literal string$PORTinstead of a number.The result is a broken Claude preview flow: Claude can report one port while Storybook binds another, and Storybook may also open an extra browser window even though Claude preview already provides the browser surface.
Related tracker: #34826
Delivery shape
This requires two coordinated PRs:
storybookjs/storybookPR for the corestorybook devbehavior and CLI docs.storybookjs/mcpPR for the Claude launch skill guidance that writes.claude/launch.json.The Storybook core PR should land first or be available as a canary before the mcp/plugin skill PR is considered complete, because the new launch guidance depends on Storybook reading
PORTand suppressing the extra browser in Claude preview.Intended behavior
storybook devis launched by Claude preview, Storybook should not open an additional browser window.CLAUDE_AGENT_SDK_VERSIONis present andAI_AGENTis absent.claude-previewruntime record behavior should keep working.getEnvConfig: truthy env values override the corresponding Commander option forSBCONFIG_HOSTNAME,SBCONFIG_STATIC_DIR,SBCONFIG_CONFIG_DIR, andCI.storybook devshould resolve and validate the dev options object with Valibot before starting the dev server.--port>SBCONFIG_PORT>PORT.PORT> explicit CLI--port>SBCONFIG_PORT.PORTshould override it.--port '$PORT'are not supported interpolation. If such a value is the selected port, Valibot should reject it.--port $PORTand should not need--cijust to avoid a browser window.Storybook core PR
Implementation notes:
agent: "claude-preview"for Claude preview-launched Storybooks.storybook devCLI path, makeoptions.opendefault tofalsefor Claude preview-launched processes.process.env.CIand do not treat Claude preview as CI.getEnvConfigmutation with an inline resolved options object that preserves the same truthy env override behavior for existing env-backed options.PORT ?? options.port ?? SBCONFIG_PORToptions.port ?? SBCONFIG_PORT ?? PORTdocs/api/cli-options.mdxwith a concise note thatstorybook devalso readsPORTfor launcher/platform integrations, and that Claude previewPORToverrides hardcoded script ports.Acceptance criteria:
CLAUDE_AGENT_SDK_VERSION=1with noAI_AGENTcausesstorybook devto skip automatic browser opening by default.agent: "claude-preview".SBCONFIG_HOSTNAME,SBCONFIG_STATIC_DIR,SBCONFIG_CONFIG_DIR, andCIvalues keep overriding their corresponding dev command options as they did throughgetEnvConfig.PORT=6123 storybook devstarts Storybook on port6123when no--portorSBCONFIG_PORTis provided.PORT=6123 storybook dev --port 7007starts Storybook on port7007.PORT=6123 SBCONFIG_PORT=7008 storybook devstarts Storybook on port7008.SBCONFIG_PORT=7008 storybook dev --port 7007starts Storybook on port7007.CLAUDE_AGENT_SDK_VERSION=1 PORT=6123 storybook dev --port 7007starts Storybook on port6123.PORT,CLAUDE_AGENT_SDK_VERSION=1 SBCONFIG_PORT=7008 storybook dev --port 7007starts Storybook on port7007.PORTor--port,CLAUDE_AGENT_SDK_VERSION=1 SBCONFIG_PORT=7008 storybook devstarts Storybook on port7008.PORT=not-a-port storybook devfails before startup with a Valibot validation error for the selected port value.SBCONFIG_PORT=7007abc storybook devfails before startup with a Valibot validation error for the selected port value.storybook dev --port '$PORT'is not treated as a supported interpolation mechanism and fails when that value is selected as the port.PORTfallback behavior near the--portCLI option.Verification:
agent: "claude-preview"still uses the helper.SBCONFIG_PORT, invalid selected port values, and Claude-previewopen: false.code/withyarn fmt:writeafter code changes.MCP plugin PR
Implementation notes:
storybookjs/mcpso it preserves the existing behavior of discovering the project's Storybook script, preferred package manager, and invocation directory, but no longer requires--port $PORT/%PORT%or--ci.storybook devcommand.storybookjs/mcpthat still describe--port $PORTor--cias required for Claude preview.Acceptance criteria:
--port $PORT/%PORT%or add--cifor Claude preview.autoPort: trueand relies on Claude providingPORTwhile Storybook core consumes it.storybookjs/mcpmatch the new launch guidance.Verification:
storybookjs/mcppackage files.autoPort: truecwdbehavior--port $PORT,%PORT%, or--ciruntimeArgsshape such as["storybook", "dev"]Out of scope
storiesskill beyond removing or updating wording that directly conflicts with this issue.End-to-end verification
After both PRs are available, manually smoke-test the intended Claude preview flow where feasible:
autoPort: true, and no--port $PORT/%PORT%/--ciruntime args.