Skip to content

fix(mcp): match latency report by path, not length delta (closes #3642) - #3643

Merged
apmantza merged 4 commits into
apmantza:masterfrom
drakeo338:claude/3642-fix
Sep 29, 2026
Merged

apmantza merged 4 commits into
apmantza:masterfrom
drakeo338:claude/3642-fix

Conversation

@drakeo338

Copy link
Copy Markdown
Contributor

Why

analyzeFile silently drops latency once the dispatcher's 100-entry ring is full.

Notes for the reviewer

  • Matching-strategy fix only.

Change outline

- analyzeFile (clients/mcp/analyze.ts)
  + drop the reportsBefore length snapshot
  + scan the ring tail-first for this file's resolved path

Summary

Closes #3642. analyzeFile matched its dispatch's report by slicing getLatencyReports() from a before-dispatch length. The ring pairs every push past capacity with a shift, so a full ring's length never changes and the slice was always empty, silently dropping latency. Now it scans the ring tail-first by resolved path.

Type of change

  • Bug fix
  • New feature (net-new capability)
  • Enhancement (improvement to existing capability)
  • Documentation

Area

  • area:lsp
  • area:dispatch
  • area:installer
  • area:diagnostics
  • area:read-guard
  • area:project-intelligence
  • area:perf
  • area:observability
  • area:session
  • area:config
  • area:security
  • area:tests

Checklist

  • I have read CONTRIBUTING.md and AGENTS.md
  • The change has tests (happy path, edge cases, regression test for bugs)
  • Targeted test files for the touched seams pass locally after npm run build; the full suite is CI's job.
  • Every NEW regression test is proven RED on pre-fix code; the red output is quoted in this PR
  • Every new guard/branch/filter is mutation-proof: deleting or neutering it reds at least one test — not run (npm run mutation:diff wasn't executed); the red/green pair below covers the same guard.
  • PR title carries the conventional prefix and the issue ref
  • npm run lint passes
  • npm run build:dist succeeds if I changed code under clients/, commands/, tools/, or index.ts
  • package-lock.json is in sync with package.json (regenerate with the exact npm pin in package.json's packageManager field) — not applicable, no dependency changed
  • AGENTS.md is updated if this PR changes behavior, commands, conventions, or invariants documented there — not applicable
  • .changelog/<branch-or-slug>-<short-desc>.md has one valid entry in this PR for any user-facing change (Added/Changed/Deprecated/Removed/Fixed/Security) — see .changelog/README.md; internal-only test/refactor PRs may skip it
  • Commit subject includes the issue number: (closes #NNN) or (refs #NNN)

Tests

  • Added a regression test pinning a full ring's latency attachment (red on pre-fix source); edited two existing tests to drop their now-obsolete empty-ring mock.
Red (commit 8523f80, candidate test file): 25 passed, 3 failed
Green (commit de07e39): 28 passed, 0 failed

Test assessment

This file pins analyzeFile's per-runner result shape; nothing is made redundant.

Blast radius

Callers: lens-engine.ts, runtime-tool-call.ts, complexity-client.ts, generated-artifacts.ts, mcp/review.ts. Only which report object is selected changes; same bounded-array scan cost.

Observability

No new failure path; no record added.

Class sweep

Length-delta matching against a shifting ring; the only such consumer among the tree's other capacity-bounded rings (degradation-ledger.ts, partial-edit-apply.ts, lsp/client.ts's stderrRing, runtime-coordinator.ts's _mutationReceipts):

$ grep -rn "reportsBefore" clients tools commands index.ts   # pre-fix: only analyze.ts

Class of size 1.

…ntza#3642)

analyzeFile snapshotted getLatencyReports().length before the dispatch
and sliced the post-dispatch array from that offset to find the report
the dispatch had just appended. dispatcher.ts pushes onto a 100-entry
ring and shifts once it is over capacity, so a full ring's length stays
at 100 across the push. Once 100 dispatches had run, the length delta
was always 0 and the slice always empty, so analyzeFile silently
returned undefined for `latency`, `fileKind`, and `lsp` on every
analysis after that point.

Match against the full current ring by resolved path instead, scanning
from the tail so the newest report for a path wins over a stale one
further back in the ring.
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your first pull request! A maintainer will review it soon. Please make sure CI passes and the PR template is filled out.

…efs apmantza#3643)

The ring stores report objects and getLatencyReports() returns a shallow copy,
so a reference delta survives the push+shift at capacity where a length delta
cannot. Take the single appended reference; with concurrent appends keep only
pathsEqual matches and never fall back to another file's report.
…efs apmantza#3643)

The exact-pin glossary sweep pins the `filter` identifier per file; the two
.filter(...) calls raised clients/mcp/analyze.ts from 1 to 3 against a pin of 1,
whose remediation is to route around the spelling rather than raise the pin.
Both loops are equivalent and keep newest-match-wins. The test also spread a
`never`-typed literal (TS2698), so the cast now sits on the array the mock
consumes.
)

The dispatcher already knows which report it appended, so return it instead of
letting the caller infer it from the ring. That deletes the before/after
reference scan in analyze.ts and closes the residual same-path concurrency
ambiguity: each caller receives its own report. The glossary pin for
clients/mcp/analyze.ts shrinks 6 -> 5 with the removed branch.
@sonarqubecloud

Copy link
Copy Markdown

@apmantza
apmantza merged commit f5af098 into apmantza:master Sep 29, 2026
38 checks passed
@apmantza

Copy link
Copy Markdown
Owner

thanks @drakeo338. merged.

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.

pilens_analyze drops the latency field once the dispatcher ring is full

3 participants