fix(coding-agent): dedupe session tree edges - #1250
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The fix is narrowly scoped, preserves documented semantics, and is covered by a targeted regression test for the reported failure mode.
Pull request overview
Fixes a performance/pathological-behavior bug in SessionManager.getTree() where duplicate persisted rows (duplicate entry IDs) produced duplicate parent→child edges, multiplying traversal work and freezing the interactive TUI tree selector. The change keeps the existing “final entry wins” semantics for duplicate IDs while ensuring only one edge is created per unique node ID.
Changes:
- Update
SessionManager.getTree()to link nodes by iterating unique nodes (nodeMap.values()) rather than re-linking for every persisted row. - Add a regression test ensuring duplicate child rows don’t create duplicate edges and that descendants remain reachable.
- Document the divergence in
src/core/changes.mdand add a changelog entry.
File summaries
| File | Description |
|---|---|
| packages/coding-agent/src/core/session-manager.ts | Dedupe session-tree edge construction by linking once per unique node ID (preserving last-entry-wins). |
| packages/coding-agent/test/suite/regressions/issue-1247-session-tree-duplicate-entry-ids.test.ts | Regression test covering duplicate child rows and descendant preservation. |
| packages/coding-agent/src/core/changes.md | Tracks the core behavior change and rationale for upstream merge/conflict awareness. |
| packages/coding-agent/CHANGELOG.md | Notes the user-visible fix under Unreleased. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
code-yeongyu
left a comment
There was a problem hiding this comment.
Summary
This PR fixes a freeze in SessionManager.getTree() when a persisted session file contains repeated rows for the same entry ID (issue #1247).
What the change does
getTree() builds a nodeMap: Map<string, SessionTreeNode> in its first loop (last-write-wins for duplicate IDs), then builds edges in a second loop. The bug was that the second loop iterated the raw entries array — which may contain duplicate IDs — rather than the deduplicated nodeMap. For each duplicate occurrence of an ID, nodeMap.get(entry.id) returned the same node object, which was then pushed to the parent's children array multiple times. Under heavy duplication the children array grew super-linearly, causing the interactive session tree to freeze.
Dedupe logic — correctness
Iterating nodeMap.values() yields exactly one node per unique ID (insertion-ordered, last-written entry authoritative), so each child is pushed to its parent exactly once. Edge cases check out: duplicate roots pushed once, self-loop guard preserved, orphans still treated as roots, duplicate rows with different parentId resolve to the last row (consistent with the map's last-write-wins), child ordering unaffected (timestamp sort happens after edge construction), and complexity drops from O(all rows) to O(unique IDs).
Test coverage
test/suite/regressions/issue-1247-session-tree-duplicate-entry-ids.test.ts reproduces the real storage path via SessionManager.inMemory() + appendEntry() with a repeated ID, and asserts exactly one root, exactly one deduped child (fails on the old code), last-write-wins (data.revision === 2), and correct grandchild linkage. No mocking of the tree-building path; not tautological.
Minor note (non-blocking)
The test helper's timestamp template `2026-09-01T00:00:0${revision}.000Z` assumes single-digit revisions — safe today (0–3), but would produce invalid ISO strings at revision ≥ 10 if extended.
Re-approving to trigger the claim-release automation (merge-ref refresh test)
code-yeongyu
left a comment
There was a problem hiding this comment.
Re-approving after branch update — content review unchanged (see dismissed review above); this re-submit triggers the review-claims release automation.
Summary
packages/coding-agent/src/core/changes.mdCloses #1247.
Root cause
SessionManager.getTree()created oneSessionTreeNodeper unique ID, but then linked nodes by iterating every persisted row. Repeated rows therefore pushed the same node object into a parent'schildrenarray multiple times. Duplicate edges along a branch multiplied the subsequent iterative traversal and could block the TUI before the tree selector rendered.Verification
expected [ 'child', 'child' ] to deeply equal [ 'child' ]bun run --cwd packages/coding-agent test test/suite/regressions/issue-1247-session-tree-duplicate-entry-ids.test.tsbun run --cwd packages/coding-agent test test/session-managerbun run checkbun run buildReal-session QA
The affected private session was exercised without exposing message contents:
getTree(): exceeded 60 secondsgetTree(): 3.4 msQA receipts are stored locally under
local-ignore/qa-evidence/and are not committed.Summary by cubic
Fixes session tree construction freezing on sessions with duplicate entry IDs.
getTree()now builds one edge per unique node instead of re-linking the same node for every persisted row, while preserving the existing final-entry-wins index semantics.Bug Fixes
Written for commit a95f349. Summary will update on new commits.