From eddd8f606678918ce36c11b5f9efd686064595d1 Mon Sep 17 00:00:00 2001 From: Ryan Groch Date: Tue, 25 Aug 2026 18:41:05 -0500 Subject: [PATCH 1/5] feat: report disk capacity on crash telemetry events Adds last_known_disk_total_mb, last_known_disk_used_mb and last_known_disk_available_mb to app:crash_detected and renderer:crash_detected, so we can see whether crashes line up with a full disk. The performance monitor already saves a snapshot every 30s that the next launch attaches to both crash events, so this is one statfs call added to that snapshot. It measures the user data volume, not the apps folder. The same tick already reads and writes user-settings.json there, so the statfs adds no blocking the main thread did not already have; the apps folder is user-configurable and could be a network mount. For a default install they are the same volume. Used and available are both reported because they are not interchangeable: every platform holds some space back from ordinary writes, so total minus used overstates the room the user really had. Available is the one to threshold on. If the statfs fails the fields are omitted rather than reported as zero, and they are optional in the schema so older snapshots still parse. Measuring how much disk Dyad itself uses is out of scope, since that needs a recursive walk of every app directory. Co-Authored-By: Claude Opus 5 --- e2e-tests/performance_monitor.spec.ts | 10 ++++ src/lib/schemas.ts | 6 +++ src/utils/crash_telemetry_fields.test.ts | 9 ++++ src/utils/crash_telemetry_fields.ts | 3 ++ src/utils/disk_usage.test.ts | 63 ++++++++++++++++++++++++ src/utils/disk_usage.ts | 31 ++++++++++++ src/utils/performance_monitor.ts | 10 ++++ 7 files changed, 132 insertions(+) create mode 100644 src/utils/disk_usage.test.ts create mode 100644 src/utils/disk_usage.ts diff --git a/e2e-tests/performance_monitor.spec.ts b/e2e-tests/performance_monitor.spec.ts index b9a74d895c..3f1e04aa3c 100644 --- a/e2e-tests/performance_monitor.spec.ts +++ b/e2e-tests/performance_monitor.spec.ts @@ -146,6 +146,16 @@ testWithConfig({})( expect( settings.lastKnownPerformance.systemCpuPercent, ).toBeGreaterThanOrEqual(0); + // statfs works on every platform we ship, so a real capture always has + // disk figures. Used never exceeds total; available can trail both. + expect(settings.lastKnownPerformance.diskTotalMB).toBeGreaterThan(0); + expect(settings.lastKnownPerformance.diskUsedMB).toBeGreaterThan(0); + expect(settings.lastKnownPerformance.diskUsedMB).toBeLessThanOrEqual( + settings.lastKnownPerformance.diskTotalMB, + ); + expect(settings.lastKnownPerformance.diskAvailableMB).toBeLessThanOrEqual( + settings.lastKnownPerformance.diskTotalMB, + ); // Verify the timestamp is recent (within the last minute) const now = Date.now(); diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts index f69c291f0f..8d99ff160e 100644 --- a/src/lib/schemas.ts +++ b/src/lib/schemas.ts @@ -352,6 +352,12 @@ export const LastKnownPerformanceSchema = z.object({ systemMemoryUsageMB: z.number().optional(), systemMemoryTotalMB: z.number().optional(), systemCpuPercent: z.number().optional(), + // Capacity of the volume holding the user data directory. diskUsedMB counts + // every allocated block; diskAvailableMB excludes space the platform holds + // back (root reserve, quota), so the two need not sum to diskTotalMB. + diskTotalMB: z.number().optional(), + diskUsedMB: z.number().optional(), + diskAvailableMB: z.number().optional(), // Main process V8 heap, from v8.getHeapStatistics(). heapUsedMB: z.number().optional(), heapLimitMB: z.number().optional(), diff --git a/src/utils/crash_telemetry_fields.test.ts b/src/utils/crash_telemetry_fields.test.ts index 6fd0a361fb..8c6c7cd13a 100644 --- a/src/utils/crash_telemetry_fields.test.ts +++ b/src/utils/crash_telemetry_fields.test.ts @@ -12,6 +12,9 @@ describe("crashPerformanceEventFields", () => { memoryUsageMB: 400, heapUsedMB: 512, heapLimitMB: 4144, + diskTotalMB: 476000, + diskUsedMB: 401000, + diskAvailableMB: 51000, processWorkingSetsMB: { browser: 400, tab: 900, zygote: 30, unknown: 20 }, activity: { activeStreams: 1, @@ -45,6 +48,11 @@ describe("crashPerformanceEventFields", () => { expect(fields.peak_active_streams).toBe(2); expect(fields.peak_ts_utility_process).toBeNull(); expect(fields.peak_heap_used_mb).toBe(1024); + expect(fields.last_known_disk_total_mb).toBe(476000); + expect(fields.last_known_disk_used_mb).toBe(401000); + // Reported separately from used because space the platform withholds + // sits between them: used + available is short of total. + expect(fields.last_known_disk_available_mb).toBe(51000); // No object-valued properties: PostHog cannot filter nested JSON. for (const value of Object.values(fields)) { @@ -60,6 +68,7 @@ describe("crashPerformanceEventFields", () => { expect(fields.last_known_memory_mb).toBe(400); expect(fields.last_known_working_set_browser_mb).toBeUndefined(); + expect(fields.last_known_disk_total_mb).toBeUndefined(); expect(fields.last_known_active_streams).toBeUndefined(); expect(fields.peak_active_streams).toBeUndefined(); }); diff --git a/src/utils/crash_telemetry_fields.ts b/src/utils/crash_telemetry_fields.ts index c81ee7457f..dee8f04862 100644 --- a/src/utils/crash_telemetry_fields.ts +++ b/src/utils/crash_telemetry_fields.ts @@ -24,6 +24,9 @@ export function crashPerformanceEventFields( last_known_system_memory_mb: perf.systemMemoryUsageMB, last_known_system_memory_total_mb: perf.systemMemoryTotalMB, last_known_system_cpu_pct: perf.systemCpuPercent, + last_known_disk_total_mb: perf.diskTotalMB, + last_known_disk_used_mb: perf.diskUsedMB, + last_known_disk_available_mb: perf.diskAvailableMB, last_known_snapshot_timestamp: perf.timestamp, time_since_last_heartbeat_ms: Date.now() - perf.timestamp, last_known_heap_used_mb: perf.heapUsedMB, diff --git a/src/utils/disk_usage.test.ts b/src/utils/disk_usage.test.ts new file mode 100644 index 0000000000..655c72366c --- /dev/null +++ b/src/utils/disk_usage.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import fs from "node:fs"; +import { getDiskUsageMB } from "@/utils/disk_usage"; + +vi.mock("node:fs", () => ({ + default: { statfsSync: vi.fn() }, +})); + +const statfsSync = vi.mocked(fs.statfsSync); + +// 4KiB blocks: 262144 total = 1024MB, 65536 free = 256MB, 32768 available +// to non-root = 128MB. The gap between free and available is the reserve. +function statfsResult(overrides: Partial = {}): fs.StatsFs { + return { + type: 61267, + bsize: 4096, + blocks: 262144, + bfree: 65536, + bavail: 32768, + files: 0, + ffree: 0, + ...overrides, + } as fs.StatsFs; +} + +describe("getDiskUsageMB", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("converts blocks to MB and reports used and available separately", () => { + statfsSync.mockReturnValue(statfsResult()); + + expect(getDiskUsageMB("/some/path")).toEqual({ + totalMB: 1024, + // Every allocated block, including the root reserve. + usedMB: 768, + // Excludes the reserve, so used + available is short of total. + availableMB: 128, + }); + expect(statfsSync).toHaveBeenCalledExactlyOnceWith("/some/path"); + }); + + it("scales with the filesystem's block size", () => { + statfsSync.mockReturnValue( + statfsResult({ bsize: 1024, blocks: 2048, bfree: 1024, bavail: 1024 }), + ); + + expect(getDiskUsageMB("/some/path")).toEqual({ + totalMB: 2, + usedMB: 1, + availableMB: 1, + }); + }); + + it("returns null when the path cannot be read", () => { + statfsSync.mockImplementation(() => { + throw new Error("ENOENT"); + }); + + expect(getDiskUsageMB("/missing")).toBeNull(); + }); +}); diff --git a/src/utils/disk_usage.ts b/src/utils/disk_usage.ts new file mode 100644 index 0000000000..d9b21f278f --- /dev/null +++ b/src/utils/disk_usage.ts @@ -0,0 +1,31 @@ +import fs from "node:fs"; + +const BYTES_PER_MB = 1024 * 1024; + +export interface DiskUsageMB { + totalMB: number; + usedMB: number; + availableMB: number; +} + +/** + * Capacity of the filesystem holding `targetPath`, from one statfs syscall. + * usedMB counts every allocated block, while availableMB is what this user + * can actually write and is lower wherever the platform holds space back — + * a root reserve, a per-user quota. Returns null when the path is unreadable + * so callers can omit the fields rather than report a zero. + */ +export function getDiskUsageMB(targetPath: string): DiskUsageMB | null { + try { + const stats = fs.statfsSync(targetPath); + const toMB = (blocks: number) => + Math.round((blocks * stats.bsize) / BYTES_PER_MB); + return { + totalMB: toMB(stats.blocks), + usedMB: toMB(stats.blocks - stats.bfree), + availableMB: toMB(stats.bavail), + }; + } catch { + return null; + } +} diff --git a/src/utils/performance_monitor.ts b/src/utils/performance_monitor.ts index 3b276a9322..be1b395388 100644 --- a/src/utils/performance_monitor.ts +++ b/src/utils/performance_monitor.ts @@ -10,6 +10,8 @@ import { import { getActiveStreamCount } from "../ipc/handlers/chat_stream_handlers"; import { runningApps } from "../ipc/utils/process_manager"; import { typescriptUtilityProcessScheduler } from "../ipc/processors/typescript_utility_process_scheduler"; +import { getUserDataPath } from "../paths/paths"; +import { getDiskUsageMB } from "./disk_usage"; const logger = log.scope("performance-monitor"); @@ -231,6 +233,9 @@ function capturePerformanceMetrics() { : 0; const kernelPeakRssMB = getKernelPeakRssMB(); const activity = snapshotActivity(); + // The user data volume, not the apps volume: writeSettings below already + // blocks on it every tick, so this adds no new main-thread exposure. + const diskUsage = getDiskUsageMB(getUserDataPath()); logger.debug( `Performance: Memory=${memoryUsageMB}MB, Heap=${heapUsedMB}/${heapLimitMB}MB, All Processes=${allProcessesMemoryMB ?? "?"}MB, CPU=${cpuUsagePercent}%, System Memory=${systemMemory.usedMemoryMB}/${systemMemory.totalMemoryMB}MB (${systemMemory.usagePercent}%), System CPU=${systemCpuPercent}%`, @@ -271,6 +276,11 @@ function capturePerformanceMetrics() { systemMemoryUsageMB: systemMemory.usedMemoryMB, systemMemoryTotalMB: systemMemory.totalMemoryMB, systemCpuPercent, + ...(diskUsage && { + diskTotalMB: diskUsage.totalMB, + diskUsedMB: diskUsage.usedMB, + diskAvailableMB: diskUsage.availableMB, + }), heapUsedMB, heapLimitMB, ...(processWorkingSetsMB && { processWorkingSetsMB }), From 8d2bdc553901f404f37a572de21bdad23fdc08cd Mon Sep 17 00:00:00 2001 From: Ryan Groch Date: Tue, 25 Aug 2026 23:01:38 -0500 Subject: [PATCH 2/5] fix: address review feedback on disk telemetry Pins the three disk fields in the schemas round-trip test, which exists to check that the lastKnownPerformance shape survives the validation parse in writeSettings. Logs at debug when a reading is unavailable. The fields are dropped silently on a statfs failure, so without this a platform-wide breakage would make them vanish from telemetry with nothing to explain why. Asserts the disk fields are present in the e2e spec before comparing them, so a missing field reports itself instead of surfacing as an undefined comparison. Says plainly in the code comment why the user data volume is the one measured: it is the system volume, which is the disk we want a reading for, and the apps folder can sit on a different drive. Co-Authored-By: Claude Opus 5 --- e2e-tests/performance_monitor.spec.ts | 9 ++++++++- src/lib/schemas.test.ts | 3 +++ src/utils/performance_monitor.ts | 8 ++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/e2e-tests/performance_monitor.spec.ts b/e2e-tests/performance_monitor.spec.ts index 3f1e04aa3c..7f3e19f0d0 100644 --- a/e2e-tests/performance_monitor.spec.ts +++ b/e2e-tests/performance_monitor.spec.ts @@ -147,7 +147,14 @@ testWithConfig({})( settings.lastKnownPerformance.systemCpuPercent, ).toBeGreaterThanOrEqual(0); // statfs works on every platform we ship, so a real capture always has - // disk figures. Used never exceeds total; available can trail both. + // disk figures. The fields are dropped on a read failure, so assert + // presence up front: an absent field is the regression to catch. + expect(settings.lastKnownPerformance).toMatchObject({ + diskTotalMB: expect.any(Number), + diskUsedMB: expect.any(Number), + diskAvailableMB: expect.any(Number), + }); + // Used never exceeds total; available can trail both. expect(settings.lastKnownPerformance.diskTotalMB).toBeGreaterThan(0); expect(settings.lastKnownPerformance.diskUsedMB).toBeGreaterThan(0); expect(settings.lastKnownPerformance.diskUsedMB).toBeLessThanOrEqual( diff --git a/src/lib/schemas.test.ts b/src/lib/schemas.test.ts index d2ebe05ddb..7cb8205c39 100644 --- a/src/lib/schemas.test.ts +++ b/src/lib/schemas.test.ts @@ -21,6 +21,9 @@ describe("StoredUserSettingsSchema lastKnownPerformance", () => { systemMemoryUsageMB: 8000, systemMemoryTotalMB: 16000, systemCpuPercent: 33, + diskTotalMB: 476000, + diskUsedMB: 401000, + diskAvailableMB: 51000, heapUsedMB: 512, heapLimitMB: 4144, processWorkingSetsMB: { browser: 400, tab: 900, utility: 300 }, diff --git a/src/utils/performance_monitor.ts b/src/utils/performance_monitor.ts index be1b395388..b33eb66323 100644 --- a/src/utils/performance_monitor.ts +++ b/src/utils/performance_monitor.ts @@ -233,9 +233,13 @@ function capturePerformanceMetrics() { : 0; const kernelPeakRssMB = getKernelPeakRssMB(); const activity = snapshotActivity(); - // The user data volume, not the apps volume: writeSettings below already - // blocks on it every tick, so this adds no new main-thread exposure. + // The user data volume is the system volume, which is the disk we want + // to measure. The apps folder is user-configurable and can sit on a + // different drive entirely. const diskUsage = getDiskUsageMB(getUserDataPath()); + if (!diskUsage) { + logger.debug("Disk usage unavailable; omitting disk fields"); + } logger.debug( `Performance: Memory=${memoryUsageMB}MB, Heap=${heapUsedMB}/${heapLimitMB}MB, All Processes=${allProcessesMemoryMB ?? "?"}MB, CPU=${cpuUsagePercent}%, System Memory=${systemMemory.usedMemoryMB}/${systemMemory.totalMemoryMB}MB (${systemMemory.usagePercent}%), System CPU=${systemCpuPercent}%`, From b48bcc51738a7d1689d8b9fd2af5ab2f4fc1c1fe Mon Sep 17 00:00:00 2001 From: Ryan Groch Date: Wed, 26 Aug 2026 22:19:29 -0500 Subject: [PATCH 3/5] fix: log the error when a disk reading fails getDiskUsageMB swallowed the error and returned null, so a failed statfs left nothing to diagnose it with. Logs at error level with the path, since a statfs that fails on the user data directory means something has gone badly wrong. Drops the debug line in the performance monitor that this replaces: it fired on the same condition and carried less detail. Co-Authored-By: Claude Opus 5 --- src/utils/disk_usage.ts | 6 +++++- src/utils/performance_monitor.ts | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/utils/disk_usage.ts b/src/utils/disk_usage.ts index d9b21f278f..1939d59257 100644 --- a/src/utils/disk_usage.ts +++ b/src/utils/disk_usage.ts @@ -1,4 +1,7 @@ import fs from "node:fs"; +import log from "electron-log"; + +const logger = log.scope("disk-usage"); const BYTES_PER_MB = 1024 * 1024; @@ -25,7 +28,8 @@ export function getDiskUsageMB(targetPath: string): DiskUsageMB | null { usedMB: toMB(stats.blocks - stats.bfree), availableMB: toMB(stats.bavail), }; - } catch { + } catch (error) { + logger.error(`Failed to read disk usage for ${targetPath}:`, error); return null; } } diff --git a/src/utils/performance_monitor.ts b/src/utils/performance_monitor.ts index b33eb66323..52ae952d18 100644 --- a/src/utils/performance_monitor.ts +++ b/src/utils/performance_monitor.ts @@ -237,9 +237,6 @@ function capturePerformanceMetrics() { // to measure. The apps folder is user-configurable and can sit on a // different drive entirely. const diskUsage = getDiskUsageMB(getUserDataPath()); - if (!diskUsage) { - logger.debug("Disk usage unavailable; omitting disk fields"); - } logger.debug( `Performance: Memory=${memoryUsageMB}MB, Heap=${heapUsedMB}/${heapLimitMB}MB, All Processes=${allProcessesMemoryMB ?? "?"}MB, CPU=${cpuUsagePercent}%, System Memory=${systemMemory.usedMemoryMB}/${systemMemory.totalMemoryMB}MB (${systemMemory.usagePercent}%), System CPU=${systemCpuPercent}%`, From ef13a7063ff90a2bf916471c3cf9626a975e573a Mon Sep 17 00:00:00 2001 From: Ryan Groch Date: Wed, 26 Aug 2026 23:02:14 -0500 Subject: [PATCH 4/5] fix: log a disk read failure once per process getDiskUsageMB runs on every 30s tick, and getSystemDebugInfo shows only the last 20 warn+ log lines, so a persistently failing statfs would fill that window in ten minutes and push out the crash warnings this telemetry exists to help read. The flag is deliberately not cleared on a later success: a volume that flaps would re-arm the log on every recovery, which is the case the guard is for. Both the once-only behaviour and the no-reset choice are pinned by tests. Co-Authored-By: Claude Opus 5 --- src/utils/disk_usage.test.ts | 40 ++++++++++++++++++++++++++++++++++++ src/utils/disk_usage.ts | 9 +++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/utils/disk_usage.test.ts b/src/utils/disk_usage.test.ts index 655c72366c..8467ee16fa 100644 --- a/src/utils/disk_usage.test.ts +++ b/src/utils/disk_usage.test.ts @@ -2,6 +2,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import fs from "node:fs"; import { getDiskUsageMB } from "@/utils/disk_usage"; +const { errorLog } = vi.hoisted(() => ({ errorLog: vi.fn() })); +vi.mock("electron-log", () => ({ + default: { scope: () => ({ error: errorLog }) }, +})); + vi.mock("node:fs", () => ({ default: { statfsSync: vi.fn() }, })); @@ -60,4 +65,39 @@ describe("getDiskUsageMB", () => { expect(getDiskUsageMB("/missing")).toBeNull(); }); + + it("logs a failure once rather than on every call", async () => { + // Fresh module so the once-per-process flag starts unset. + vi.resetModules(); + const { getDiskUsageMB: freshGetDiskUsageMB } = + await import("@/utils/disk_usage"); + statfsSync.mockImplementation(() => { + throw new Error("ENOENT"); + }); + + freshGetDiskUsageMB("/missing"); + freshGetDiskUsageMB("/missing"); + freshGetDiskUsageMB("/missing"); + + expect(errorLog).toHaveBeenCalledTimes(1); + }); + + it("stays quiet on a later failure even after a reading succeeds", async () => { + vi.resetModules(); + const { getDiskUsageMB: freshGetDiskUsageMB } = + await import("@/utils/disk_usage"); + const throwENOENT = () => { + throw new Error("ENOENT"); + }; + + statfsSync.mockImplementation(throwENOENT); + freshGetDiskUsageMB("/missing"); + statfsSync.mockReturnValue(statfsResult()); + freshGetDiskUsageMB("/some/path"); + statfsSync.mockImplementation(throwENOENT); + freshGetDiskUsageMB("/missing"); + + // A volume that flaps would otherwise re-arm the log on every recovery. + expect(errorLog).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/utils/disk_usage.ts b/src/utils/disk_usage.ts index 1939d59257..9a9665204e 100644 --- a/src/utils/disk_usage.ts +++ b/src/utils/disk_usage.ts @@ -5,6 +5,10 @@ const logger = log.scope("disk-usage"); const BYTES_PER_MB = 1024 * 1024; +// This runs every 30s, and getSystemDebugInfo shows only the last 20 warn+ +// lines, so a repeating error would push the crash warnings out of view. +let hasLoggedFailure = false; + export interface DiskUsageMB { totalMB: number; usedMB: number; @@ -29,7 +33,10 @@ export function getDiskUsageMB(targetPath: string): DiskUsageMB | null { availableMB: toMB(stats.bavail), }; } catch (error) { - logger.error(`Failed to read disk usage for ${targetPath}:`, error); + if (!hasLoggedFailure) { + hasLoggedFailure = true; + logger.error(`Failed to read disk usage for ${targetPath}:`, error); + } return null; } } From 3a22c7b69d885b1dc415442c68588a968260dec9 Mon Sep 17 00:00:00 2001 From: Ryan Groch Date: Thu, 27 Aug 2026 00:03:54 -0500 Subject: [PATCH 5/5] fix: log every disk read failure, not just the first The once-per-process guard hid more than it protected. getSystemDebugInfo returns the LAST 20 warn+ lines, so a single failure logged at startup is pushed out by any later warnings, and the disk problem is missing from exactly the window a bug report includes. A failure that repeats is the one thing always present in that view. A repeating error also carries information a single line cannot: whether the failure is persistent or a one-off, and whether it ever recovered. The other warnings are not lost either way, since the session debug bundle reads 5000 lines at every level. Co-Authored-By: Claude Opus 5 --- src/utils/disk_usage.test.ts | 34 ++++++---------------------------- src/utils/disk_usage.ts | 9 +-------- 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/src/utils/disk_usage.test.ts b/src/utils/disk_usage.test.ts index 8467ee16fa..973024c85f 100644 --- a/src/utils/disk_usage.test.ts +++ b/src/utils/disk_usage.test.ts @@ -66,38 +66,16 @@ describe("getDiskUsageMB", () => { expect(getDiskUsageMB("/missing")).toBeNull(); }); - it("logs a failure once rather than on every call", async () => { - // Fresh module so the once-per-process flag starts unset. - vi.resetModules(); - const { getDiskUsageMB: freshGetDiskUsageMB } = - await import("@/utils/disk_usage"); + it("logs every failure, not just the first", () => { statfsSync.mockImplementation(() => { throw new Error("ENOENT"); }); - freshGetDiskUsageMB("/missing"); - freshGetDiskUsageMB("/missing"); - freshGetDiskUsageMB("/missing"); + getDiskUsageMB("/missing"); + getDiskUsageMB("/missing"); - expect(errorLog).toHaveBeenCalledTimes(1); - }); - - it("stays quiet on a later failure even after a reading succeeds", async () => { - vi.resetModules(); - const { getDiskUsageMB: freshGetDiskUsageMB } = - await import("@/utils/disk_usage"); - const throwENOENT = () => { - throw new Error("ENOENT"); - }; - - statfsSync.mockImplementation(throwENOENT); - freshGetDiskUsageMB("/missing"); - statfsSync.mockReturnValue(statfsResult()); - freshGetDiskUsageMB("/some/path"); - statfsSync.mockImplementation(throwENOENT); - freshGetDiskUsageMB("/missing"); - - // A volume that flaps would otherwise re-arm the log on every recovery. - expect(errorLog).toHaveBeenCalledTimes(1); + // A repeating failure is itself diagnostic, and only a recent line + // survives in the last-N-lines view that bug reports include. + expect(errorLog).toHaveBeenCalledTimes(2); }); }); diff --git a/src/utils/disk_usage.ts b/src/utils/disk_usage.ts index 9a9665204e..1939d59257 100644 --- a/src/utils/disk_usage.ts +++ b/src/utils/disk_usage.ts @@ -5,10 +5,6 @@ const logger = log.scope("disk-usage"); const BYTES_PER_MB = 1024 * 1024; -// This runs every 30s, and getSystemDebugInfo shows only the last 20 warn+ -// lines, so a repeating error would push the crash warnings out of view. -let hasLoggedFailure = false; - export interface DiskUsageMB { totalMB: number; usedMB: number; @@ -33,10 +29,7 @@ export function getDiskUsageMB(targetPath: string): DiskUsageMB | null { availableMB: toMB(stats.bavail), }; } catch (error) { - if (!hasLoggedFailure) { - hasLoggedFailure = true; - logger.error(`Failed to read disk usage for ${targetPath}:`, error); - } + logger.error(`Failed to read disk usage for ${targetPath}:`, error); return null; } }