Skip to content

fix(local-files): floor mtimeMs to integer for BIGINT compatibility#79

Merged
MAnders333 merged 1 commit into
MAnders333:mainfrom
dnnspaul:fix/local-files-mtime-integer-coercion
May 26, 2026
Merged

fix(local-files): floor mtimeMs to integer for BIGINT compatibility#79
MAnders333 merged 1 commit into
MAnders333:mainfrom
dnnspaul:fix/local-files-mtime-integer-coercion

Conversation

@dnnspaul
Copy link
Copy Markdown
Contributor

Summary

fs.Stats.mtimeMs is documented as a float that preserves sub-millisecond precision when the underlying filesystem supports it (APFS on macOS, ext4 / btrfs / xfs on Linux, etc.). The local-files reader propagates that float through the consolidation pipeline into the Postgres BIGINT columns of knowledge_entry, which Postgres rejects strictly.

Reproduction

A Postgres-backed deployment ingesting a .md file via the local-files reader fails on every novel-entry insert with:

PostgresError: invalid input syntax for type bigint: "1779275328228.197"

Example from a real run (17 extracted entries from a single Markdown source, all 17 inserts failed):

[ERROR] [consolidation/local-files] Failed to insert novel entry "…" — skipping:
  PostgresError: invalid input syntax for type bigint: "1779275328228.197"

Critically, UPDATEs of existing entries succeed in the same run because they use Date.now() (integer) for the new updated_at, not entryTime. This produces a confusing partial-success failure mode: extraction + the synthesis pass run normally, but no rows from local-files reach the store.

SQLite users would never see this because SQLite coerces floats to integers via type affinity on INTEGER columns. The bug only surfaces against Postgres.

Cause

Tracing the float from source to sink:

statSync().mtimeMs  ←  float, sub-ms precision (e.g. 1779275328228.197)
   in src/daemon/readers/local-files.ts:99 and :174
  ↓
episode.maxMessageTime / episode.timeCreated
  ↓
consolidate.ts:556-562   chunkSessionTimestamp = max(e.maxMessageTime, …)
  ↓
reconsolidate.ts:699-700 entryTime = Math.min(sessionTimestamp, now)
  ↓
KnowledgeEntry.{createdAt, updatedAt, lastAccessedAt}
  ↓
INSERT INTO knowledge_entry (… created_at BIGINT, updated_at BIGINT, …)

Other readers (opencode, claude-code, codex, cursor, vscode) source their maxMessageTime from already-integer values stored in upstream session DBs/JSONL, so they aren't affected. local-files is the only reader that pulls directly from a Node fs.Stats float.

Fix

Math.floor(statSync(...).mtimeMs) at both statSync call sites in src/daemon/readers/local-files.ts. All downstream consumers inherit the integer.

No schema or API change. No behavioural change beyond removing sub-ms precision (which can never be persisted into a BIGINT ms column anyway).

Verification

  • bun test tests/episodes-local-files.test.ts — all 27 tests pass
  • bun test (full suite) — no new failures introduced (one unrelated config-loading test was already failing on main before this branch)
  • bunx biome check src/daemon/readers/local-files.ts — no new lint findings (the existing import-order warning on this file is pre-existing on main)

Notes for reviewers

I considered a defence-in-depth Math.floor in consolidate.ts where chunkSessionTimestamp is computed, but kept the patch minimal to the actual source. Happy to add it if you'd prefer the consolidator to never trust reader-supplied timestamps.

fs.Stats.mtimeMs is documented as a float that preserves sub-millisecond
precision when the underlying filesystem supports it (APFS, ext4, btrfs,
etc.). The local-files reader propagated this float through:

  statSync().mtimeMs (float)
    → episode.maxMessageTime / timeCreated
    → consolidate.ts chunkSessionTimestamp
    → reconsolidate.ts entryTime
    → KnowledgeEntry.{createdAt,updatedAt,lastAccessedAt}
    → INSERT INTO knowledge_entry (… BIGINT cols)

SQLite tolerates the float via type-affinity coercion, but Postgres
rejects it strictly:

  PostgresError: invalid input syntax for type bigint: "1779275328228.197"

Every novel-entry insert from a local-files-sourced session fails on a
Postgres-backed deployment, while updates (which use Date.now()) succeed —
producing a confusing partial-success failure mode where extraction works
but no rows reach the store.

Fix: Math.floor(stat.mtimeMs) at both statSync call sites in the reader,
so all downstream uses inherit a guaranteed-integer value. No schema or
API change. Existing 27 local-files tests still pass.
@MAnders333 MAnders333 merged commit 66e1135 into MAnders333:main May 26, 2026
2 checks passed
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