preview_start prompts in every permission mode since desktop 1.28929.0; approval is in-memory per-chat and cannot be allowlisted

Status Fixed / completed
Reported on v2.1.227
Maintainer reply None cached
Activity 1 comment · opened Aug 12, 2026 · closed Aug 25, 2026

Summary

Since desktop 1.28929.0, starting a dev server via mcp__Claude_Browser__preview_start raises a permission card in every permission mode, including Bypass permissions. The approval is held in memory for the current chat only, so every new chat prompts again, and there is no way to allowlist it. Unattended sessions can no longer start a dev server at all.

I suspect the gate itself is deliberate — spawning a command out of a repo-committed .claude/launch.json is a reasonable thing to ask about, and the code around it looks purpose-built. But three things around it look unintended:

  1. the approval never persists and cannot be allowlisted;
  2. the docs state the opposite in two places, and the bypass-mode exception list doesn't include it;
  3. unattended sessions are hard-denied while the harness still instructs agents to verify in the Browser pane.

Environment

  • Claude Desktop 1.28929.0 (was 1.26832.0), macOS 27.0 (26A5406e), arm64
  • Bundled CLI 2.1.227 (~/Library/Application Support/Claude/claude-code/2.1.227)
  • Permission mode: bypassPermissions (confirmed in the session transcripts)
  • skipDangerousModePermissionPrompt: true in ~/.claude/settings.json
  • No hooks, no managed-settings.json, project settings.local.json has no relevant rules

Timeline — ~/Library/Logs/Claude/main.log

Dev servers started with no permission request at all under 1.26832.0, and the first request appears seven minutes after the update:

2026-08-11 15:46:24 [info] [Preview] Starting server with config {      <- 1.26832.0, no prompt
2026-08-11 21:46:35 [info] Starting app {  appVersion: '1.26832.0',
2026-08-11 21:47:22 [info] Starting app {  appVersion: '1.28929.0',     <- update takes effect
2026-08-11 21:54:11 [info] Emitted tool permission request 63c92ade-… for mcp__Claude_Browser__preview_start in session local_b9018ab9-…
2026-08-11 21:54:22 [info] [Preview] Starting server with config {

Rotated logs covering Jul 30 – Aug 11 contain ~40 [Preview] Starting server with config lines and zero preview_start permission requests. Every start since the update is preceded by one; all six so far were answered once, because once is the only durable outcome available.

Repro

  1. Desktop 1.28929.0, a project with a .claude/launch.json entry that has runtimeExecutable / runtimeArgs.
  2. Set the session to Bypass permissions.
  3. Ask Claude to preview the app, or let auto-verify fire after an edit.
  4. A permission card appears for preview_start. Approve it.
  5. Further preview_start calls in the same chat reuse the running server and don't prompt.
  6. Open a new chat in the same project → prompts again. No dialog option, settings key, or permissions.allow rule prevents it.

This contradicts the documentation

From Desktop application:

  • Under Browse external sites: "Your local dev servers and project files don't need approval", with auto-verify explicitly described as continuing without prompts.
  • The Bypass permissions row of the permission-mode table enumerates what still prompts in that mode — explicit ask rules, org ask connector tools, MCP tools marked requiresUserInteraction, removals targeting / or $HOME, safety classifiers on external sites, and specific desktop actions such as archiving a session. Dev-server spawn is not among them.

Session archiving is documented as always asking "in every permission mode, including Auto and Bypass permissions." Dev-server spawn now behaves the same way but has no equivalent note anywhere, and no changelog entry in 2.1.2232.1.228 covers it.

What the bundle shows

From the minified main bundle in Claude.app/Contents/Resources/app.asar (identifiers are minified, so treat names as approximate):

  • In createCanUseTool, a helper auto-allows every mcp__Claude_Browser__* tool except preview_start, which is special-cased by name and routed to handlePreviewStartPermission.
  • handlePreviewStartPermission itself returns allow for all three outcomes — including early-allow branches for a {url} input and for attach-only configs. So the tool-permission layer is not what prompts.
  • The prompt comes from a second consent request raised inside the spawn path: consentToLaunchSpawnaskForLaunchSpawnhandleToolPermission. Notably, attach and reuse both return before this gate; only a fresh spawn hits it. That split reads as intentional: attaching to something already running is free, executing a command from launch.json is not.
  • handleToolPermission never consults permissionMode to auto-allow. It reads it only to populate telemetry (lam_tool_permission_requested). Its sole auto-approve path is for scheduled tasks. This is why Bypass permissions has no effect — the gate lives in the desktop process, upstream of the CLI's permission engine.
  • Approvals are stored in approvedSpawns, an in-memory Set keyed by sessionId + config, with a forgetLaunchApprovals(sessionId) companion. Nothing is written to disk: no record appears in ~/.claude.json or ~/Library/Application Support/Claude/config.json after approving.
  • preview_start is a member of the set whose permission suggestions get stripped (i = void 0), so the card can't offer Always allow; and bare rules for tools in that set are filtered out of the allowedTools replay, so a hand-written permissions.allow entry is dropped too.

Net effect: approval is once per chat, forever, with no opt-out — which is what makes it feel like an unfinished half rather than a decision.

Unattended sessions are hard-denied

Dev servers can't be started from unattended sessions (scheduled-task runs and
remote-dispatched trees) — nobody is present to approve the command.

This fires before any consent attempt, telemetry reason: unattended_preview_start. Scheduled tasks and Dispatch-spawned trees therefore cannot verify anything in the Browser pane, while the session harness still tells agents to call preview_start and follow the verification workflow. Those runs now fail on an instruction they were given.

What I'd expect

  1. Persist the approval. An Always allow option on the card, scoped to the launch config (or the project), stored on disk like external-site approvals already are. Re-prompting every chat for a command the user wrote and committed themselves has a poor security-to-friction ratio.
  2. Make it allowlistable, or say plainly that it can't be. If the design intends no allowlist, drop preview_start from the suggestion-stripping path anyway so the dialog stops implying an option that gets filtered out downstream.
  3. Fix the docs either way — the two statements above are now wrong, and the bypass exception list should name this if it's staying.
  4. Give unattended sessions a path. A settings-level pre-authorization for a named launch config would cover scheduled verification without a human present; otherwise the harness shouldn't ask those sessions to preview.

Workaround for others hitting this

An attach-only entry returns before the consent gate, so it never prompts — add a config with a url and no command, and start the server yourself:

{ "name": "attach", "url": "http://localhost:8000", "port": 8000 }

Within a single chat, approving once is enough; reuse-by-name skips the gate for later calls. Note that a server owned by a different chat is filtered out of the reuse candidates, so it re-prompts rather than reusing.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗