UserPromptSubmit queue auto-merges sequential inputs into single message; under bypassPermissions this enables silent execution of stale-clipboard templates (PHI-adjacent risk)

Resolved 💬 3 comments Opened May 2, 2026 by mjrgman Closed May 6, 2026

Summary

Claude Desktop's input queue (type: queue-operation, operation: enqueue) merges two or more sequential user inputs that are typed/pasted in close succession into a single role: user message before transmission. There is no UI indication of the merge, no separator inserted, and no warning. When one of the merged inputs is a stale clipboard / snippet template that the user did NOT intend to send in the current context, this can cause silent destructive execution under defaultMode: "bypassPermissions" + skipDangerousModePermissionPrompt: true.

In healthcare and other PHI-adjacent workflows, the silent execution can affect production codebases (e.g., automatically writing .claude/launch.json and starting dev servers via the Claude Preview MCP preview_start tool inside a clinical EHR repo).

Reproduction

Environment:

  • Claude Desktop, version 2.1.111 (Windows 11)
  • Account-level Claude Preview MCP connector enabled
  • defaultMode: "bypassPermissions" + skipDangerousModePermissionPrompt: true (typical "trusted machine" config)

Steps:

  1. Have a stale clipboard / snippet entry containing a template-style prompt that references a Claude Code tool. Example template (this is a real one from the Claude Preview onboarding pattern):

```
Detect my project's dev servers and save all their configurations to .claude/launch.json, then ask which ones to start.

``json
{
"version": "0.0.1",
"configurations": [
{ "name": "<server-name>", "runtimeExecutable": "<command>",
"runtimeArgs": ["<args>"], "port": <port> }
]
}
``

Use runtimeExecutable for the command (e.g. "yarn", "npm", "node", "python") and runtimeArgs for its arguments. Call preview_start for each server the user wants to run.
```

  1. In a Claude Code session, type a short unrelated question (e.g., is it complete) and submit.
  1. Within ~30 seconds — before the assistant has fully responded — paste or type the template into the input box and submit.
  1. Expected: Two separate role: user messages reach the assistant, in order. OR: a single message that visibly indicates two inputs were combined.
  1. Actual: A single role: user message arrives at the assistant containing both texts concatenated with a single \n separator. The assistant has no way to distinguish "user typed both intentionally as one message" from "two sequential paste events got merged".

Evidence (from session jsonl transcripts)

In the affected session (sanitized session ID; full path in the user's local .claude/projects/ tree):

// At 22:54:25 — first enqueue
{"type":"queue-operation","operation":"enqueue","timestamp":"2026-04-24T22:54:25.988Z","content":"is it complete"}

// At 22:54:53 — second enqueue (28 seconds later)
{"type":"queue-operation","operation":"enqueue","timestamp":"2026-04-24T22:54:53.621Z","content":"Detect my project's dev servers and save all their configurations to .claude/launch.json..."}

// At 22:55:01 — single user message sent containing BOTH
{"type":"user","timestamp":"2026-04-24T22:55:01.916Z","permissionMode":"bypassPermissions","entrypoint":"claude-desktop","version":"2.1.111","message":{"role":"user","content":"is it complete\nDetect my project's dev servers and save all their configurations to .claude/launch.json..."}}

Forensic pattern across multiple sessions

The same template appeared as role: user content in 15+ Claude Code session transcripts over 4 weeks (2026-03-27 to 2026-04-24) across 7+ project directories. In sessions where the project had package.json etc., the user used the template intentionally for legitimate dev-server detection. In sessions without those markers (manuscript / docs / data folders), the template appeared via the queue-merge pattern described above and triggered Claude to write .claude/launch.json and call preview_start silently.

The ~/.claude/launch.json file on the affected machine was last modified 2026-04-19 — populated with cwd entries pointing at multiple project directories, including a clinical EHR repository. Each of those entries was almost certainly written by a Claude Code session under bypassPermissions after a queue-merge incident, not by the user explicitly.

Impact severity

Medium-High. The silent execution path is gated behind bypassPermissions (which is a user-chosen setting), but the failure mode is invisible to the user and can affect production / regulated codebases:

  • .claude/launch.json written without confirmation in ANY project directory the queue-merge happens to land in
  • preview_start MCP tool called against arbitrary cwd inferred from the polluted prompt
  • Possible port binding exposing data to local network (depending on the dev server config) — high stakes for healthcare PHI codebases
  • No visual log of the merge in the TUI; user can only discover post-hoc by reading session jsonl

Suggested fixes

In rough priority order:

  1. Fix the queue merge. When two enqueue events have different content, send them as separate user messages, not merged. If they MUST be merged for batching reasons, insert an explicit separator (e.g., --- next input ---) so the assistant can recognize the boundary, AND surface a "2 inputs merged" indicator in the TUI.
  1. Pre-submit warning under bypassPermissions. When defaultMode: "bypassPermissions" is active AND the queue is about to merge ≥2 inputs into one user message, prompt the user once: "About to send 2 queued inputs as one message: [preview]. OK?" with explicit cancel.
  1. Per-tool confirmation override. Even under bypassPermissions, allow users to mark certain MCP tools (e.g., preview_start, Write to .claude/launch.json) as "always confirm regardless of permission mode". Prevents silent execution from stale templates.
  1. Document the queue-merge behavior. If it's intentional product behavior, document it in the Claude Code TUI docs so users know to wait for response before typing the next prompt. Currently undocumented.

Workaround already deployed (for reference)

User has installed a UserPromptSubmit hook (~/.claude/hooks/pollution-quarantine.py) that:

  • Layer 1: Blocks any prompt containing the exact fingerprint of two known templates (the dev-server detection template + a tool-suppression template seen in 24 occurrences across 8 workspaces).
  • Layer 2: Structural detector blocks unknown variants based on code-block ratio + imperative verbs + tool name mentions.
  • Layer 0 (added per project context): Auto-allows the dev-server template if cwd contains a dev-server-relevant config file (package.json, pyproject.toml, Cargo.toml, go.mod, etc.), allowing legitimate use to pass silently while blocking accidental misuse in non-project directories.
  • Logs every block + auto-allow to ~/.claude/hooks/pollution-quarantine.log for auditability.

The hook already caught a novel "Build my project's test runners and save all their configurations to .claude/test-config.json" variant via the structural detector, validating the multi-layer design.

This works around the symptom but does NOT address the root cause (queue merge + bypassPermissions silent execution). A product-level fix is needed.

Related observations

  • A second polluted template — "CRITICAL: Respond with TEXT ONLY. Do NOT call any tools..." — was found in 24 occurrences across 8 workspaces over 9 days. Different failure mode (silent denial-of-functionality instead of silent execution) but same root cause (clipboard / snippet template arriving via queue-merge under bypassPermissions).
  • Local forensic report (~25 KB) is available on request with full timeline, all transcript IDs, and artifact-register.

View original on GitHub ↗

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