fix(claude): keep IDE-context envelopes out of first_message previews - #1265
fix(claude): keep IDE-context envelopes out of first_message previews#1265adammw wants to merge 27 commits into
Conversation
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
58dabd8 to
5193f4a
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
The VS Code extension often prepends a <ide_opened_file> or <ide_selection> wrapper directly onto a real prompt in the same user entry. PR kenn-io#1252 promoted standalone envelopes to hidden system metadata, but this mixed case (envelope + real prompt in one message) falls outside that strict standalone match by design, so the raw markup stayed in first_message and the visible transcript. Split a leading IDE-context envelope off from the rest of the message: the envelope becomes its own hidden system-metadata message (same ide_opened_file/ide_selection subtype as the standalone case), and the remaining real prompt becomes an ordinary user message. first_message and user turn counts are computed from the same firstMessageAndUserCount pass used before, so they now derive from the real prompt only. Bumps the parser data version to 75 so existing rows re-parse.
The IDE-envelope split gave both the synthetic hidden message and the real prompt the same source UUID. Pins and Recall evidence resolve messages by (session_id, source_uuid) and require the key to be unique: the duplicate made Recall endpoint resolution ambiguous (permanently revoking previously trusted evidence on the v75 reparse) and let pin restoration attach a pin to both rows. The prompt now keeps the entry's own uuid and the synthetic envelope row derives a distinct '<uuid>:ide-context' identity (empty stays empty). Full-rebuild pin migration in CopySessionMetadataFrom mapped pins across DBs by (session_id, ordinal) alone, so inserting the hidden envelope row shifted every later ordinal and moved pins onto unrelated rows. The copy now prefers the source_uuid natural key when the old row has one that resolves to exactly one fresh row, falling back to ordinal only for pins the uuid pass could not resolve. Records the mixed envelope-plus-prompt transcript shape in docs/internal/session-format-sources.md per the provider format provenance rule. Adds parser coverage for the split identities, a metadata-copy test for pins across the ordinal shift (including the legacy ordinal fallback), and a Recall evidence test that the split shape remaps endpoints instead of revoking provenance.
The cross-DB pin copy fell back to plain ordinal matching whenever a nonempty source_uuid did not resolve to exactly one fresh row. When the pinned message disappeared in the re-parse and another message took over its ordinal, the pin silently attached to unrelated content. The fallback now applies only to legacy pins with no source_uuid, or when the row at the old ordinal carries the pin's own uuid (the duplicate-uuid case, where the old coordinates pick which copy was meant). A nonempty uuid with no matching fresh row drops the pin. Adds coverage for a vanished uuid whose ordinal was reused and for a duplicated uuid resolved by old ordinal.
The uuid pin-copy pass verified uniqueness only in the fresh DB. When the old DB itself held duplicate source_uuids and the re-parse removed all but one, a pin on a removed duplicate matched the lone survivor and transferred to a message it was never on. The uuid now has to be unique on both sides; old-side duplicates go through the guarded ordinal fallback, which restores a pin only when the row at its old ordinal carries the same uuid and drops it otherwise. Adds coverage for a pin on a removed old-side duplicate (dropped) and on the surviving duplicate (restored at its ordinal).
splitLeadingClaudeIDEEnvelope runs after preprocessClaudeUserText, so a command envelope hidden behind an IDE-context wrapper bypassed command and system-reminder preprocessing: the revealed remainder was stored as raw command XML and could become first_message. The remainder now goes through preprocessClaudeUserText in both extraction paths, normalizing command XML (e.g. to /clear) and honoring the skip result for command envelopes that cannot be normalized. Covered by the already-pending v79 reparse in this change; no further data version bump is needed.
26c1417 to
af011ba
Compare
roborev: Combined Review (
|
Legacy pins without a source_uuid still restored by bare ordinal in the cross-DB copy. After an ordinal-shifting re-parse such as the IDE envelope split, the old combined prompt's ordinal belongs to the hidden envelope row, so a legacy pin silently attached to system metadata instead of the prompt. The ordinal fallback now also requires the candidate row to match the pinned row's role and content, both when the old DB has an empty source_uuid and when it predates the column entirely; pins whose identity cannot be established are dropped. Adds coverage for a uuid-less pin on a split combined prompt.
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
|
The pin reattachment parts look a bit overwrought here? |
|
Agreed... this has grown beyond the original bug. I’m going to keep the prompt as one message and remove the leading IDE wrapper in place. That avoids shifting message ordinals, so the pin reattachment changes can come out of this PR. |
Fixes #1238.
Claude IDE-context wrappers prepended to real prompts are now split into hidden
system metadata and a visible user prompt in both parser paths. The revealed
prompt goes through the normal user-text preprocessing, while the synthetic
envelope receives a distinct source identity. This keeps raw IDE markup out of
first_messageand the visible transcript without aliasing the prompt'sidentity. Data version 81 reparses existing sessions, and the Claude format
provenance records the observed mixed-envelope shape.
Because that reparse can rebuild message rows, SQLite and PostgreSQL pin
restoration now follow only unambiguous prior message identities. UUID-less and
duplicated-UUID rows may use a guarded ordinal, role, and content fallback;
vanished or ambiguous targets are dropped instead of moving or multiplying
pins. Explicit re-uploads preserve edited legacy pins by ordinal only while the
hidden-row layout through that ordinal is unchanged. In-place SQLite updates
fall back to guarded remapping when a pinned identity changes, orphan recovery
maps pins by the verbatim-copied ordinal, and PostgreSQL serializes pin
mutations with transcript replacement while keeping recorded and resolved
anchor ordinals distinct. Changed visible prompt digests also revoke stale
Recall provenance.
The parser intentionally handles only well-formed leading
ide_opened_fileandide_selectionwrappers with a non-empty remainder; malformed or mid-messagemarkup remains untouched. The main review areas are the split logic in
internal/parser/claude.go, conservative pin remapping ininternal/db/messages.go,internal/db/orphaned.go, andinternal/postgres/push.go, and the explicit upload adapter ininternal/server/upload.go.