From 3f7bb047acfac2d374282032591e7437bc3588d9 Mon Sep 17 00:00:00 2001 From: Brian Spackman <283016431+brian-spackman@users.noreply.github.com> Date: Tue, 12 May 2026 13:28:03 -0600 Subject: [PATCH] fix: floor sub-millisecond CLI mtimes to prevent mobile crash On Linux, fs.Stats.mtimeMs and ctimeMs return floats with sub-millisecond precision derived from the kernel's nanosecond filesystem mtime. Stored raw, this produces document timestamps like 1778511180024.462 in CouchDB rather than integer milliseconds. Mobile clients running LiveSync 0.25.60 have been observed to crash when processing change-feed updates carrying non-integer millisecond timestamps from CLI-written documents. Desktop and mobile GUI plugins write integer milliseconds, so the crash only manifests when the headless CLI on Linux is the source. Whether the issue was introduced in 0.25.60 or had been latent in earlier versions hasn't been investigated; 0.25.60 is the version where the crash was confirmed and the fix verified. Floor the values at every stat-read site (six across three adapters and one command) so CLI-written documents carry integer-millisecond timestamps consistent with the rest of the mesh. --- src/apps/cli/adapters/NodeFileSystemAdapter.ts | 8 ++++---- src/apps/cli/adapters/NodeStorageAdapter.ts | 4 ++-- src/apps/cli/adapters/NodeVaultAdapter.ts | 8 ++++---- src/apps/cli/commands/runCommand.ts | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/apps/cli/adapters/NodeFileSystemAdapter.ts b/src/apps/cli/adapters/NodeFileSystemAdapter.ts index b90ad73..d1b6764 100644 --- a/src/apps/cli/adapters/NodeFileSystemAdapter.ts +++ b/src/apps/cli/adapters/NodeFileSystemAdapter.ts @@ -104,8 +104,8 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter { const stat = await fs.stat(this.resolvePath(p)); return { size: stat.size, - mtime: stat.mtimeMs, - ctime: stat.ctimeMs, + mtime: Math.floor(stat.mtimeMs), + ctime: Math.floor(stat.ctimeMs), type: stat.isDirectory() ? "folder" : "file", }; } catch { diff --git a/src/apps/cli/adapters/NodeVaultAdapter.ts b/src/apps/cli/adapters/NodeVaultAdapter.ts index 947ad01..fc10577 100644 --- a/src/apps/cli/adapters/NodeVaultAdapter.ts +++ b/src/apps/cli/adapters/NodeVaultAdapter.ts @@ -66,8 +66,8 @@ export class NodeVaultAdapter implements IVaultAdapter { path: p as any, stat: { size: stat.size, - mtime: stat.mtimeMs, - ctime: stat.ctimeMs, + mtime: Math.floor(stat.mtimeMs), + ctime: Math.floor(stat.ctimeMs), type: "file", }, }; @@ -89,8 +89,8 @@ export class NodeVaultAdapter implements IVaultAdapter { path: p as any, stat: { size: stat.size, - mtime: stat.mtimeMs, - ctime: stat.ctimeMs, + mtime: Math.floor(stat.mtimeMs), + ctime: Math.floor(stat.ctimeMs), type: "file", }, }; diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index e188c23..c888855 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -83,8 +83,8 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext console.log(`[Command] push ${sourcePath} -> ${destinationDatabasePath}`); await core.serviceModules.storageAccess.writeFileAuto(destinationDatabasePath, toArrayBuffer(sourceData), { - mtime: sourceStat.mtimeMs, - ctime: sourceStat.ctimeMs, + mtime: Math.floor(sourceStat.mtimeMs), + ctime: Math.floor(sourceStat.ctimeMs), }); const destinationPathWithPrefix = destinationDatabasePath as FilePathWithPrefix; const stored = await core.serviceModules.fileHandler.storeFileToDB(destinationPathWithPrefix, true);