Repository navigation
v4.22.0 — What real usage said
Every change below answers a failure counted in real transcripts (765 sessions
over 30 days, then a second pass), not a hypothesis. Measured on this
project's own A/B benchmark (tsbench --reel, Haiku, same evening): with the
auto profile, 8/8 tasks solved like the baseline, cost -14 %, new tokens
-25 %, wall time +45 %. One pass per task, variance not measured.
Second pass on real usage (07/10/2026)
ts_executeresults are capped at 60 000 characters
(TS_EXECUTE_MAX_CHARS). A script that read four whole files returned
57 545, which Claude Code refused and parked in a file: the script ran for
nothing. The returned value is cut, logs and counters kept, and the cut is
reported.- README:
"alwaysLoad": truein the Claude Code snippet. Without it every
tool sits behind ToolSearch and the model reaches forReadandGrep. get_function_source/get_class_sourcereturn the source again.
Without an explicitlevel, a Thompson-sampling bandit picked the detail
level and scored every non-empty answer a success, a[L2]summary
included; a re-request with an explicitlevelrecorded nothing, so no
failure was ever counted. It had drifted to 5 284 successes for[L2]
against 34 for the full source: over 30 days, 61 of 95 calls without
levelreturned a summary or a bare signature instead of the code asked
for. Full source is now the default; the bandit only runs with
TS_LEVEL_BANDIT=1.- The home directory is a container, never a project. A home that is
itself a git repository (a VPS under version control) made every
marker-less folder below it —/root/claude-chat,/root/gmgn-api… —
resolve to the whole home: a 10 000-file index, capped, answering
"not found". Such a path now belongs to its first-level subdirectory, and
a session launched from home no longer registers home as its project. - The active project survives a server restart. Each Telegram message
starts a freshclaude -p, hence a fresh MCP server whose active project
fell back to the first configured root. The last project made active is
now remembered per launch directory and restored when nothing more
reliable (environment hint, launch directory that is a project) chose. - The over-exploration hint stops repeating. Past 15 navigation calls in
a server's life (sub-agents included), ~340 identical characters were
appended to every answer. It now shows at the threshold, then every 25
calls. - Leaner
automanifest: 18 → 13 tools, ~4 160 → ~3 220 tokens.
move_symbol(0 calls in 30 days),ts_search(1) andget_git_status
(11) leave the always-on set;set_project_rootandget_functionsare
never promoted (switch_projectandget_structure_summaryalready do
their job). All stay callable by name and fromts_execute. Hot slots
10 → 8 (TS_AUTO_HOT_K). test_vector_distance_floorskips when the vector stack is half
installed (fastembed without sqlite-vec madevec_search_rowsreturn
[]silently, read as a recall defect).
What 30 days of real calls said (06/10/2026)
Audit of 765 transcripts (3 569 Token Savior calls) and of the discipline
guard's own log. Every change below answers a measured failure, counted in
the commit that fixes it.
.mjsand.cjsare indexed. Everyget_functionsmiss and most
read_linesmisses were on ES modules (launchpad/pump.mjs,
runtime/cycle-reconciler.mjs): the include patterns stopped at.js/.jsx.
Imports between them resolve, editing works. Index cache version bumped to 4.- Constants have a context.
find_symbolwith default kinds now falls
back to variables on a miss instead of answeringretry_with; the
TypeScript/JavaScript annotator records top-levelconst/let/var
bindings (it recorded none);get_full_contextandget_edit_context
return the binding's source (newget_variable_source, brackets balanced)
instead of "function 'X' not found". Half of theget_edit_context
failures wereexport consttables. read_linesreads files outside the index — absolute, or relative to
the active root; regular text files under 5 MB, no binaries — and says so.
17 of its 19 failures were files that existed on disk.search_codebase(max_results=0)is cut at 60 000 characters with a
_tronquenote. Five such calls returned 107-192 k characters, which
Claude Code refuses.ts_executeacceptscode(andbody,js) forscript. 8 of its 30
failures. The two homonymous_ARG_ALIASEStables inserver.pyhad
diverged (schema read one, dispatch the other); merged into one.- "Found in project" no longer sticks to successful reads. The footer
fired whenever the returned text contained "not found in index"; reading
the handler that emits it was enough, and the footer ended up pasted into
the source by areplace_symbol_source. - An empty
.gitdirectory is not a project, and the temp directory never
is. A stray/tmp/.git(empty, 27/09) made/tmpa repository, so every
scratch clone resolved to/tmp, which a nightly job indexed whole
(25 MB cache). - MCP SDK 2.x and protocol 2026-07-28. Claude Code negotiates the
2026-07-28 protocol on stdio by default since 2.1.292; a server that does
not answer it pays a slow connect, then seven days remembered in the old
mode. The 2.x SDK serves both eras in one loop but dropped the
list_tools()/call_tool()decorators, so the server crashed at start
under it.main()now builds the server with the 2.x callbacks when that
SDK is installed and keeps the 1.x path otherwise; the pin ismcp>=1.25,<3.
Checked over stdio: 2.x server answers 2026-07-28 to a modern client and
2025-11-25 to a 1.x one. - Discipline guard, recalibrated on its own log (30 days):
- native
Edit/Writeon indexed code is now advice, logged as
conseil, never denied: 303 of 305 refusals were re-issued identically
within five minutes; - native
Readis refused only whole-file and over 200 lines
(TS_GUARD_READ_FREE_LINES): 78 % of refused Reads were re-issued, and
Claude Code'sEditrequires a priorRead; insert_near_symbolno longer needs the anchor's context (26 of 29
refusals were on an anchor that is not modified);get_function_source,get_class_sourceandts_executescripts
calling the context tools count as context; a symbol edited once can be
edited again;- a marker at
/or in the temp directory no longer makes a project.
- native
Reading by line number
An audit of 1 047 real code reads done in Bash rather than through this server
found that 93,2 % never tried a tool at all (only 1,4 % were repairs after a
failure), and that among the reads the index could have served, the dominant
shape was sed -n '105,150p' path.py. The caller held a line number — from a
traceback, a grep -n hit, a compiler error — and every read tool on the
surface asked for a symbol name.
read_lines(file_path, start, end). 1-indexed, inclusive, numbered by
default (numbers=falseto get raw text back).endis optional: with only
astart, it returns a 60-line window, which is the shape a traceback
gives you. Capped at 400 lines by default (max_lines) so a hugeend
can't turn back intocat, with a marker naming thestartto resume from.
Names the enclosing function or class after the excerpt, so a range that
turns out to be the wrong unit routes toget_function_sourceinstead of a
second blind guess. The engine already hadget_lines— tested, reachable
from the Python API, exposed by no tool. Also whitelisted forts_execute
scripts, where it was equally missing.TS_STICKY_ACTIVEnow holds for unregistered directories. The freeze
lived inserver_state.noter_racine_active, at the dispatch layer, but
SlotManager.resolveassignedactive_rootitself on one branch: the real
path nobody had registered. That is exactly the /tmp clone, the export
folder, the scratch file — 23,4 % of those same Bash reads. So the one
guarantee meant to stop a parallel agent from repointing the shared default
failed precisely where the need is most common, and the next hint-less call
silently answered from the wrong repository. Default behaviour is unchanged
(promotion still happens with the flag off).
One server, many worktrees, no stolen calls
Parallel agents in sibling worktrees shared one server and one mutable
active_root, and the worktree a session actually worked in was often never
even registered. Verified against the hosts, not their folklore: Claude Code
exports CLAUDE_PROJECT_DIR (stable — always the main checkout, never the
worktree); CLAUDE_PROJECT_ROOT is set by no host at all; Codex exports no
path variable and whitelist-filters the MCP server environment, so only the
spawn cwd carries its signal.
- Per-call routing by absolute path. A call with no
projecthint but an
absolutefile_path/path/target_filenow routes to the tree that owns
the path — nearest marker root, so a worktree nested in a registered repo
(repo/.claude/worktrees/wt) wins over the parent checkout that contains
it. A linked worktree's.gitfile counts as a marker. The owning root
registers on the fly, and the sharedactive_rootis never touched. - Path hints resolve to their owner.
project=hints that are paths go
through the same nearest-root routing; a subdirectory of a registered
project no longer gets registered as a project of its own. Removed a
literally duplicated reverse-containment/register block inresolve(). - Boot signals, validated and ranked.
CLAUDE_PROJECT_ROOT(deliberate,
ours: may register, always wins) → launch-directory root (the only signal
that follows worktrees and the only one Codex has; when it is a linked
worktree it takes active) →CLAUDE_PROJECT_DIR(automatic: promotes among
registered roots, never registers — pytest under Claude Code would adopt
the developer's repo at import otherwise). A candidate counts only if it
exists and carries a project marker. The launch directory now always gets
a slot even whenWORKSPACE_ROOTSpinned the registry. TS_STICKY_ACTIVE=1freezesactive_rootfor multi-agent sessions:
explicit hints and path arguments still route every call, but no call
repoints the default the other agents fall back to.- Hooks and the standalone CLI now fall back
CLAUDE_PROJECT_ROOT→
CLAUDE_PROJECT_DIR→$PWD; the session-start memory hook previously
read only the first, which no host sets, and ran with an empty project.