Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

fix(coding-agent): prevent duplicate session prefixes and /tree freezes#1672

Closed
w-winter wants to merge 1 commit intobadlogic:mainfrom
w-winter:fix/session-dedup-tree-freeze
Closed

fix(coding-agent): prevent duplicate session prefixes and /tree freezes#1672
w-winter wants to merge 1 commit intobadlogic:mainfrom
w-winter:fix/session-dedup-tree-freeze

Conversation

@w-winter
Copy link
Contributor

Problem

/tree freezes the TUI permanently (no input or paint until the tab is closed) on some sessions. The affected sessions' .jsonl files contain duplicated session headers and duplicate entry IDs. /tree does synchronous traversals that assume a well-formed tree, so duplicates and cycles cause non-terminating loops on the UI thread.

How duplicated headers happen

Extensions can call pi.appendEntry(...) before the first assistant message. For example, examples/extensions/preset.ts persists state on turn_start, which fires before the assistant responds. Session persistence is deferred until the first assistant message. When that flush fires, _persist() appends the full in-memory fileEntries to disk, but the file already has content from those early appendEntry calls. Result: duplicated session header, prefix entries, and entry IDs.

The added test "rewrites deferred pre-assistant state instead of duplicating the existing session prefix" reproduces this directly.

Fix

This prevents the duplication and hardens traversal so that existing malformed sessions don't hang.

Persistence: first deferred flush now rewrites the file instead of appending, so early extension entries are preserved without duplication.

Traversal guards:

  • buildSessionContext() and getBranch(): visited-ID set terminates leaf-to-root walks on cycles
  • getTree(): dedupes entries by ID (last occurrence wins), breaks parent cycles into roots, memoizes cycle detection for large sessions

Validation

  • npm run check
  • cd packages/coding-agent && npx vitest run test/session-manager/file-operations.test.ts
  • cd packages/coding-agent && npx vitest run test/session-manager/build-context.test.ts
  • cd packages/coding-agent && npx vitest run test/session-manager/tree-traversal.test.ts

@github-actions
Copy link
Contributor

Hi @w-winter, thanks for your interest in contributing!

We ask new contributors to open an issue first before submitting a PR. This helps us discuss the approach and avoid wasted effort.

Next steps:

  1. Open an issue describing what you want to change and why (keep it concise, write in your human voice, AI slop will be closed)
  2. Once a maintainer approves with lgtm, you'll be added to the approved contributors list
  3. Then you can submit your PR

This PR will be closed automatically. See https://github.com/badlogic/pi-mono/blob/main/CONTRIBUTING.md for more details.

@github-actions github-actions bot closed this Feb 27, 2026
@badlogic badlogic reopened this Feb 27, 2026
@badlogic
Copy link
Owner

This papers over the actual issue: somehow a corrupted session was created. I need to understand how that happened. This is not the fix we are looking for. Can report on #contributors on discord. pinged you there.

@badlogic badlogic closed this Feb 27, 2026
badlogic added a commit that referenced this pull request Feb 27, 2026
…m pre-assistant entry

createBranchedSession() wrote the file and set flushed=true even when the
branched path had no assistant message. The next _persist() call saw no
assistant, reset flushed=false, and the subsequent flush appended all
in-memory entries to the already-populated file, duplicating the header
and entries.

Fix: defer file creation when the branched path has no assistant message,
matching the newSession() contract. _persist() creates the file on the
first assistant response.

closes #1672
@badlogic
Copy link
Owner

Thanks @w-winter for the report and investigation. The root cause is right: createBranchedSession() sets flushed = true on the new file, then _persist()'s no-assistant guard resets it to false, and the subsequent flush appends all in-memory entries to the already-populated file.

We went with a narrower fix: createBranchedSession() now defers file creation when the branched path has no assistant message, matching the newSession() contract. This avoids the flushed state mismatch entirely rather than changing the flush strategy in _persist().

The traversal guards (cycle detection in getTree/getBranch/buildSessionContext) aren't needed since the root cause is fixed and we don't produce malformed sessions. Closing in favor of 2f64df1.

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.

2 participants