fix(orchestrator): version-drift + path-hashing leading-dash strip in mcp/server.ts - #10
Open
evannadeau wants to merge 1 commit into
Conversation
… mcp/server.ts
Two trivial fixes in mcp/server.ts plus rebuilt dist/server.js (bun build,
249 modules; 516/516 tests pass).
1. Version-drift (mcp/server.ts:655)
`system_status` reported a hardcoded `0.30.28` instead of the
dynamically-read `${PLUGIN_VERSION}` (set from package.json at module
load, lines 32-37). The 0.30.31 consolidation that introduced
PLUGIN_VERSION explicitly aimed to fix this kind of drift but missed
this one site. Symptom: every release since (0.30.32 → 0.30.38) has
shipped `system_status` reporting `0.30.28`.
2. Path-hashing leading-dash strip (mcp/server.ts:2184)
`projectHash` stripped leading dashes after replacing path separators:
projectDir.replace(/[\\/:]/g, "-").replace(/^-+/, "");
On POSIX, every absolute path produces a leading dash after the
replace, which the strip then removes. But Claude Code preserves the
leading dash when it names project directories under
`~/.claude/projects/-<...>`. The mismatch silently breaks the
agent-channel filewatcher: `projectsHashDir` resolves to a
non-existent directory, `listJsonlFiles()` returns `[]`, and every
`@SA-<id8>` channel address gets silently dropped.
Symlinking the stripped name to the real one restores routing (validated
workaround); the proper fix is to stop stripping.
On Windows, the prior leading-dash strip was over-correction for the
drive-letter case (`C:\foo` → `C--foo`) — a cosmetic double-dash, not
broken routing. Removing the strip is harmless there.
dist/server.js regenerated via `bun run build` against the same source
to guarantee the bundled artifact matches the source (per the long-stale
PR rebuild-after-rebase rule).
Author
|
Independently re-verified during a duplicate-PR cleanup that the 5 remaining
(Companion PR #13 from me was a partial-duplicate version-drift-only fix opened before this PR's existence was noticed in the workspace KB — closing it now as subset.) |
SpawnBox-dev
pushed a commit
that referenced
this pull request
Aug 9, 2026
… could not surface at all (0.49.0) The hybrid search could not return a note the keyword leg had missed, however good its cosine. So the chunking and model work shipped hours earlier in 0.46-0.47 was being discarded one stage downstream. MEASURED on the live 7148-note KB, tracing one probe end to end. Target cc1d3816, query "chatter about a topic got mistaken for the real event and corrupted the label": vector similarity ......... #2 of 7148 <- second-best match in the corpus after RRF ................. #11 after signal/confidence ... #19 of 24 candidateTopK slice(0,12) . DROPPED final result .............. ABSENT from the top 6 TWO STRUCTURAL SUPPRESSORS, neither wrong in isolation: 1. RRF DUAL-CONTRIBUTION ASYMMETRY. reciprocalRankFusion sums 1/(k+rank) over both lists, so a note present in BOTH can reach ~0.033 while a vector-only note is capped at 1/(60+1) = 0.0164 - and only the ~18 FTS candidates are eligible for the bonus. No cosine score can lift a note the keyword leg missed above a mediocre note it found. Amplified by list-length imbalance: 18 keyword candidates against 7148 vector-ranked notes. 2. THE SIGNAL BOOST IS AN ABSORBING STATE - the same class this lane already found in briefing ordering (ed316fcd entry R). signal is EARNED BY BEING SURFACED, so a note that has never surfaced cannot earn the multiplier that would let it surface. Observed while writing these tests: a filler with signal 50 outranked a note that won on BOTH keyword and vector. THE FIX: reserve at most 2 result slots for the highest-cosine notes. Displaces from the TAIL, never the head, so a note both signals agree on is never evicted. Reserved candidates pass the same superseded / code_ref filters as any other result - a reserve that bypassed them would be a back door around the caller's constraints. Direct precedent, same shape and same remedy: GLOBAL_RESERVED in recall.ts, whose comment reads "without reserved slots, the larger project DB drowns them out". The vector leg was the drowned minority list here. VERIFIED LIVE: cc1d3816 now surfaces. HONEST LIMIT, stated rather than left to be discovered: probes ranking #10, #60 and #129 by vector still do NOT surface. A 2-slot reserve rescues the head of the semantic distribution, not its tail. THE DEEPER REWORK IS DEFERRED ON PURPOSE, filed as 27d1da01 with five candidate directions and none prescribed. The blocker is a LABELLED EVALUATION SET, not implementation effort: this same session tried mean-centering - the textbook anisotropy fix - and measured it WORSE on every probe. Five hand-written adversarial probes can DETECT a broken ranker; they cannot TUNE one, and changing live fusion without an eval set is a coin flip that feels like progress. Guards: tests/engine/semantic-reserve.test.ts, 7 tests - a zero-keyword-overlap note surfacing against 30 lexically-matching signal-80 decoys; the reserve capped at 2 of 6 so keyword keeps the majority; the top hybrid result not evicted; a superseded note never promoted; no-vector queries still returning keyword results; plus wiring assertions on the bound and on tail-displacement. Suite 1125 pass / 0 fail. One fixture correction recorded in the test file rather than made quietly: the head-preservation test originally gave its decoys signal 50 and failed - not because of the reserve, but because the pre-existing boost let a high-signal filler outrank a note winning on both signals. Decoys are now signal 0 so the test measures what its name claims; the boost finding went to 27d1da01. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MD4kPkrZLWbUxe4arhdwii
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 trivial bug fixes in
plugins/orchestrator/mcp/server.ts, plus rebuiltdist/server.js. Independent fixes, bundled because they touch the same file and have minimal blast radius.Fix 1 — Version-drift (
mcp/server.ts:655)system_statusreports a hardcoded0.30.28instead of the dynamically-read${PLUGIN_VERSION}. The 0.30.31 consolidation that introducedPLUGIN_VERSION(read frompackage.jsonat module load, lines 32-37) explicitly aimed to fix this kind of drift — "the version isn't hardcoded in multiple spots and forgotten on every other version bump" per the comment — but missed this one site. Every release since (0.30.32 → 0.30.38) has shippedsystem_statusreturning0.30.28while the manifest, package.json, and startup banner all correctly show the current version.Before:
After:
Cosmetic to user-visible, but operators reading
system_statusto confirm they're on the expected version are currently misled.Fix 2 — Path-hashing leading-dash strip (
mcp/server.ts:2184)projectHashstrips leading dashes after replacing path separators:On POSIX, every absolute path produces a leading dash after the
replace(/[\\/:]/g, "-")(e.g.,/home/user/project→-home-user-project). The subsequent.replace(/^-+/, "")strips it, yieldinghome-user-project. But Claude Code preserves the leading dash when it names project directories: the actual directory created at~/.claude/projects/is-home-user-project, with the dash.The runtime consequence:
projectsHashDirresolves to a non-existent directory.listJsonlFiles()readsreaddirSync(projectsHashDir)and theexistsSync()guard returns false, yielding[]. The agent-channel filewatcher processes zero JSONL files, never writes offsets, never parses@SA-<id8>channel addresses, and never emits routing notifications. Every multi-paragraph PA→SA dispatch on POSIX silently drops until an operator notices and symlinks the stripped path to the real one as a workaround.Other agent-channel paths (
stateDir = join(projectDir, ".orchestrator-state")forsessions.json, system_events DB, etc.) are correctly relative to cwd — onlyprojectsHashDir, which has to match CC's directory naming, is affected.Before:
After:
The original strip was over-correction for the Windows drive-letter case (
C:\foo→C--foo) — a cosmetic double-dash, not broken routing. Removing the strip is harmless on Windows; on POSIX it's load-bearing.Diagnostic recipe for anyone reproducing the bug:
ls <project>/.orchestrator-state/agent-channel/— absence ofoffsets-<id8>.jsonfiles means JSONL is not being processed.grep -c '<channel source' <project_jsonl_dir>/<sa_session_id>.jsonl— zero means no events were ever delivered to that SA.ls ~/.claude/projects/shows-home-...with leading dash; plugin-computedprojectsHashDir(instrumented viaprocess.env.DEBUG=1or similar) showshome-...without it.Test plan
bun install(98 packages, clean).bun run buildagainst the updated source →dist/server.js0.94 MB, 249 modules bundled. Output regenerated to match source (no stale dist).bun test516 pass / 0 fail / 1207 expect() calls across 38 test files.PLUGIN_VERSIONresolves correctly at module load (already in use atserver.ts:464forversion: PLUGIN_VERSIONin the MCPserverInfoand atserver.ts:2900for the startup banner — same identifier).cb40771(currentmain) — line numbers verified directly againstmainsource, not against stale notes.projectHashagainst POSIX vs Windows inputs — happy to follow up if requested.Files changed
plugins/orchestrator/mcp/server.ts— 2 surgical edits (1 line for version, 1 line + 3 comment lines for path-hashing)plugins/orchestrator/dist/server.js— regenerated viabun run build(only 4 lines changed, bundler-deterministic)Out of scope
Two larger upstream candidates still queued from the same operator's KB:
assistant_textevents at the first colon-after-blank-line — substantive change to the truncation logic, separate PR.Both will ship after maintainer review of the trivial-fix bundle here.
Related
🤖 Generated with Claude Code (Admiral PA orchestrating an upstream-cleanup batch)