Debug Repeated Prompt Review Sessions

This session log repeats the same prompt 13 times. Investigate why that is. It seems to be a "review" marked session? https://entire.io/gh/entirehq/entire.io/session/019ee087-16f3-70c3-b877-e20598add357 you have planetscale mcp access.
I've traced the whole chain. Here's what's happening.
What you're looking at
Session 019ee087-16f3-... is indeed a review session — agent Codex, model gpt-5.5, started by dipree on 2026-06-19 15:38:27, with the canonical prompt "Review the current branch changes and report actionable findings…". It reports checkpoint_count = 13.
But it only legitimately owns 2 checkpoints (90b5447afbc7, faf8a96b581e at 20:29 and 20:33, branch dip/trail-types). The other 11 belong to completely unrelated sessions:
| Owning session | Agent | Prompt | # checkpoints |
|---|---|---|---|
019ee088-250c… | Pi | "Create a PR for the Gemini config here." | 2 |
019ee116-f0f0… | Pi | "Merge latest main and resolve conflicts." | 9 |
019ee087-16f3… (this one) | Codex | review prompt | 2 |
The review session leaked into all 13 because every one of those 13 checkpoints carries this session id inside its session_ids JSON array — and so does a second, near-duplicate review session 019ee087-16ab-… (also Codex, identical review prompt, started the same second, 72 ms earlier). Both review sessions are pinned into every checkpoint's session_ids.
Root cause (two layers)
1. A stale review session leaks into every subsequent checkpoint's session_ids.
session_ids on a checkpoint is copied verbatim from the commit/transcript metadata's sessions array (api/src/lib/github.ts:732, checkpoint-payload-hydrator.ts). Nothing filters out sessions that didn't actually contribute to that checkpoint — dead_session_indices is computed but the raw session_ids list is left whole. The two review sessions opened at 15:38:27 stayed "open" in the local transcript's session list and were stamped onto every checkpoint created over the next ~5 hours, across unrelated branches (add-gemini-entire-config, dip/trail-types, dip/drop-trail-labels) and unrelated agents (Pi). That's why both review sessions show checkpoint_count = 13 while one owns 2 and the other owns 0.
2. The session-log merge amplifies the leak into 13 repeated prompts.
The session page queries checkpoints with cp.session_ids LIKE %sessionId% (cache.ts:3711), so all 13 leaked checkpoints are pulled in. mergeSessionTranscript() (session-transcript.ts:81-152) then loops over each checkpoint, finds this session inside cp.sessions, and appends its transcript slice. Each of those 13 slices begins at the same point (transcript_lines_at_start = 58, checkpoint_transcript_start = 58) and contains a copy of the review prompt message. Messages are deduped by msg.id — but because each checkpoint's slice gives that prompt a distinct id, the dedupe misses and the review prompt gets emitted once per checkpoint → 13 identical prompt blocks in the log.
Two distinct bugs worth filing
- Session-id leakage: a long-lived/"open" session contaminates the
session_idsof every later checkpoint. The fix belongs at ingest — only include a session in a checkpoint'ssession_idsif it actually contributed in that checkpoint's transcript window (the same signalcomputeDeadSessionIndicesalready derives), or filter dead indices out ofsession_idsbefore persisting. - Duplicate review session: the review was recorded as two near-identical sessions 72 ms apart (
…16aband…16f3), suggesting a double-trigger / double-insert when a review is launched.
Either fix alone reduces the symptom: filtering session_ids would shrink the 13 down to the 2 real review checkpoints; fixing the merge dedupe would collapse the repeated prompt even if the leak remained.
Want me to dig into the ingest path to pin down exactly where the dead/non-contributing sessions should be filtered, or look at the review-trigger code to find the double-insert?
I've found the root cause, and it spans both repos. Here's the full chain.
Why the session log repeats the prompt 13×
It's two bugs stacking — a fan-out that creates duplicate review sessions, and a leak that staples those sessions onto every unrelated commit.
1. The review created two sessions (RunMulti fan-out)
On the review-profiles branch, entire review runs RunMulti (cmd/entire/cli/review/run_multi.go), which fans out multiple reviewer agents. Each reviewer is launched as its own agent process and gets its own agent-generated session id. That's why you see two near-identical Codex/gpt-5.5 review sessions started 72 ms apart (…16ab and …16f3), both carrying the identical "Review the current branch changes…" prompt but with different generated names ("…Trail Type Rollout and Gemini Changes" vs "…Trail Type Rollout Migration").
2. The leak: review sessions get folded into every commit in the worktree
At commit time, ManualCommitStrategy.PostCommit calls findSessionsForWorktree (cmd/entire/cli/strategy/manual_commit_session.go:137):
No filter on session Kind and no filter on ended-state. Every session living in that worktree's state store is then written into the checkpoint's Sessions array. The only skip, in the PostCommit loop (manual_commit_hooks.go:981 and :995), is:
A review session is read-only ("Do not make code changes") — it never touches files, so it never produces a shadow branch and never becomes FullyCondensed. It therefore fails this skip even after it ends, and gets folded into every subsequent commit in that worktree. Over the ~5 hours those two review sessions lingered, the user's other work (the "Create a PR for the Gemini config" session, then nine "Merge latest main" commits across dip/trail-types / dip/drop-trail-labels) all got stamped with both review session ids.
3. entire.io ingests and amplifies
The checkpoint summary's Sessions array becomes repo_checkpoints.session_ids on ingest (api/src/lib/github.ts:732). The session page queries session_ids LIKE %sessionId% (cache.ts:3711), so all 13 contaminated checkpoints match, and mergeSessionTranscript (session-transcript.ts:81) emits this session's prompt slice once per checkpoint — and the per-checkpoint copies don't share a message id, so dedupe misses → the review prompt renders 13 times.
The smoking gun for the fix
The author already knew review sessions must be excluded — but applied it in only one place. reviewSessionContext (cmd/entire/cli/review_context.go:212) deliberately skips them:
That exact exclusion is missing from the commit-folding path. The fix belongs in findSessionsForWorktree (or the PostCommit skip gate): exclude KindAgentReview sessions so a read-only review never gets attributed to commits. That alone collapses the 13 down to the 2 checkpoints the review legitimately owns. Secondarily, the FullyCondensed && PhaseEnded gate is too strict for no-op sessions — a session with no file changes that has ended should also be skippable.
Want me to draft the patch in findSessionsForWorktree (mirroring the KindAgentReview skip), with a regression test?
TL;DR: Two bugs stack.
-
Two review sessions, not one —
entire reviewon thereview-profilesbranch usesRunMulti, which fans out multiple reviewer agents, each getting its own session (the two near-identical…16ab/…16f3Codex sessions). -
Review sessions leak into every commit — at commit time,
findSessionsForWorktreegrabs all sessions in the worktree with no filter on kind. Read-only review sessions never make file changes, so they never becomeFullyCondensedand never get skipped — so they get stapled onto every later commit's checkpoint for ~5 hours. That's how one review session ends up attached to 13 unrelated checkpoints, and the session page renders its prompt 13×.
Fix: exclude KindAgentReview sessions in the commit path — the same skip already exists in review_context.go:212, just missing here.
Ok, but the idea of the review sessions is that it's attached one time to the next checkpoint I guess and that's it? Because how would attribution otherwise work here :thinkies:
Right — exclude-always is the wrong fix. A review session does need to land on a checkpoint once, otherwise the review work (and its token/attribution data) attaches to nothing.
The correct model is attach once, then go terminal:
- The review session gets folded into one checkpoint (its own, or the next commit) — that's the legitimate attribution moment. Attribution-wise it's cheap: read-only review = 0 agent lines touched, so it contributes tokens/metadata but no line attribution.
- After that, it should be marked done so it stops re-attaching.
The actual bug is that nothing ever flips it to terminal. The only "skip" gate is FullyCondensed && PhaseEnded, and a read-only review session never becomes FullyCondensed (no file changes → no shadow branch to condense), so it stays "live" in the worktree and re-attaches to every commit for hours.
So the fix isn't "filter out KindAgentReview" — it's "let a review session reach terminal state after its one attachment," e.g.:
- when the review completes, mark its sessions ended and condensed (or clear the state), or
- treat a no-op/read-only session that has ended as skippable without requiring
FullyCondensed.
Then it attaches exactly once, attribution works, and the 13× repeat goes away.
Done. Here's the fix.
What I changed (in ../cli, branch review-profiles)
cmd/entire/cli/strategy/manual_commit_hooks.go — in postCommitProcessSessionLocked, after a session is condensed into a checkpoint, pin a review session terminal:
cmd/entire/cli/strategy/phase_postcommit_test.go — added TestPostCommit_ReviewSession_PinnedToSingleCheckpoint: a review session is condensed into one checkpoint on the first commit, marked terminal, and confirmed not re-condensed into a second commit's checkpoint.
Why this is the right spot
The leak only happens when a session is actually condensed into a commit — that's what writes it into the checkpoint's Sessions array (→ session_ids on ingest). A read-only review touches no files, so it never accrues shadow-branch content and never hits the existing terminal path (FullyCondensed && PhaseEnded, line 1337). Left active, it stayed in the worktree's session set and got re-condensed into every later commit.
The fix gives it exactly the "attach once, then done" semantics you described:
- It's gated on
handler.condensed, so it only goes terminal after it has actually landed in a checkpoint — attribution (tokens/metadata/transcript) gets its one home. - A review skipped by the existing read-only gate isn't written to a checkpoint and stays eligible to attach to a later one.
- Once terminal, the existing PostCommit skip (
FullyCondensed && PhaseEnded) drops it from all subsequent commits — so no more leaking into 13 unrelated checkpoints, and the session page stops repeating the prompt.
Verification
go build✅- New regression test passes ✅
- Full
strategypackage suite passes (27.5s) ✅ go vetclean ✅
One thing I deliberately left out of scope: this caps a review at one attachment but doesn't change which checkpoint it attaches to — if an unrelated commit lands first, the review still rides along on it once. If you'd rather a review never piggyback on a non-review commit at all (and instead always get its own checkpoint), that's a larger behavior change — say the word and I'll dig into the review checkpoint flow.
Want me to commit this on the branch?
Commit then attach this session to that commit via entire session attach and push to the cli repo.
There's a clean override point: ENTIRE_TEST_CLAUDE_PROJECT_DIR. Since this session ran in the entire.io-1 project dir, I'll point attach there. Let me confirm the default agent name and that the binary resolves the transcript.