From 3166730e368ab48b635eb9575829187cce5e8e7a Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:50:05 +0200 Subject: [PATCH] fix(server): let Git observation measurements reach their summary Startup discovery queues a refresh beyond the old 250 ms wait. Wait for Git command quiescence at phase boundaries so idle measurements exclude startup work, and pass the observer to healthy teardown before measuring degraded polling. --- .../server/scripts/measure-git-observation.ts | 11 +++++- ...it-service.observation.integration.test.ts | 38 ++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/packages/server/scripts/measure-git-observation.ts b/packages/server/scripts/measure-git-observation.ts index b292c0e065..e2aa3281ff 100644 --- a/packages/server/scripts/measure-git-observation.ts +++ b/packages/server/scripts/measure-git-observation.ts @@ -12,6 +12,7 @@ import { import { startGitCommandMetrics, stopGitCommandMetrics, + waitForGitCommandMetricsIdle, type GitCommandMetricsSnapshot, } from "../src/utils/run-git-command.js"; import { CheckoutDiffManager } from "../src/server/checkout-diff-manager.js"; @@ -160,6 +161,7 @@ async function measurePhase(input: { serviceMetrics.workspaceRefreshQueuedCount === 0 ); }, "observation queue to drain"); + await waitForGitCommandMetricsIdle({ quietMs: 1_500, timeoutMs: 8_000 }); } finally { metrics = stopGitCommandMetrics(); } @@ -243,7 +245,9 @@ async function main(): Promise { healthy.watcher.subscriptions.size === 2, "healthy bootstrap", ); - await sleep(250); + // Linux startup discovery schedules a one-second debounced refresh even + // after subscriptions are ready. Keep that work in the bootstrap phase. + await waitForGitCommandMetricsIdle({ quietMs: 1_500, timeoutMs: 8_000 }); const bootstrapMetrics = stopGitCommandMetrics(); const bootstrapServiceMetrics = healthy.service.getMetrics(); phases.bootstrap = { @@ -342,6 +346,7 @@ async function main(): Promise { diffManager = null; await closeMeasuredService({ service: healthy.service, + observer: healthy.watcher.observer, subscriptions: healthy.watcher.subscriptions, }); healthy = null; @@ -357,7 +362,9 @@ async function main(): Promise { degraded.service.getMetrics().workspaceObservationSetupInFlightCount === 0, "degraded bootstrap", ); - await sleep(250); + // Linux startup discovery schedules a one-second debounced refresh even + // after subscriptions are ready. Keep that work in the bootstrap phase. + await waitForGitCommandMetricsIdle({ quietMs: 1_500, timeoutMs: 8_000 }); const degradedBootstrapMetrics = stopGitCommandMetrics(); const degradedBootstrapServiceMetrics = degraded.service.getMetrics(); phases.degradedBootstrap = { diff --git a/packages/server/src/server/workspace-git-service.observation.integration.test.ts b/packages/server/src/server/workspace-git-service.observation.integration.test.ts index 8ac1ef0018..b02289becb 100644 --- a/packages/server/src/server/workspace-git-service.observation.integration.test.ts +++ b/packages/server/src/server/workspace-git-service.observation.integration.test.ts @@ -1,5 +1,7 @@ import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; -import { execFileSync } from "node:child_process"; +import { execFile, execFileSync } from "node:child_process"; +import { promisify } from "node:util"; +import { fileURLToPath } from "node:url"; import { tmpdir } from "node:os"; import path from "node:path"; import pino from "pino"; @@ -522,3 +524,37 @@ test("a late Git-ignored tree is pruned while tracked changes still notify consu rmSync(tempDir, { recursive: true, force: true }); } }, 15_000); + +test("Git observation measurement separates startup from idle and reaches its degraded summary", async () => { + const script = fileURLToPath( + new URL("../../scripts/measure-git-observation.ts", import.meta.url), + ); + const { stdout } = await promisify(execFile)(process.execPath, ["--import", "tsx", script], { + timeout: 30_000, + maxBuffer: 1024 * 1024, + }); + const summary = stdout + .split("\n") + .find((line) => line.startsWith("GIT_OBSERVATION_MEASUREMENT ")); + expect(summary).toBeTypeOf("string"); + const report = JSON.parse(summary!.slice("GIT_OBSERVATION_MEASUREMENT ".length)); + expect(Object.keys(report.phases)).toEqual([ + "bootstrap", + "legacyFullRefreshReference", + "idle", + "activeDiff", + "editBurst", + "ignoredStorm", + "degradedBootstrap", + "degradedWatcher", + ]); + expect(report.phases.idle.gitCommands).toBe(0); + expect(report.phases.ignoredStorm.gitCommands).toBe(0); + expect(report.phases.bootstrap.producers.structural).toBeGreaterThanOrEqual(1); + expect(report.phases.activeDiff.producers.worktree).toBeGreaterThanOrEqual(1); + expect(report.phases.editBurst.producers.worktree).toBeLessThanOrEqual(2); + expect(report.phases.degradedWatcher.producers.worktree).toBeGreaterThanOrEqual(1); + for (const phase of Object.values(report.phases) as Array<{ queueAfter: unknown }>) { + expect(phase.queueAfter).toEqual({ inFlight: 0, queued: 0 }); + } +}, 35_000);