Compaction integrity bugs: dangling logicalParentUuid + title resolver prefers lastPrompt over firstPrompt

Status Fixed / completed
Reported on v2.1.112
Maintainer reply None cached
Activity 3 comments · opened Apr 17, 2026 · closed Apr 17, 2026

Two related bugs in how sessions are recorded and displayed across compaction boundaries. Version: 2.1.112 (VSCode/Antigravity extension).

Both observable across every auto-compacted session I have (20+ sessions in one project). Filing together because they're in the same neighbourhood of code (session recording + the lp / fetchSessions metadata parser) and because they interact with each other and with #48937.

---

Bug 1: Compaction stitch has a dangling logicalParentUuid

What happens

Every time a conversation auto-compacts (or /compact is invoked), the JSONL gets a "stitch" system message written with this shape:

{
  "type": "system",
  "content": "Conversation compacted",
  "parentUuid": null,
  "logicalParentUuid": "<some-uuid-X>",
  "compactMetadata": { "trigger": "auto", "preTokens": 181304, ... }
}

The uuid <X> pointed to by logicalParentUuid never appears as a message's own uuid anywhere on disk.

Evidence

Reproducible trace in my project (~/.claude/projects/-home-juraj-phusion2/):

  • Session a9a7936a-621d-4329-b699-24a22ed6badb.jsonl:
  • L652 (last pre-compact message): type=user, uuid=3b2a562e-..., timestamp=2026-04-08T14:02:56
  • ~2-minute gap
  • L655 (stitch): type=system, uuid=4c4ea619-..., parentUuid=null, logicalParentUuid=84845ffd-d22a-45d4-bc3b-768e06b92bab, timestamp=2026-04-08T14:05:07
  • Grep 84845ffd across all 60+ JSONLs in the project: appears only as logicalParentUuid, never as uuid.
  • The compaction subagent JSONL (a9a7936a/subagents/agent-acompact-1947ebe65e892c99.jsonl) has its own line-1 logicalParentUuid=84845ffd-... — same dangling uuid.
  • 20+ sibling sessions forked from this compaction moment all inherit the same dangling reference.

This pattern holds for every compaction boundary I've inspected across multiple sessions.

Why it's wrong

logicalParentUuid is documented (by its name and use in getTranscript) as a cross-boundary parent pointer. A dangling pointer violates the invariant that any uuid referenced in a JSONL resolves to a concrete persisted message.

Direct downstream consequence: the secondary-issue fix proposed in #48937 (make forkSession's getTranscript walk logicalParentUuid across compaction boundaries so forks contain the original pre-compact history) can't be implemented as stated — the pointer doesn't resolve, so the fork still wouldn't reach pre-compaction messages. Any tooling that walks the conversation graph across compaction boundaries (fork history, session linearization, transcript archaeology, the CLI's branch.ts-style rewrite) has to special-case this.

Expected

Either:

  • (a) Persist the trigger/control message that currently has its uuid pre-allocated-but-never-flushed, OR
  • (b) Set logicalParentUuid on the stitch to the actual last pre-compact uuid (e.g. 3b2a562e-... in the trace above).

(b) is cleaner — it keeps ephemeral /compact invocations out of the persisted transcript while still giving chain-walkers a resolvable pointer.

---

Bug 2: Title resolver prefers lastPrompt over firstPrompt, title drifts with message size

What happens

Session title (shown in sessions list / tab) is resolved via (minified, from lp(K,V,j) and the inline variant in fetchSessions):

z = customTitle || aiTitle || lastPrompt || summary || firstPrompt(head)

customTitle, aiTitle, lastPrompt, summary are tail-scanned (last 64KB). firstPrompt is head-scanned (first 64KB) via a different extractor (Jq4/gr).

Consequence: whether a session's title shows the original user prompt (stable identity) or the latest user prompt (drifts every turn) depends entirely on whether a last-prompt metadata entry happens to land in the tail 64KB at resolution time. That in turn depends on session size and how interleaved the metadata writes are — entirely an implementation detail the user has no control over.

Evidence

Three concrete sessions from the same project, all without custom-title or ai-title (so the fallback chain runs):

| session | total lines | last-prompt entries | in tail 64KB? | resolved title |
|---|---|---|---|---|
| a9a7936a-... | 1740 | 5 | no | "Let's start with @CLAUDE…" (original first prompt, stable) |
| 7c569713-... | 2478 | 51 | yes (3) | "I staged what I think should go into the commit, can you check?" (latest prompt, drifts) |
| 43c400a9-... | 2513 | 67 | — (has 91 custom-title in tail, customTitle wins) | "btw: btw you're sure it's not on the CLI side? …" |

The a9a7936a and 7c569713 difference is purely file-layout luck. Neither session did anything explicit to pick its title style.

Why it's wrong

  • A session's "identity" in the sessions list should be stable. Users recognize conversations by what they started — not by what they most recently typed.
  • lastPrompt changes on every user message. It's a live-tail signal, not an identity.
  • The current ordering is silently non-deterministic from the user's perspective: two sessions with similar usage but different sizes resolve to completely different kinds of titles, for no principled reason — just which side of the 64KB tail window the metadata landed on.

Interaction with #48937

The primary fix for #48937 (append a last-prompt metadata entry on fork so the session becomes discoverable by listSessions) is correct and necessary. But it also causes Bug 2's drift on any session that has enough last-prompt entries to saturate the tail 64KB. The two fixes for discovery and for title-stability should be separated: last-prompt is good discovery metadata; it's bad title data.

Expected

Either:

  • (a) Reorder the priority chain so firstPrompt wins over lastPrompt:

customTitle || aiTitle || firstPrompt(head) || summary || lastPrompt

  • (b) Remove lastPrompt from the title chain entirely. It's more of a "last activity" / tooltip-level signal than a title.

Either gives deterministic, size-independent behavior.

---

How to verify

For Bug 1: take any auto-compacted session's JSONL, find the compact stitch (type:"system", parentUuid:null, logicalParentUuid set), grep that logicalParentUuid across all JSONLs — it never resolves to a real uuid.

For Bug 2: open two sessions without custom-title/ai-title entries, one small (last-prompt entries NOT in tail 64KB) and one larger (last-prompt entries IN tail 64KB). First shows original prompt as title; second shows latest prompt as title. No user action distinguishes them.

View original on GitHub ↗

3 Comments

github-actions[bot] · 6 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/46603
  2. https://github.com/anthropics/claude-code/issues/32150
  3. https://github.com/anthropics/claude-code/issues/33898

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

ojura · 6 months ago

Thanks bot. Both halves of this report do overlap with existing issues. Cross-referencing:

Bug 1 (dangling UUID on compaction stitch) — covered by #46603 with a more detailed reproduction and structural JSONL analysis (11595 lines, 10 broken references, 8736 orphaned messages). Same root cause: a uuid referenced by a post-compaction message is never persisted to the JSONL. What I observed as a dangling logicalParentUuid on the compaction-stitch system message, and what #46603 observed as dangling parentUuid causing --resume to only render ~17% of the conversation, are two surfaces of the same phantom-uuid bug in the compaction writer.

Bug 2 (title drifts because lastPrompt wins) — #32150 already covers this with the same diagnosis (extension appends last-prompt after the session, and the sidebar ends up preferring it). #33898 is the narrower case of /rename titles being lost across compaction. The mechanism I added — lp() / fetchSessions only scan the last 64KB of the JSONL, so whether customTitle wins depends on whether its entry falls inside that tail window — explains why #32150's "last entry wins" observation manifests for some sessions but not others.

My new contribution here was the 64KB tail-window explanation for Bug 2 and the Thread B observation (19 orphaned fork-duplicate sessions from a single compaction moment). Since the root bugs are already filed, closing this as a duplicate of #46603 + #32150. Adding the 64KB-window note as a comment on #32150 separately.

github-actions[bot] · 5 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.