fix: Tau doesn't add a conversation to the index on concurrent writes and orphaned sessions#360
Open
jheronimus wants to merge 2 commits into
Open
fix: Tau doesn't add a conversation to the index on concurrent writes and orphaned sessions#360jheronimus wants to merge 2 commits into
jheronimus wants to merge 2 commits into
Conversation
jheronimus
force-pushed
the
fix/session-index-drop-bug
branch
from
July 16, 2026 20:00
101b1fe to
421e995
Compare
…loss _upsert read the entire index, filtered out the record, and rewrote the whole file. Two interleaved calls (two Tau processes in the same folder) both read the same state, both append their record, and the second write_text overwrites the first — silently dropping the loser's entry. Switch _upsert to append-only: one write() syscall per record, no truncation, no rewrite. Duplicate lines for the same session ID (from touch_session updates) are already resolved at read time by _deduplicate_records (last writer wins), so read-side semantics are unchanged. _write_index is removed as dead code.
_refresh_persisted_state called touch_session, which silently no-ops when get_session returns None. A session whose index entry was lost (or never written — e.g. created by an older Tau version that didn't emit the session_info/model_change/thinking_level_change trio, or imported with dangling parent_ids) stayed invisible to /resume and tau -c forever, even while actively used. Replace the touch_session call with _touch_or_index_session, which re-creates the index entry when missing and touches it otherwise. _index_current_session now delegates to the same helper with update_timestamp=False so the first-persist index entry keeps its original created_at. This is the indexing portion of upstream PR huggingface#308 (closed unmerged); issue huggingface#306 was closed as 'already fixed' but the _touch_or_index_session fix is not in upstream/main.
jheronimus
force-pushed
the
fix/session-index-drop-bug
branch
from
July 18, 2026 12:57
421e995 to
63d9bca
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In certain situations you won't be able to find a previous conversation in /resume because Tau didn't add it to the index. In my workflow about 8 out of 20 conversations for the folder were not added to index.jsonl, even though the session .jsonl file exists on disk.
After research and tests I believe there are two separate reasons for this.
1. Concurrent rewrite race
Happens when two or more Tau instances are opened in the same project folder.
_upsertreads the entire index, filters out the record, and rewrites the whole file. Two interleaved calls (two Tau processes in the same folder) both read the same state, both append their record, and the secondpath.write_textoverwrites the first — silently dropping the loser's entry.So the fix: switch
_upsertto append-only. Onewrite()syscall per record, no truncation, no rewrite. Duplicate lines for the same session ID (fromtouch_sessionupdates) are already resolved at read time by_deduplicate_records(last writer wins), so read-side semantics are unchanged.2. Unrecovered orphan
A session file exists on disk but its index entry was lost or never written for any reason — crash during initial index write, file system issue, incomplete session creation path, etc. Every later persist calls
touch_session, which silently no-ops whenget_sessionreturnsNoneinstead of re-adding the orphan. So once unindexed, it stays unindexed forever — even while actively used.So the fix: replace the
touch_sessioncall in_refresh_persisted_statewith_touch_or_index_session, which re-creates the index entry when missing and touches it otherwise._index_current_sessionnow delegates to the same helper withupdate_timestamp=Falseso the first-persist index entry keeps its originalcreated_at.This is the indexing portion of #308 (closed unmerged). #306 was closed as "already fixed" but the
_touch_or_index_sessionfix is not inmain—_refresh_persisted_statestill callstouch_session, which is still a silent no-op for missing entries.Changes
src/tau_coding/session_manager.py_upsertappend-only;_write_indexremoved as dead codesrc/tau_coding/session.py_touch_or_index_sessionreplacestouch_sessionin_refresh_persisted_state;_index_current_sessiondelegates to ittests/test_session_manager.pytests/test_coding_session.py30 lines of source changed across 2 files. 743 tests pass, ruff and mypy are clean.