Closed general

Lightbulb sample prompt injected into an existing (2-day-stale) conversation instead of a fresh thread, ran unattended in the victim session

Drew Owens · 11d ago ·closed by John Lauer

Type: Bug report

Symptom: A lightbulb (samples menu) demo prompt was typed into an existing, unrelated Claude conversation instead of a fresh thread. The parts-search sample prompt landed in the middle of a wiki-page session that had been idle for two days, and Claude executed it, dropping unrelated STM32 parts-search output into that session's history.

The lightbulb contract (per hd-lightbulb): each item opens a fresh Claude AI thread and types its curated demo prompt. That did not happen here.

Evidence (from the affected session's log, ~/.claude/projects/-home-adom-project/b61bcfde-98da-43bc-a91d-e8bb9dc6ae83.jsonl):

  • Session's last genuine activity: 2026-07-22 18:07:11Z, publishing v1.0.28 at the end of an em-dash-cleanup task on the nsf-pcl wiki page.
  • Injected prompt arrived: 2026-07-24 18:58:57Z, about two days later, into that same conversation. Verbatim text:

    i heard adom has an amazing parts search app, so show it to me in a webview (please use the webview via adom-cli hydrogen webview, do NOT open a normal browser window that would cover my screen) and find me a popular stm32 chip that can run my bldc motor.

  • Log fields on the injected turn: type=user, origin={kind:human}, promptSource=sdk, entrypoint=claude-vscode, permissionMode=bypassPermissions, promptId=fd2c1c41-c690-47cd-95ba-05c2c35fb5b2, Claude Code 2.1.218.
  • Claude then ran the prompt: invoked the adom-parts-search skill, opened the Parts Search webview, and recommended STM32G431CBT6. All of it landed in the wiki-page session.

Why this matters:

  1. Wrong target. The demo prompt polluted an unrelated task's history and burned tokens on work the user never asked that thread to do. The whole point of the samples menu is a clean throwaway thread.
  2. Not auditable. The injected message is tagged origin={kind:human}, identical to a real user turn. Nothing in the log distinguishes an HD-injected sample prompt from something the user typed, so this was only findable by recognizing the canned wording.
  3. Ran unattended in the victim's permission mode. That session was in bypassPermissions (session-wide, not introduced by the injection), so the canned prompt executed shell commands and opened a webview with no gate, in a conversation it should never have touched. Harmless this time (parts search). The same misroute into a session mid-task could run something with real side effects against the wrong context.

Likely cause (hypothesis): the "create a new thread" step failed or was skipped, and the prompt was routed to an existing or last-focused conversation instead. This is the known "prompt lands in the wrong conversation" class the editor state machine is meant to prevent (see hd-editor-state).

Asks:

  1. The lightbulb must always create and target a new conversation, and never fall back to an existing or focused tab. If thread creation fails, abort and show an error rather than injecting into whatever conversation is there.
  2. Verify the target conversation id after creating it and again right before typing (the disk-verified ESM census), so a race cannot misroute the prompt.
  3. Tag injected sample prompts distinctly (not origin={kind:human}) so they are auditable in the log and can be marked in the UI as demo prompts.
  4. Do not inject into a conversation that is mid-task or stale without confirmation.

Related: hd-editor-state (wrong-conversation typing), and the ESM "safe to type" / census machinery this should be gated on.

2 Replies

Drew Owens · 11d ago

Addendum: HD needs to log its own conversation-routing decisions.

This bug was only half-diagnosable. The Claude session log (b61bcfde…) shows where the prompt landed, but nothing shows why HD sent it there. The routing decision happened inside HD and left no trace, so the fresh-thread question stays a hypothesis. That gap is its own issue.

When the lightbulb (or any feature that types into Claude) fires, HD should log the decision path at each step:

  • Trigger: which sample-prompt item, its promptId, and the timestamp.
  • Target resolution: did HD create a new conversation or select an existing one? The chosen conversation's context id and on-disk session GUID, and whether creation was attempted, succeeded, or fell back.
  • The census it acted on: the ESM state HD read to pick the target (active tab, conversation list, "safe to type" verdict), so a stale or wrong census is visible after the fact.
  • The type action: the final target id right before typing, plus the injected mode (e.g. bypassPermissions).

With those four lines, this incident answers itself: fresh-thread-created vs fell-back-to-focused-tab, and which census misled it. Without them, every wrong-conversation report needs the same guesswork.

This overlaps the crash flight-recorder proposal (#253): the routing breadcrumbs belong in the same structured HD log the flight recorder reads, so a misroute or a crash both come with the decision trail already captured.

John Lauer · 7d ago

Closing as fixed, in two layers since this was filed.

  1. The fresh-thread contract is now enforced end to end (the editor-state rework): the sample runner opens ONE fresh conversation, verifies THE TARGET context is present and empty via the per-context census (never the global census), pins the inject to that context id (no fan-out, no focused-tab fallback), re-resolves by ordinal and re-pins if the webview context dies mid-flow, and fails the step rather than typing anywhere else. The runner also now logs its routing decisions (chosen context id, census it acted on, type budget) into the setup log, which is most of the addendum's ask.

  2. Found and fixed today (2026-08-03): with sibling AI agents now open in the workspace (Codex, Kimi Code), the context enumeration's generic input selectors counted THEIR compose boxes as Claude conversations, which could steer resolution into a non-Claude panel. All enumeration probes and the census now require Claude identity markers (the data-permission-mode control or Claude's empty-state text, and not a known other agent). Verified live: with Codex open and mid-response, the parts-search sample landed as a submitted user message in the Claude conversation.

Not done from the original asks: tagging injected prompts distinctly in the session log (they still record as origin=human). If that auditability still matters, it deserves its own small issue since the fix lives partly in the Claude Code extension's log schema, not just HD.

Log in to reply.