Stop image prompts from posting a second user bubble - #43
Merged
Conversation
Pi stores an image-carrying prompt as a text block plus one block per image. Reading that entry back through the generic content renderer appended an "[Image]" line per image, so the current prompt no longer matched the text Jasmine sent. The run then treated its own prompt as a new user turn: the bubble was painted twice live and persisted twice. The same match links the prompt to its Pi entry, so an image turn was also left unbranchable. Read user entries from their text blocks alone. Attachments already travel on the message row, so the placeholders carried no information the bubble needed. The runtime suite only ever ran image attachments through the vision-capability rejection path, so no case reached the identity match with images attached. The new case covers the persisted rows, the live snapshot, and the prompt link. Also drop the Codex review step from the change workflow; CI green on the head commit is now the bar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
Sending a message with image attachments painted the user turn twice: once as the bubble with its thumbnails, then again as a copy carrying the
Attached local paths:block and one[Image]line per image. The copy was persisted, so it survived a reload.Why
Pi stores an image-carrying prompt as a text block plus one content block per image.
userTextFromSessionEntryread that entry through the generic content renderer, which renders an image block as[Image], so the entry's text came back as the prompt text plus a placeholder line per image.The runtime identifies its own prompt among the new session entries by exact text match —
sessionEntriesToMessagesskips that entry because the caller already has a row for it, andlinkCurrentPromptEntryuses the same match to link the message to its Pi entry. With images attached the match never succeeded, so the run emitted its own prompt as a new user turn (live and persisted), and the turn was left without asessionEntryId, so it could not be branched from.Fix
Read user entries from their text blocks alone. Attachments already travel on the message row, so the placeholders carried nothing the bubble needed. Assistant and tool-result rendering is untouched.
Test
tests/unit/pi-runtime-equivalence.mjsonly ever ran image attachments through the vision-capability rejection path, so nothing reached the identity match with images attached. The new case sends a two-image prompt to a vision-capable mock provider and asserts the persisted rows, the streamed live snapshot, and the prompt link.Verified red before the fix (
['user','assistant']instead of['assistant'], and auserrole in the live snapshot), green after.Checks run
npm run buildnpm run test:unit— all suites pass exceptpi-remote, which fails on a stale localruntime.tar.gzleft over from another branch (its gitignored artifact hash predatesmain'sartifact.json); unrelated to this change and not reproducible on a clean checkoutnpm run harness:checkFull E2E is CI's to confirm.
Also
AGENTS.mdno longer routes changes through@codex review; green CI on the head commit is the bar.🤖 Generated with Claude Code