Skip to content

Windows: mtime encoding split makes the incremental classifier see every file as changed #1917

Open

Description

@DeusData

Two Windows-only comparisons can never match, because the two sides encode file mtime differently. Found while diagnosing a Windows-only failure in #1905, where the same split had a third instance (fixed there); these two are pre-existing and were deliberately left alone because fixing them changes broad indexing behaviour.

The encoding split

file_hashes.mtime_ns is stamped by semantic_manifest_hash_file → cbm_path_info_utf8 (pipeline_incremental.c:186), whose Windows branch derives the value from ftLastWriteTime — 100-nanosecond FILETIME ticks (compat_fs.c:146-152).

Both sites below re-read the same files with a stat-family call and encode st_mtime * 1e9 — whole seconds.

The two agree only when a file's write time lands exactly on a second boundary. On POSIX there is no split at all: both paths read st_mtim nanoseconds.

Site 1 — the incremental classifier sees every file as changed

src/pipeline/pipeline_incremental.c:693:

if (stat_mtime_ns(&st) != h->mtime_ns || st.st_size != h->size) {
    changed[i] = true;

On Windows the left side is whole seconds and the right side is FILETIME ticks, so this is true for essentially every file. Every incremental reindex on Windows re-parses the entire project.

It fails safe — over-reporting change can only cost time, never correctness — which is why this has gone unnoticed. But it means Windows users have effectively never had incremental indexing, only a full reindex wearing its name. Given how much of this project's performance work targets the warm path (delta merge, closure repair, the 121.7s kernel warm figure), that is a substantial platform gap hiding behind a benign failure mode.

Site 2 — check_index_coverage reports metadata_changed for everything

src/mcp/mcp.c (check_index_coverage, the row-vs-disk comparison) has the same shape, so on Windows it reports metadata_changed for every file it inspects. That is a user-visible wrong answer from a tool whose entire job is telling you whether the index is current.

Why this is filed rather than fixed

Both changes alter broad indexing behaviour. Site 1 in particular would flip Windows from "always full reindex" to "genuinely incremental", which is a real behavioural change deserving its own measurement — and a bug that has been failing safe for a long time should not be fixed as a rider on an unrelated PR.

Suggested shape

Read the disk side through cbm_path_info_utf8 — the same function that wrote the row — so there is no encoding to keep in sync. That is what #1905 does for the reconcile path, and both sites there now carry comments pinning the asymmetry where a whole-second stat is deliberately retained.

Note the two encodings cannot simply be unified everywhere: restamped rows are consumed by these very call sites, so changing one side without the other moves the mismatch rather than removing it. Whichever site is fixed first should assert its own round-trip.

Verification note

The split is visible in both code paths and was reproduced on macOS by emulating the Windows encoding (truncating the disk side to whole seconds), which reproduced #1905's Windows CI signature exactly — 7 failing sites, matching. It has not been executed on Windows; that confirmation is still outstanding.

Activity

  1. quakquak86 commented on Aug 30, 2026

    @quakquak86

    Windows confirmation from codebase-memory-mcp 0.10.8 on Windows 11 Pro build 26200 / NTFS:

    I can reproduce Site 2 directly on Windows with a fresh synthetic two-file TypeScript repo. Immediately after a successful index_repository --mode fast, with neither file modified after indexing, check_index_coverage reports freshness: "metadata_changed" and recommended_action: "read_source_and_reindex" for both files.

    Observed response excerpt:

    {
      "recording_status": "complete",
      "hash_records_complete": true,
      "generation_matches": true,
      "paths": [
        {"path":"game/src/math.ts","status":"no_recorded_issue","freshness":"metadata_changed"},
        {"path":"game/src/use.ts","status":"no_recorded_issue","freshness":"metadata_changed"}
      ]
    }

    This matches the Windows mtime encoding split described here and is also consistent with #1714. We currently work around it locally only after verifying SHA-256 and file size still match the indexed row; the native coverage result itself is otherwise unusable as a freshness signal on Windows 0.10.8.

  2. added
    bugSomething isn't working
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Sep 5, 2026
  3. DeusData commented on Oct 25, 2026

    @DeusData
    OwnerAuthor

    Thank you for the native Windows confirmation of the coverage freshness mismatch. That addresses Site 2; the incremental-classifier behavior remains a separate check. Sorry for the delay; this is waiting on maintainer follow-up, and I’ll follow up soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.windowsWindows-specific issues

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions