claude-code - 💡(How to fix) Fix Phantom session rows + unauthenticated localhost bridge accepts writes from any local process

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When using a custom --permission-prompt-tool, Claude Desktop's session list and floating approval stack can be populated by any local process via the local HTTP bridge at 127.0.0.1:7437/agent-state — no authentication, no validation that the posted agent_id maps to a known cliSessionId. The resulting "phantom" rows persist after the originating process exits, are mis-grouped under an "Approval" page, and fall back to rendering the raw JSON envelope as the row subtitle.

Root Cause

When using a custom --permission-prompt-tool, Claude Desktop's session list and floating approval stack can be populated by any local process via the local HTTP bridge at 127.0.0.1:7437/agent-state — no authentication, no validation that the posted agent_id maps to a known cliSessionId. The resulting "phantom" rows persist after the originating process exits, are mis-grouped under an "Approval" page, and fall back to rendering the raw JSON envelope as the row subtitle.

Code Example

curl -X POST -H 'Content-Type: application/json' \
  -d '{"event":"approval_request","request_id":"probe-1","tool":"","prompt_b64":"","agent_id":"unknown","pid":1,"cwd":"/tmp","branch":"","framework":"claude-code"}' \
  http://127.0.0.1:7437/agent-state
RAW_BUFFERClick to expand / collapse

Summary

When using a custom --permission-prompt-tool, Claude Desktop's session list and floating approval stack can be populated by any local process via the local HTTP bridge at 127.0.0.1:7437/agent-state — no authentication, no validation that the posted agent_id maps to a known cliSessionId. The resulting "phantom" rows persist after the originating process exits, are mis-grouped under an "Approval" page, and fall back to rendering the raw JSON envelope as the row subtitle.

Environment

  • Claude Desktop 1.7196.3 (Electron 41.5.0)
  • claude-code CLI 2.1.142
  • macOS 25.4.0 (arm64)
  • Permission prompt configured via --permission-prompt-tool stdio / external shim

Observed

A row in the session list:

  • Status: orange !, label Stalled
  • Title: tmp
  • Subtitle: literally {"session_id":"qa3","cwd":"/tmp"} (raw JSON)

…paginated under a header reading "4/5 Approval" alongside three Working sessions that have nothing to do with approvals. The same orphan also surfaces as a floating top-right approval card with the JSON as its ACTION REQUIRED body.

Defects (6 stacked)

#DefectSeverityCategory
B127.0.0.1:7437/agent-state accepts unauthenticated POSTs; any local process can inject session-list entries. No agent_idcliSessionId validation.P1 / local-securityTrust boundary
ARaw JSON envelope rendered as row subtitle when tool/title fields are empty/missingP1Fallback rendering
CPhantom rows counted in "N/M Approval" paginationP2Classifier
DTitle falls back to cwd basename (tmp) when no session label existsP2Classifier
ESame orphan drives both the list row AND the floating card; no dedupe across surfacesP2Layout / state
FNo GC for orphans whose origin PID is dead; rows persist indefinitelyP3Persistence

Reproduction (defect B)

From any local process running as the user:

curl -X POST -H 'Content-Type: application/json' \
  -d '{"event":"approval_request","request_id":"probe-1","tool":"","prompt_b64":"","agent_id":"unknown","pid":1,"cwd":"/tmp","branch":"","framework":"claude-code"}' \
  http://127.0.0.1:7437/agent-state

Returns HTTP 204 No Content. A new row immediately appears in the Claude Desktop session list with title tmp and the raw payload as its subtitle. No CLI session was ever started; no cliSessionId matches agent_id="unknown"; the originating PID (1) is not a real claude-code process.

Expected

  1. Bridge should require a per-launch secret (env var / header / unix socket) and reject POSTs that don't present it.
  2. Bridge should verify the posted agent_id matches a known cliSessionId for the current user session; reject otherwise.
  3. Renderer should never stringify the IPC envelope into user-facing chrome. Fall back to Unknown request and put raw payload behind a "Show details" disclosure.
  4. Session list should separate Needs attention from Working rather than counting them under a single N/M Approval header.
  5. Orphan reaper: rows whose origin PID is dead and that have no decision after T should auto-quarantine or auto-deny.

Impact

Local-only (attacker needs code execution as the user), but allows arbitrary spoofing of Claude Code's approval UI. A malicious local process could surface plausible-looking approval prompts to trick the user into clicking through. Also a quality-of-life regression — legitimate session lists become cluttered with phantom rows that can't be cleared without a desktop restart (no admin API; LevelDB-backed in-memory state).


Filed by Kevin Segel — https://www.linkedin.com/in/kevinsegel/

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Phantom session rows + unauthenticated localhost bridge accepts writes from any local process