Show a PR marker once, at its first sighting, not on every re-stamp - #322
Merged
Conversation
The chat repeated the same "Opened PR #N" marker throughout a session — often dozens of times for one PR. The marker (added in #317) read a `pr-link` entry as "a PR was opened here". It isn't an event. Claude Code re-stamps a session's PR links in the metadata preamble it writes at the top of EVERY user turn, beside last-prompt / ai-title / mode / permission-mode. So one PR yields ~6 entries differing only in `timestamp`: 1163 entries for 195 real PRs across the corpus, 66% of them directly after `permission-mode`. The existing fold only caught CONSECUTIVE repeats, so the rest — separated by whole turns — each rendered a marker. Fixed on both paths: - `_entry_id`/`entryId` key a pr-link on its URL alone, dropping the timestamp. The re-stamps are re-records of one fact, so sharing an id lets the live tail's id-keyed merge collapse them to the first, and stops the redundant copies reaching the archive. - `buildItems` dedups by URL across the whole conversation rather than folding neighbours. This is the half that covers the archive/ended session view, which renders stored entries with no merge step at all. The first sighting is the right one to keep: measured across the corpus it lands 1-6 entries BEFORE the `gh pr create` tool_result that opened the PR (Claude Code stamps the link as soon as it sees the URL), so it marks where the PR really landed in the conversation. Verified against the corpus (107 transcripts holding pr-links): - markers rendered 1154 -> 195, exactly one per distinct PR, in first-seen order; no render still holds a duplicate URL. - zero non-PR display items differ, so nothing else moved. - py/js mirrors agree on entryId over all 1177 pr-link entries, both before and after (baseline 0 mismatches, after 0). - rendering the reported session's own transcript through itemsToHtml: 140 marks -> 17, with PR #80 going from 36 renders to 1. Suites: 804 python, 621 turma JS, 64 tunnel JS, all green. Also records the underlying lesson in the corpus-eval skill: count distinct values and each entry type's predecessor, because a high occurrences-to-facts ratio means state re-recorded on a schedule rather than an event to render in place.
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.
The bug
The chat repeated the same
↗ Opened PR #Nmarker over and over through a session — dozens of times for a single PR.Cause
The marker (added in #317) treats a
pr-linktranscript entry as "a PR was opened here". It isn't an event.Claude Code re-stamps a session's PR links inside the metadata preamble it writes at the top of every user turn — the same block that carries
last-prompt,ai-title,mode,permission-mode:So one PR produces ~6 entries differing only in
timestamp. Across the corpus that's 1163pr-linkentries for 195 real PRs (6×) over 107 transcripts, with 66% sitting directly afterpermission-mode.The existing fold only caught consecutive duplicates. The repeats are separated by whole turns, so nearly all of them slipped through and rendered a marker apiece.
Fix — both render paths
_entry_id/entryIdkey a pr-link on its URL alone, dropping the timestamp. The re-stamps re-record one fact, so sharing an id lets the live tail's id-keyed merge collapse them onto the first, and keeps the redundant copies out of the archive.buildItemsdedups by URL across the whole conversation instead of folding neighbours. This is the half that covers the archive / ended-session view, which callsbuildItemson stored entries with no merge step at all.Keeping the first sighting is deliberate and checked: across the corpus the first
pr-linkfor a URL lands 1–6 entries before thegh pr createtool_resultthat opened it (Claude Code stamps the link the moment it sees the URL), so it marks where the PR genuinely landed in the conversation.Verification
Corpus differential over the 107 transcripts containing pr-links, one snapshot for both sides:
entryIdover all 1177 pr-link entries — 0 mismatches, and 0 on the pre-existing baseline too (compared as parsed values, notJSON.stringify).itemsToHtml— 140 marks → 17, PR Agent Hub: add Kill button to the live-terminal top bar #80 going from 36 renders to 1.Markup and CSS for a marker are unchanged from #317, so this only changes how many are emitted.
Suites: 804 Python, 621 turma JS, 64 tunnel JS — all green.
Also
Records the general lesson in the
corpus-evalskill: count distinct values and each entry type's predecessor, not just occurrences. A high occurrences-to-facts ratio, or a near-constant preceding type, means the entry is state re-recorded on a schedule rather than an event to render in place — which is exactly what this bug was.Android renders no PR markers yet (existing
PARITY.mdgap); its entry there now warns that a per-entry port would reproduce this.