Collapse exact testreport_read re-reads to a notice - #611
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #611 +/- ##
==========================================
+ Coverage 96.83% 96.85% +0.02%
==========================================
Files 214 214
Lines 62943 63399 +456
==========================================
+ Hits 60951 61407 +456
Misses 1992 1992 ☔ View full report in Codecov by Harness. |
mimi1vx
left a comment
There was a problem hiding this comment.
Blocker
- [crates/mtui-mcp/src/testreport_tools.rs:448] No bypass mechanism (
force/no_cache) to retrieve file content on re-read. Collapsing identical reads to"[unchanged since …]"in the"content"field breaks callers (such as LLM subagents or pipelines whose context was compacted) that genuinely need to inspect the file text again. Add an optional parameter (e.g.force: Option<bool>) allowing a client to bypass the notice and get the real file content. - [crates/mtui-mcp/src/testreport_tools.rs:413]
rrid_keydiscrepancy between explicit and defaulted template calls. If a single template is loaded but not active,resolve_target_pathsucceeds whileguard.templates.active_rrid()isNone(resulting inrrid = ""). A subsequent call passingtemplate = Some(rrid)produces a differentRereadKey, causing a false cache miss. Using the canonical resolvedpath: PathBufas the key eliminates both template-resolution discrepancies and path-normalization variations. - [crates/mtui-mcp/src/session.rs:643] Notice message missing force-read instructions. Document in the notice message how callers can force a full re-read.
…#611) Exact re-reads collapsed with no way to resend (breaks compacted-context callers); (rrid, relpath) key missed when defaulted vs explicit template resolved identically. Key on the canonical resolved path, add optional force boolean, document it in the notice.
63dc937 to
2a73bee
Compare
mimi1vx
left a comment
There was a problem hiding this comment.
Blockers
-
[P0] Dedup key is canonicalized on the explicit-relpath path but not on the default (no-relpath) path, so two spellings of the same file don't share a key on macOS.
testreport_tools.rs::resolve_target_path: whenrelpathisNoneit returnsresolve_path(session, template)(the raw path stored on the report, uncanonicalized); whenrelpathisSome(_)it goes throughsafe_template_file, which callsbase.canonicalize()first. On a host where the temp/checkout path crosses a symlink (macOS:/var→/private/var), the two branches produce differentPathBufs for the identical on-disk file, soRereadKeydiffers and the second read never collapses to the "unchanged" notice — this is the exact "path-normalization variations" gap called out in the original review, fixed for the template-resolution case but not this one.Reproduced 100% locally (not flaky):
cargo test -p mtui-mcp --lib testreport_tools::tests::reread_relpath_spellings_share_one_keyfails every run on macOS; this is also why the PR's own "MTUI macOS (Apple Silicon)" CI job is red while the Linux job (no such symlink) is green.Fix: canonicalize consistently on both branches — either canonicalize the result of
resolve_paththe same waysafe_template_filecanonicalizesbase, or canonicalize once at theRereadKeyconstruction site intestreport_readso neither branch has to be trusted individually. Add (or fix) coverage that actually runs on a symlink-crossing temp path so this can't regress silently on Linux-only CI again.
|
CI fix (cccc70e): the macOS failure was a real platform bug, not flakes. The relpath arm canonicalizes the checkout base while the defaulted |
mimi1vx
left a comment
There was a problem hiding this comment.
BLOCKING: crates/mtui-mcp/src/testreport_tools.rs — path.canonicalize() now runs inline on the async task on every testreport_read call (not just cache hits), not inside the existing spawn_blocking wrapper — this violates the function's own stated policy ("must not block a Tokio thread") on the hottest MCP read path.
BLOCKING: the collapsed "unchanged" response is only detectable by string-matching "[unchanged since" inside content, with no structured flag — a non-LLM consumer expecting content to always be file text will silently receive a synthetic string instead.
FLAG: same root cause as #620 — a blocking syscall added to an async dispatch path without spawn_blocking. Please coordinate a single fix/convention reminder across both PRs instead of independent patches.
|
Review-sweep responses (2026-09-12) — all findings addressed, each rework adversarially re-reviewed (GO) with red-proven tests, full workspace gates green. Cross-PR summary (per-PR diffs carry the detail):
One systemic thread across #610/#611/#620: new I/O or truncation reuses the project primitives (spawn_blocking for blocking I/O, in-band truncation notices) — applied in each. |
mimi1vx
left a comment
There was a problem hiding this comment.
Blocker (P1)
crates/mtui-mcp/src/testreport_tools.rs:454:_scopelock dropped prematurely before read I/O begins. Intestreport_read,let _scope = session.scoped_lock(template).await;is declared inside thelet target = { ... };block despite the comment stating// The gate scope is held for the whole call, the inner mutex only for the path resolution._scopeis dropped as soon astargetresolves, releasinggate.shared()and the per-RRID lock beforeresolve_relpath_async,stream_read, or dedup run. All sibling tools (testreport_patch,testreport_write,testreport_fill,testreport_logs) declarelet _scopeoutside the inner block. Movelet _scope = session.scoped_lock(template).await;outside and beforelet target = { ... };.
Blocker (P2)
crates/mtui-mcp/src/testreport_tools.rs:480, 495: Consolidate sequentialspawn_blockinghops.testreport_readperforms up to three separatespawn_blockinghops (resolve_relpath_async,stream_read,canonicalize_for_key_async).stream_readcan canonicalize the path during its execution and return(StreamRead, PathBuf), eliminating the separatecanonicalize_for_key_asynchop.
…#611) Exact re-reads collapsed with no way to resend (breaks compacted-context callers); (rrid, relpath) key missed when defaulted vs explicit template resolved identically. Key on the canonical resolved path, add optional force boolean, document it in the notice.
9af4343 to
4651724
Compare
mimi1vx
left a comment
There was a problem hiding this comment.
Blockers
- [P1]
docs/src/mcp.md:306-317— still documents the pre-PRtestreport_readcontract; doesn't mention the newforceparam ordedupedresponse field. Please update before merge since this is a user-facing MCP reference and the CHANGELOG calls this an additive schema change. - [P2] No test exercises the
_scopelock's hold duration (only its downstream effects). Consistent with sibling tools in this file, so not a regression — just noting the gap.
All four numbered blockers from the prior four review rounds (force/no_cache bypass, dedup key canonicalization on the default path, canonicalize() inside spawn_blocking, lock held across the whole call) are verified resolved, including against the macOS symlink case.
…#611) Exact re-reads collapsed with no way to resend (breaks compacted-context callers); (rrid, relpath) key missed when defaulted vs explicit template resolved identically. Key on the canonical resolved path, add optional force boolean, document it in the notice.
…enSUSE#611) The testreport_read section still described the pre-PR contract. Covers force=true resend+refresh and deduped on collapsed (incl. windowed) vs full reads.
bdd0fc1 to
ae703a3
Compare
Same RRID+file+offset/limit with unchanged payload collapses to an [unchanged since ...] notice. Last-16 LRU; edits always resend. Output-only.
Pin line_count with a capped-head test, state the collapse in the read description, FNV-1a window hash, LRU wording, relpath key normalisation.
…#611) Exact re-reads collapsed with no way to resend (breaks compacted-context callers); (rrid, relpath) key missed when defaulted vs explicit template resolved identically. Key on the canonical resolved path, add optional force boolean, document it in the notice.
testreport_read did traversal stat + canonicalize inline on the worker; move both off via spawn_blocking, keeping errors and dedup keys identical.
…tream_read _scope lived inside the target block so the gate/per-RRID lock dropped before the read I/O; stream_read now returns the canonical key path in the same worker hop, dropping the third spawn_blocking.
…enSUSE#611) The testreport_read section still described the pre-PR contract. Covers force=true resend+refresh and deduped on collapsed (incl. windowed) vs full reads.
ae703a3 to
7b2a109
Compare
mimi1vx
left a comment
There was a problem hiding this comment.
Reviewed at head 7b2a109 (full diff + changed files). Traced the cache-poisoning-relevant paths specifically: the ambiguous-template and path-escaping checks in resolve_path/safe_template_file both return via ? before the RereadKey/hash are ever constructed, so the cache is not reachable from either. FNV-1a64 constants/order are correct. Cache lives on McpSession — per-client, no cross-session leakage. 16-entry LRU bound and eviction are correct and tested at the exact boundary. deduped field is additive-only, documented, and the schema snapshot was regenerated to match. The "never suppresses an edit" tests are genuine red/green tests (line_count-only and hash-only cases both covered).
No blockers, no Priority items. Approving.
Exact re-reads collapsed with no way to resend (breaks compacted-context callers); (rrid, relpath) key missed when defaulted vs explicit template resolved identically. Key on the canonical resolved path, add optional force boolean, document it in the notice.
Summary
testreport_readcollapses an exact re-read of the same window to a short[unchanged since <hash>, N lines; use offset/limit to move]notice instead of resending the full text. Kills the observed 99k-token / 109x re-read spike; edits always resend (hash + line-count gated, bounded 16-window per-session LRU).Changes
crates/mtui-mcp/src/session.rs:RereadKey { rrid, relpath, offset, limit }+ LRU(16) + inline FNV-1a64 (specified hash, zero new deps; comparison uses fullu64, display truncates cosmetically).testreport_tools.rs: key from template-arg-or-active-RRID + normalised relpath (./logshareslog's key); ambiguous multi-template calls refuse before cache lookup (no cross-template poisoning); escaping relpaths error before lookup; same JSON shape, output-only, no schema change beyond one documented description sentence.Checklist
cargo fmt --all --checkis clean.cargo clippy --workspace --all-targets --all-features -- -D warningsis clean.cargo test --workspacepasses.--no-default-features,--all-features, compile-only).CHANGELOG.md.Related issues
Part 3 of 4 in the MCP token-reduction stack. Merge order: log-fold → json-crush → reread-dedup → token-nudges. Expected trivial conflict with siblings on
CHANGELOG.mdonly; resolved at merge time in that order.