Repository navigation
fix(feed): suppress orphan ghosts with title-gen / predict-next-prompt shape - #54
Merged
Merged
Conversation
…t shape
Diagnosed via debug bundle 2026-05-07T08-26-35-212-5d948ab5 that 7 stale
2-10-word fragments were parked permanently at the bottom of a Claude
session's feed:
- Each fragment was a g-msg_*-0 ghost rendered by selectMergedEntries.
- proxy-semantic.json history showed each one as a sub-second turn
(188-602ms, 12-41 chars). The same bundle's real assistant turns
were 808ms-37s, 76-6579 chars.
- 0 of 23 flows had attribution='ignored' — the existing sidecar
predicate in ClaudeProxyAdapter (c8c2623, body-shape based) never
fired for these calls.
Root cause: Claude Code routes title-gen, branch-name-gen, and the new
"predict the user's next prompt" feature through the same /v1/messages
endpoint as real turns, but does NOT write them to its JSONL rollout.
ClaudeProxyAdapter sees them as live flows and emits turn_started /
text_delta / turn_completed semantic events; ghostsFromSemanticTurn
mints a ghost; reconcileUpstream never matches because there's no JSONL
entry to match against; the orphan timeout fires; mergeWithUpstream
appends the ghost to the tail of the feed forever.
The body-shape predicate misses the predict-next-prompt feature because
those calls include the full conversation history (messageCount > 3 so
the compound budget signal fails) and use a system prompt not in the
4-entry SIDECAR_SYSTEM_PROMPT_PREFIXES list.
Fixing the predicate properly requires capturing a live request body
to extend the prefix list, which we don't have here. This commit
addresses the symptom by detecting the SHAPE of leaked sidecar turns
(assistant role, single text block, ≤200 chars) at render time and
dropping them before mergeWithUpstream sees them.
Trade-off: a genuine but very brief assistant orphan (e.g. "Done.")
whose JSONL never committed would also be suppressed. Accepted because
orphans are already an error path and the user-visible permanent
clutter is the bigger UX harm.
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.
Summary
Stops Claude Code's auxiliary API calls (title-gen, branch-name-gen, "predict the user's next prompt") from leaking 2-10-word fragments to the bottom of the feed where they accumulate over the whole session.
Forensic root cause (debug bundle
2026-05-07T08-26-35-212-5d948ab5)data-entry-uuid=\"g-msg_*-0\"(orphan ghosts).proxy-semantic.jsonhistory: 188-602ms, 12-41 chars (vs real turns 808ms-37s, 76-6579 chars).attribution: 'ignored'— the existing sidecar predicate (c8c2623) never demoted any of these calls.The body-shape predicate misses the predict-next-prompt feature because those calls include the full conversation history (
messageCount > 3→ compound predicate fails) and use a system prompt not in the 4-entrySIDECAR_SYSTEM_PROMPT_PREFIXESlist. Fixing it properly needs the actual request body to extend the prefix list — which we don't have.Fix
Add
ghostHasSidecarShape(ghost)toselectMergedEntries. Filter out orphan ghosts that look like title-gen / predict-next-prompt output:The bundle's longest sidecar text was 41 chars; shortest real assistant turn was 76 chars — so 200 is a comfortable margin.
Trade-off
A genuinely brief assistant orphan (e.g. "Done.") whose JSONL never committed would also be suppressed. Accepted because orphans are already an error path (JSONL not landing means upstream Claude Code crashed), and permanent visual clutter from 6+ stale title fragments is a daily UX harm whereas a hidden 4-char crash response is rare and unrecoverable either way.
Future work
When we capture a live request body for the predict-next-prompt feature, extend
SIDECAR_SYSTEM_PROMPT_PREFIXESand the body-shape predicate inclaude-code-headless. At that point this renderer-side filter becomes redundant and can be deleted.Verification
npm run build:app✅🤖 Generated with Claude Code