Skip to content

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
huggingface:mainfrom
jheronimus:fix/session-index-drop-bug
Open

fix: Tau doesn't add a conversation to the index on concurrent writes and orphaned sessions#360
jheronimus wants to merge 2 commits into
huggingface:mainfrom
jheronimus:fix/session-index-drop-bug

Conversation

@jheronimus

Copy link
Copy Markdown
Contributor

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.

_upsert reads 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 second path.write_text overwrites the first — silently dropping the loser's entry.

So the fix: 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.

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 when get_session returns None instead of re-adding the orphan. So once unindexed, it stays unindexed forever — even while actively used.

So the fix: replace the touch_session call in _refresh_persisted_state 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 #308 (closed unmerged). #306 was closed as "already fixed" but the _touch_or_index_session fix is not in main_refresh_persisted_state still calls touch_session, which is still a silent no-op for missing entries.

Changes

File Change
src/tau_coding/session_manager.py _upsert append-only; _write_index removed as dead code
src/tau_coding/session.py _touch_or_index_session replaces touch_session in _refresh_persisted_state; _index_current_session delegates to it
tests/test_session_manager.py regression tests for concurrent upserts and read-side dedup
tests/test_coding_session.py regression test: session re-indexed after index is wiped

30 lines of source changed across 2 files. 743 tests pass, ruff and mypy are clean.

@jheronimus
jheronimus requested a review from alejandro-ao as a code owner July 14, 2026 01:22
@jheronimus
jheronimus force-pushed the fix/session-index-drop-bug branch from 101b1fe to 421e995 Compare July 16, 2026 20:00
…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
jheronimus force-pushed the fix/session-index-drop-bug branch from 421e995 to 63d9bca Compare July 18, 2026 12:57
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.

1 participant