Skip to content

fix(coding-agent): dedupe session tree edges - #1250

Open
jc01rho wants to merge 2 commits into
code-yeongyu:mainfrom
jc01rho:fix/session-tree-duplicate-entry-ids
Open

fix(coding-agent): dedupe session tree edges#1250
jc01rho wants to merge 2 commits into
code-yeongyu:mainfrom
jc01rho:fix/session-tree-duplicate-entry-ids

Conversation

@jc01rho

@jc01rho jc01rho commented Sep 1, 2026

Copy link
Copy Markdown

Summary

  • build session-tree edges once per unique persisted entry ID
  • preserve the existing final-entry-wins index semantics for repeated IDs
  • add an issue regression covering duplicate child rows and descendant preservation
  • document the core divergence in packages/coding-agent/src/core/changes.md

Closes #1247.

Root cause

SessionManager.getTree() created one SessionTreeNode per unique ID, but then linked nodes by iterating every persisted row. Repeated rows therefore pushed the same node object into a parent's children array multiple times. Duplicate edges along a branch multiplied the subsequent iterative traversal and could block the TUI before the tree selector rendered.

Verification

  • RED: regression initially failed with 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.ts
  • bun run --cwd packages/coding-agent test test/session-manager
  • bun run check
  • bun run build
  • Senpi QA common harness: 10/10
  • Senpi RPC QA: 4/4
  • Senpi mock-loop QA: 48/48, zero real provider calls
  • Senpi TUI smoke: 5/5

Real-session QA

The affected private session was exercised without exposing message contents:

  • 7,202 entries
  • 7,094 unique IDs
  • 108 duplicate IDs
  • pre-fix getTree(): exceeded 60 seconds
  • fixed getTree(): 3.4 ms
  • returned 7,094 unique tree nodes and 7,093 edges
  • source JSONL SHA-256 unchanged

QA 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

  • Duplicate persisted rows previously appended the same node object to a parent’s children repeatedly, multiplying traversal work and blocking the TUI before the tree rendered.
  • Adds a regression test covering duplicate child rows and descendant preservation.

Written for commit a95f349. Summary will update on new commits.

Review in cubic

Copilot AI lite review requested due to automatic review settings September 1, 2026 06:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.md and 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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Re-trigger cubic

@code-yeongyu code-yeongyu added stale-review Review claim sat 3+ days without a review; claim labels were cleared. Needs a reviewer. will-review Reviewer claimed this PR and will review it soon. Merge is blocked until their review lands. labels Sep 1, 2026
@github-actions github-actions Bot removed the stale-review Review claim sat 3+ days without a review; claim labels were cleared. Needs a reviewer. label Sep 1, 2026
code-yeongyu
code-yeongyu previously approved these changes Sep 1, 2026

@code-yeongyu code-yeongyu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@code-yeongyu
code-yeongyu dismissed their stale review September 1, 2026 07:37

Re-approving to trigger the claim-release automation (merge-ref refresh test)

@code-yeongyu code-yeongyu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after branch update — content review unchanged (see dismissed review above); this re-submit triggers the review-claims release automation.

@github-actions github-actions Bot removed the will-review Reviewer claimed this PR and will review it soon. Merge is blocked until their review lands. label Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

session tree: duplicate entry IDs make getTree traversal explode and freeze the TUI

3 participants