fix(mcp): record_turn chat fallback + most-relevant-thread selection - #8
Merged
Merged
Conversation
Two compounding bugs broke Claude Desktop chat's record_turn: 1. _resolve_thread_id_for_write raised ValueError on every chat call because the v0.2.3 migration retired ~/.tinm/current_thread and chat has no cwd to fall back to. Add a per-host chat-thread fallback persisted in ~/.tinm/chat-thread, auto-created once on first use with origin=user and project_root=None. Disambiguates to claude-desktop-chat-<sha6> if the bare slug is owned by a real project. Read resolver path is unchanged. 2. The success-string formatter dereferenced result['alpha_used'], but the v0.3.0 hot path moved alpha/update_count ownership to the async worker and no longer returns them. Use .get() with a '—' placeholder; degrades cleanly on both the v0.3.0 hot path and the legacy sync path. Tests cover: explicit thread_id, chat-fallback create + persist + collision disambiguation + bare-slug recovery, legacy CURRENT_FILE still wins for pre-v0.2.3 installs, and the formatter never crashes on missing alpha_used. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…cent
_most_recent_thread used to win on max(last_updated) with a strict >
tiebreaker, so:
2a. Empty SessionStart auto-creates spawned by cron / autopilot
from /root or /tmp bumped their last_updated to "now" and
shadowed the user's real work-in-progress.
2b. Two threads with the same last_updated second fell back to
Path.glob() order — ext4 hash-table order, i.e. non-
deterministic across runs.
Replace the scalar timestamp comparison with a four-tuple relevance
score:
(is_substantive, last_updated, n_user_turns, slug)
is_substantive = 1 iff the thread has ≥ 1 user turn AND
last_updated != created_at. Empty autocreates score 0 and are only
chosen when no substantive thread exists. n_user_turns + slug
provide a deterministic order for ties, eliminating the glob-order
randomness.
Logic factored into _thread_relevance_score so the read-path
resolver can adopt the same ranking later without copy/paste. This
commit only wires it into _most_recent_thread to keep the diff
narrow.
Tests cover: pure scoring (substantive beats recent-empty, multi-
turn beats single-turn at the same second, assistant-only turns
don't count as substantive, slug-deterministic ordering),
integration (substantive beats more-recent autocreate, only-
autocreates falls back to most-recent, corrupt JSON is skipped,
missing/empty threads dir returns None).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
Two MCP bugs surfaced by the v0.2.3 + v0.3.0 combo on Claude Desktop chat:
Bug 1 —
record_turnfailed on every chat callTwo compounding causes:
_resolve_thread_id_for_writehad no fallback once the v0.2.3 migration retired~/.tinm/current_thread. Claude Desktop chat has no cwd, so every call without an explicitthread_idraisedValueError.result['alpha_used'], but the v0.3.0 hot path moved alpha/update_count ownership to the async worker and no longer returns them →KeyError.Fix:
~/.tinm/chat-thread. Auto-created once on first use withorigin=user,project_root=None. Disambiguates toclaude-desktop-chat-<sha6>if the bare slug already belongs to a real project. Last-resort only — the explicit-slug path and the legacyCURRENT_FILEpath are unchanged..get()everywhere with a—placeholder for missing alpha. Degrades cleanly on both v0.3.0 hot path and legacy sync path.Bug 2 — Resume loaded the avant-dernier thread
Two root causes in
_most_recent_thread:SessionStartauto-creates spawned by cron/autopilot from/rootor/tmphad theirlast_updatedbumped to "now" every minute, shadowing the user's real WIP thread.last_updatedthreads fell back toPath.glob()order — ext4 hash-table order, i.e. non-deterministic across runs.Fix:
(is_substantive, last_updated, n_user_turns, slug), extracted into_thread_relevance_score()so the read-path resolver can adopt the same ranking later.is_substantive = 1iffn_user_turns ≥ 1ANDlast_updated != created_at. Empty autocreates score 0 and are only chosen when no substantive thread exists.Test plan
pytest mvp/tests/test_record_turn.py— 10 new tests, all green (resolver paths, chat-fallback create/persist/collision/recovery, legacyCURRENT_FILEprecedence, formatter never crashes on missing alpha).pytest mvp/tests/test_thread_selection.py— 12 new tests, all green (pure score helper + integration: substantive beats recent-empty, same-second multi-turn wins, only-autocreates falls back, corrupt/missing dirs handled).pytest mvp/tests/full suite — 420 passed, 1 pre-existing skip. No regressions.Notes
~/.tinm/current_threadwas NOT reintroduced as the resolver — the new fallback uses a distinct per-host file (~/.tinm/chat-thread) that the read resolver never consults.🤖 Generated with Claude Code