code-yeongyu / code-yeongyu/senpi
session tree: duplicate entry IDs make getTree traversal explode and freeze the TUI
- Dominant language
- TypeScript
- Stars
- 429
- Forks
- 98
- Avg merge
- 5h 3m
- Merged PRs (30d)
- 526
Description
## Summary
If a stored session contains duplicate entry IDs, the cost of opening the session tree can explode and may not finish within a usable amount of time.
When the conversation input is empty and `Esc` is pressed twice within 500ms, `showTreeSelector()` is called. This function synchronously calls `sessionManager.getTree()` before showing the selector. On the affected 7k-record session, `getTree()` did not finish within 60 seconds, so the entire TUI appeared frozen and the process had to be killed to recover.
By contrast, the same benchmark finished in about 67–73ms on two 6k+ sessions with no duplicate IDs. So the core trigger appears to be corrupted or duplicated entry identity, rather than session size itself.
## Environment
- OMO 5.0.0-0.beta.31
- OMO bundled `@code-yeongyu/senpi` 2026.8.31
- Linux x86_64
- Node.js 24.14.0
- Affected session: about 7.1k JSONL records at the time of measurement
## Reproduction steps
Do not modify the original session; use a copy or a synthetic session.
1. Prepare a healthy session with a long parent-child entry chain.
2. Duplicate several adjacent entry lines without changing `id` or `parentId`.
3. Resume the copied session.
4. With the input empty, press `Esc` twice within 500ms, or run `/tree`.
5. The TUI does not respond while `sessionManager.getTree()` is building the tree.
## Observed behavior
Affected session:
- `SessionManager.getEntries()` entry count at measurement: 7,176
- Duplicate IDs: 108
- Extra duplicate entries: 108
- `SessionManager.open()`: about 215ms
- `SessionManager.getTree()`: did not finish within 60 seconds (timeout)
Control measurements:
| Session | Duplicate IDs | `getTree()` |
| --- | ---: | ---: |
| 143 entries | 0 | 3.4ms |
| 6,431 entries | 0 | 72.9ms |
| 6,244 entries | 0 | 67.4ms |
| 7,176 entries | 108 | >60s / timeout |
The duplicate entries in the problem file were adjacent pairs with the same `id`, `parentId`, type, and timestamp.
## Cause evidence
- `packages/coding-agent/src/modes/interactive/interactive-mode.ts`
- Double `Esc` calls `showTreeSelector()`.
- `showTreeSelector()` synchronously calls `sessionManager.getTree()` before creating `TreeSelectorComponent`.
- `packages/coding-agent/src/core/session-manager.ts`
- `_buildIndex()` stores entries in `byId`, so a later duplicate ID overwrites the earlier map value.
- `getTree()` creates one `SessionTreeNode` in `nodeMap` per unique ID.
- It then walks all persisted entries again and appends `nodeMap.get(entry.id)` to the parent's `children`.
When persisted IDs are duplicated, the same node object is added to `children` multiple times. If such duplicate pairs sit consecutively in a parent-child chain, later traversal revisits the same node graph repeatedly. `getTree()` currently has no visited-node guard, so the work grows combinatorially and can effectively never finish.
`TreeList.render()` already renders only the `maxVisibleLines` range. The actual freeze happened in `getTree()` before any attempt to draw every row to the terminal.
## Expected behavior
Duplicate IDs in persisted session data must not freeze the TUI via `/tree` or double `Esc`.
On session load, duplicate entry IDs should be diagnosed clearly, or tree construction should avoid attaching the same node more than once, and `getTree()` traversal must be safe against duplicates and cycles.
## Sharable evidence
The affected session contains private work, so the original JSONL is not attached to this public issue. A minimal synthetic fixture can be provided with message bodies replaced by dummy values while keeping the duplicate-ID structure.
## Duplicate issue search
I searched open/closed issues in this repository for `session tree freeze`, `duplicate entry id`, and related terms, and did not find a directly matching report.
Contributor guide
Research direction
Start in packages/coding-agent/src/core/session-manager.ts, reading _buildIndex() and getTree(), then inspect the showTreeSelector() call in packages/coding-agent/src/modes/interactive/interactive-mode.ts. Reproduce the issue with a copied or synthetic session containing duplicate IDs and run /tree or double Esc. Done means duplicate IDs cannot make tree construction freeze, and traversal remains safe for duplicate entries and cycles.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100