From 4dc9526cfc77c3926eb85da3a26a5d182dd88e88 Mon Sep 17 00:00:00 2001 From: Cong <7380929+robotlearning123@users.noreply.github.com> Date: Mon, 28 Sep 2026 04:18:37 -0400 Subject: [PATCH] fix(fs): make expected ENOENT probes silent control flow fileExists, readFile, and directoryExists are used as existence probes on every scan; a missing path is expected control flow, not an error. Log ENOENT at trace level without the err object instead of emitting debug records carrying full stacks and absolute paths. Non-ENOENT errors keep the existing debug record. --- src/utils/fs.ts | 18 +++++++++++++++--- test/utils.test.ts | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/utils/fs.ts b/src/utils/fs.ts index dc9436a..0c798fc 100644 --- a/src/utils/fs.ts +++ b/src/utils/fs.ts @@ -15,7 +15,11 @@ export async function fileExists(filePath: string): Promise { await fs.promises.access(filePath, fs.constants.R_OK); return true; } catch (err) { - logger.debug({ path: filePath, err }, 'File does not exist or is not readable'); + if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') { + logger.trace({ path: filePath }, 'File does not exist or is not readable'); + } else { + logger.debug({ path: filePath, err }, 'File does not exist or is not readable'); + } return false; } } @@ -27,7 +31,11 @@ export async function readFile(filePath: string): Promise { try { return await fs.promises.readFile(filePath, 'utf-8'); } catch (err) { - logger.debug({ path: filePath, err }, 'Failed to read file'); + if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') { + logger.trace({ path: filePath }, 'Failed to read file'); + } else { + logger.debug({ path: filePath, err }, 'Failed to read file'); + } return null; } } @@ -94,7 +102,11 @@ export async function directoryExists(dirPath: string): Promise { const stats = await fs.promises.stat(dirPath); return stats.isDirectory(); } catch (err) { - logger.debug({ path: dirPath, err }, 'Directory does not exist'); + if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') { + logger.trace({ path: dirPath }, 'Directory does not exist'); + } else { + logger.debug({ path: dirPath, err }, 'Directory does not exist'); + } return false; } } diff --git a/test/utils.test.ts b/test/utils.test.ts index 62d3fdb..324444c 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -5,10 +5,19 @@ import { describe, it } from 'node:test'; import * as assert from 'node:assert'; import * as fs from 'node:fs'; +import * as os from 'node:os'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { fileExists, readFileCached, findFiles, relativePath, safePath } from '../src/utils/fs.js'; +import { + fileExists, + readFile, + readFileCached, + findFiles, + relativePath, + safePath, +} from '../src/utils/fs.js'; +import { logger } from '../src/utils/logger.js'; import { isUnsafeRegex, safeRegex, safeRegexTest } from '../src/utils/regex.js'; const __filename = fileURLToPath(import.meta.url); @@ -38,6 +47,31 @@ describe('fs utilities', () => { }); }); + describe('ENOENT probes', () => { + it('should not log error stacks or absolute paths for missing files', async () => { + const missing = path.join(os.tmpdir(), `agent-ready-i82-does-not-exist-${process.pid}`); + const records: Record[] = []; + const originalDebug = logger.debug; + logger.debug = (obj: unknown) => { + records.push(obj as Record); + }; + try { + await fileExists(missing); + await readFile(missing); + } finally { + logger.debug = originalDebug; + } + for (const record of records) { + const err = record.err as { stack?: string } | undefined; + assert.strictEqual(err?.stack, undefined, 'ENOENT probe must not log err.stack'); + assert.ok( + !(typeof record.path === 'string' && path.isAbsolute(record.path)), + 'ENOENT probe must not log an absolute path' + ); + } + }); + }); + describe('readFileCached', () => { it('should read file contents', async () => { const readmePath = path.join(FIXTURES_DIR, 'minimal-repo/README.md');