UserPromptSubmit queue auto-merges sequential inputs into single message; under bypassPermissions this enables silent execution of stale-clipboard templates (PHI-adjacent risk)
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:
- 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.
```
- In a Claude Code session, type a short unrelated question (e.g.,
is it complete) and submit.
- Within ~30 seconds — before the assistant has fully responded — paste or type the template into the input box and submit.
- Expected: Two separate
role: usermessages reach the assistant, in order. OR: a single message that visibly indicates two inputs were combined.
- Actual: A single
role: usermessage arrives at the assistant containing both texts concatenated with a single\nseparator. 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.jsonwritten without confirmation in ANY project directory the queue-merge happens to land inpreview_startMCP 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:
- 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.
- 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.
- Per-tool confirmation override. Even under bypassPermissions, allow users to mark certain MCP tools (e.g.,
preview_start,Writeto.claude/launch.json) as "always confirm regardless of permission mode". Prevents silent execution from stale templates.
- 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.logfor 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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗