Skip to content

perf: a file already outside the window keeps its empty contribution (#99) - #119

Merged
Carl723000 merged 2 commits into
ClaudeCodeUsage:mainfrom
rsyuzyov:perf/expired-file-rebase-keeps-payload
Sep 30, 2026
Merged

Carl723000 merged 2 commits into
ClaudeCodeUsage:mainfrom
rsyuzyov:perf/expired-file-rebase-keeps-payload

Conversation

@rsyuzyov

Copy link
Copy Markdown
Contributor

Measured on a 979-file history with several agents appending: the append fast
path never ran. Every refresh reported 53 analysis payload rebases, and any
payload rebase disables fastAppendAnalysis, so each tick of a few kilobytes
paid the slow path — rebuilding the seen-UUID set over the whole history.

The 53 files are the ones whose every event is older than the window. On each
refresh the cutoff moves, rebaseFileAnalysisCutoff takes the "last event is
before the cutoff" branch and returns a fresh newAnalysisAcc. The payload is
empty before and after, but sameAnalysisPayload compares collections by
identity (by design: a metadata-only rebase keeps them), so every such file
reads as changed on every refresh, for good.

That branch is now taken only when the last event has just left the window
(last >= prior.analysis.cutoffMs). A file that was already past the previous
cutoff is empty already — analyzeLine ignores lines older than the cutoff —
and falls through to the metadata-only rebase, keeping its collections.

On the same history: payloadRebases 53 -> 0 on every refresh.

Test: a file already outside the window keeps its empty contribution as the window drifts — it fails on the parent commit ("Values have same structure but
are not reference-equal") and asserts the analysis equals the full loader's.

Context for #99: this is the first of the remaining per-tick costs found by
profiling a live history (979 files, ~17 concurrent agent sessions). The
largest one left after this is the continuously rolling cutoff re-reading whole
files whose events sit at the 30-day boundary — I'll open that separately, since
it touches the full loader's cutoff contract and deserves its own discussion.

Local note: on Windows a 645-session corpus reads only seven live tails… and
default content analysis does not revisit historical… fail on main as well
(NTFS reuses file identities); they pass in CI.

🤖 Generated with Claude Code

…laudeCodeUsage#99)

Measured on a 979-file history with several agents appending: the append fast
path never ran. Every refresh reported 53 analysis payload rebases, and any
payload rebase disables `fastAppendAnalysis`, so each tick of a few kilobytes
paid the slow path — rebuilding the seen-UUID set over the whole history.

The 53 files are the ones whose every event is older than the window. On each
refresh the cutoff moves, `rebaseFileAnalysisCutoff` takes the "last event is
before the cutoff" branch and returns a fresh `newAnalysisAcc`. The payload is
empty before and after, but `sameAnalysisPayload` compares collections by
identity (by design: a metadata-only rebase keeps them), so every such file
reads as changed on every refresh, for good.

That branch is now taken only when the last event has just left the window
(`last >= prior.analysis.cutoffMs`). A file that was already past the previous
cutoff is empty already — `analyzeLine` ignores lines older than the cutoff —
and falls through to the metadata-only rebase, keeping its collections.

On the same history: `payloadRebases` 53 -> 0 on every refresh.

Test: `a file already outside the window keeps its empty contribution as the
window drifts` — it fails on the parent commit ("Values have same structure but
are not reference-equal") and asserts the analysis equals the full loader's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🤖 Automated first-pass review

TL;DR

The fix is well-reasoned and the added test looks like a genuine red-green case. Two gaps stand out before merge: no CHANGELOG.md entry (this is an observable performance behavior change), and the new guard condition in rebaseFileAnalysisCutoff adds a subtle branch that deserves a comment in the commit/PR description about the prior.analysis.cutoffMs comparison semantics. Otherwise it is close to merge-ready.

Analysis

Correctness of the change:

  • The new condition last < cutoffMs && last >= prior.analysis.cutoffMs correctly narrows the "just left the window" case to the single refresh where the boundary crosses the file's last event. A file whose last was already below the prior cutoff now falls through to the metadata-only rebase, keeping its collections. That matches the stated invariant (analyzeLine ignores lines older than the cutoff), and the !prior.analysisHasUnboundedTimestamp guard is preserved, so unbounded-timestamp files still take the emptying branch.
  • One edge to double-check: last === current cutoff vs the strict <. Since the original branch used last < cutoffMs, and both sides share the same < against cutoffMs, the boundary behavior is unchanged for the "just left" case. If last can equal cutoffMs exactly, the file is still considered in-window and this branch is not taken — consistent with prior behavior.
  • The test asserts warm.diagnostics.bodyReads === 1 (only the active tail is read) and references the append fast path staying on. This is the most valuable assertion because it proves the perf claim, not just the collection identity.

Test quality:

  • Freezing Date.now and advancing by 60s per step is a good way to make the window drift deterministic. The final assert.deepEqual(warm.contentAnalysis, full.contentAnalysis) cross-checks against the full loader, which is the right oracle for this module.
  • The iteration runs 3 steps; that is enough to exercise the "already outside for multiple refreshes" path, but consider whether one step is truly sufficient to distinguish "just left" from "was already outside" — 3 steps covers both, so it is fine.

Gaps / merge-readiness:

  • AGENTS.md / CONTRIBUTING.md both require a CHANGELOG.md entry for user-facing behavior changes. Even though this is a perf-only change with no visible UI, the refresh behavior (fewer payload rebases) is observable in diagnostics, so a short entry is warranted.
  • The change touches claudeIncrementalIndex.ts, which AGENTS.md calls out as owning "exact compatibility" semantics. No i18n or README update is needed here since nothing user-facing changed; confirm that is intentional.
  • The PR body explains the "empty before and after but identity-comparison says changed" root cause well; consider lifting one sentence of that reasoning into the inline comment so a future reader of the removed branch does not re-add the condition.
  • Local note about two Windows-only failures on main is responsibly disclosed; CI is the source of truth, so no action needed unless those tests are newly flaky.

Suggested next step(s)

  1. Add a CHANGELOG.md entry (e.g. under the unreleased/patch section) describing the append fast path no longer being disabled by already-expired files.
  2. Optionally expand the inline comment to name sameAnalysisPayload's identity comparison as the reason the fresh collections looked changed, so the guard is not removed as "redundant".
  3. Confirm maintainer intent on the "rolling cutoff re-reads whole files at the 30-day boundary" follow-up — the PR defers it correctly, but a tracked issue would help.

🤖 Generated by DeepSeek as an automated first pass — not a maintainer decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@Carl723000 Carl723000 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the empty-contribution and cutoff-metadata paths, including expiry, appends and backward cutoff movement. The original regression and the combined Node suite pass; the required Node, UI and package checks are green. No blocking finding.


🤖 Generated with OpenAI Codex

@Carl723000
Carl723000 merged commit 7aa6946 into ClaudeCodeUsage:main Sep 30, 2026
5 checks passed
@Carl723000

Copy link
Copy Markdown
Collaborator

Merged. Thank you @rsyuzyov for the profiling, minimal fix and regression test. Keeping already-expired contributions empty without recreating their collections removes a real obstacle to incremental refreshes. This is merged into main; it is not yet a published extension update.


🤖 Generated with OpenAI Codex

Carl723000 pushed a commit that referenced this pull request Oct 4, 2026
…it (#99) (#123)

* perf: a new transcript no longer re-tags every file discovered after it (#99)

Discovery order only breaks ties between files with the same first timestamp,
but it is stored as the manifest position. A transcript appearing (a new
session, and every sub-agent run) shifts the position of every file after it,
and each shifted file went through `retagFileOrdering`: all of its records were
removed and re-added, re-bucketing every aggregate they touch.

Measured on a live history (1,043 files, a new file at position 312): one new
transcript cost 20.7 s of CPU and 116,131 aggregate mutations for a single
body read. The extension log of one working day showed ~25 such refreshes at
15-40 s each — after #119-#121 the largest remaining cost of a refresh.

Files the index already holds now keep their stored rank as long as their
relative order is unchanged, and a new file takes a rank between its
neighbours. The order is the manifest's, so every tie-break still agrees with
the full loader. If known files change their relative order, or the gap
between two neighbours runs out, the manifest positions are used as before.
Ranks are only compared, never used as positions.

Same measurement with the change: 2.2 s of CPU and 19 aggregate mutations.

Test: `a new transcript does not re-tag the files discovered after it` inserts
a file in the middle of twelve, tying with its right neighbour on first
timestamp and message so that only discovery order picks the counted copy. It
fails on the parent commit (33 mutations for 1 body read), asserts that the
full loader counts the new file's copy, and that records and content analysis
equal the full loader's — also after two known files swap order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: changelog entry for stable discovery ranks (#99)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants