fix(daemon): tolerate coarse artifact mtimes

This commit is contained in:
Denis Redozubov 2026-05-28 22:00:11 +04:00
parent 755d8173df
commit 67396350c8
3 changed files with 13 additions and 3 deletions

View file

@ -32,10 +32,16 @@ const FORBIDDEN_SEGMENT = /^$|^\.\.?$/;
const RESERVED_PROJECT_FILE_SEGMENTS = new Set(['.live-artifacts']); const RESERVED_PROJECT_FILE_SEGMENTS = new Set(['.live-artifacts']);
const DESIGN_HANDOFF_FILENAME = 'DESIGN-HANDOFF.md'; const DESIGN_HANDOFF_FILENAME = 'DESIGN-HANDOFF.md';
const DESIGN_MANIFEST_FILENAME = 'DESIGN-MANIFEST.json'; const DESIGN_MANIFEST_FILENAME = 'DESIGN-MANIFEST.json';
export const RUN_ARTIFACT_RECONCILE_MTIME_GRACE_MS = 1000;
export const projectFileRenameTestHooks = { export const projectFileRenameTestHooks = {
beforeCommit: null as null | ((paths: { source: string; target: string }) => Promise<void> | void), beforeCommit: null as null | ((paths: { source: string; target: string }) => Promise<void> | void),
}; };
export function isRunTouchedProjectFile(fileMtimeMs, runStartTimeMs) {
if (!Number.isFinite(fileMtimeMs) || !Number.isFinite(runStartTimeMs)) return false;
return fileMtimeMs + RUN_ARTIFACT_RECONCILE_MTIME_GRACE_MS >= runStartTimeMs;
}
export function projectDir(projectsRoot, projectId) { export function projectDir(projectsRoot, projectId) {
if (!isSafeId(projectId)) throw new Error('invalid project id'); if (!isSafeId(projectId)) throw new Error('invalid project id');
return path.join(projectsRoot, projectId); return path.join(projectsRoot, projectId);

View file

@ -353,6 +353,7 @@ import {
assertSandboxProjectRootAvailable, assertSandboxProjectRootAvailable,
detectEntryFile, detectEntryFile,
ensureProject, ensureProject,
isRunTouchedProjectFile,
isSafeId, isSafeId,
listFiles, listFiles,
mimeFor, mimeFor,
@ -12751,7 +12752,7 @@ export async function startServer({
try { try {
const filePath = path.join(dir, f.name); const filePath = path.join(dir, f.name);
const st = await fs.promises.stat(filePath); const st = await fs.promises.stat(filePath);
if (st.mtimeMs < runStartTimeMs) continue; if (!isRunTouchedProjectFile(st.mtimeMs, runStartTimeMs)) continue;
await reconcileHtmlArtifactManifest( await reconcileHtmlArtifactManifest(
PROJECTS_DIR, PROJECTS_DIR,
run.projectId, run.projectId,

View file

@ -9,7 +9,7 @@ import fs from 'node:fs';
import os from 'node:os'; import os from 'node:os';
import path from 'node:path'; import path from 'node:path';
import { closeDatabase, insertProject, openDatabase } from '../src/db.js'; import { closeDatabase, insertProject, openDatabase } from '../src/db.js';
import { reconcileHtmlArtifactManifest, writeProjectFile } from '../src/projects.js'; import { isRunTouchedProjectFile, reconcileHtmlArtifactManifest, writeProjectFile } from '../src/projects.js';
const PROJECT_ID = 'reconcile-test'; const PROJECT_ID = 'reconcile-test';
let tempDir = null; let tempDir = null;
@ -146,6 +146,9 @@ describe('run-end artifact manifest reconciliation (#2893)', () => {
// File written during the run // File written during the run
await writeProjectFile(projectsRoot, PROJECT_ID, 'new-output.html', '<p>new</p>'); await writeProjectFile(projectsRoot, PROJECT_ID, 'new-output.html', '<p>new</p>');
const newPath = path.join(projectsRoot, PROJECT_ID, 'new-output.html');
const coarseFsTime = new Date(runStartTimeMs - 500);
fs.utimesSync(newPath, coarseFsTime, coarseFsTime);
// Simulate the close-handler reconciliation with mtime filter // Simulate the close-handler reconciliation with mtime filter
const dir = path.join(projectsRoot, PROJECT_ID); const dir = path.join(projectsRoot, PROJECT_ID);
@ -154,7 +157,7 @@ describe('run-end artifact manifest reconciliation (#2893)', () => {
const ext = path.extname(name).toLowerCase(); const ext = path.extname(name).toLowerCase();
if (ext !== '.html' && ext !== '.htm') continue; if (ext !== '.html' && ext !== '.htm') continue;
const st = fs.statSync(path.join(dir, name)); const st = fs.statSync(path.join(dir, name));
if (st.mtimeMs < runStartTimeMs) continue; if (!isRunTouchedProjectFile(st.mtimeMs, runStartTimeMs)) continue;
await reconcileHtmlArtifactManifest(projectsRoot, PROJECT_ID, name); await reconcileHtmlArtifactManifest(projectsRoot, PROJECT_ID, name);
} }