From f93123cdbbed1845752c5c5bd5b57a4a19b6dbe2 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 18:23:54 -0700 Subject: [PATCH 1/2] fix(gsd): suppress misleading warnings for expected ENOENT/EISDIR conditions Skip ENOENT warnings in clearProjectRootStateFiles and untracked file cleanup since missing files are expected. Check if .git is a directory before attempting readFileSync in resolveGitDir to avoid EISDIR warning in normal (non-worktree) repos. Fixes #3597 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-worktree.ts | 13 +++++++++---- src/resources/extensions/gsd/worktree-manager.ts | 6 ++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/resources/extensions/gsd/auto-worktree.ts b/src/resources/extensions/gsd/auto-worktree.ts index a0275619a..49696e308 100644 --- a/src/resources/extensions/gsd/auto-worktree.ts +++ b/src/resources/extensions/gsd/auto-worktree.ts @@ -188,8 +188,10 @@ function clearProjectRootStateFiles(basePath: string, milestoneId: string): void try { unlinkSync(file); } catch (err) { - /* non-fatal — file may not exist */ - logWarning("worktree", `file unlink failed: ${err instanceof Error ? err.message : String(err)}`); + // ENOENT is expected — file may not exist (#3597) + if ((err as NodeJS.ErrnoException).code !== "ENOENT") { + logWarning("worktree", `file unlink failed: ${err instanceof Error ? err.message : String(err)}`); + } } } @@ -218,8 +220,11 @@ function clearProjectRootStateFiles(basePath: string, milestoneId: string): void try { unlinkSync(join(basePath, f)); } catch (err) { - /* non-fatal */ - logWarning("worktree", `untracked file unlink failed: ${err instanceof Error ? err.message : String(err)}`); + // ENOENT/EISDIR are expected for already-removed or directory entries (#3597) + const code = (err as NodeJS.ErrnoException).code; + if (code !== "ENOENT" && code !== "EISDIR") { + logWarning("worktree", `untracked file unlink failed: ${err instanceof Error ? err.message : String(err)}`); + } } } } diff --git a/src/resources/extensions/gsd/worktree-manager.ts b/src/resources/extensions/gsd/worktree-manager.ts index b73e4e5ee..d8eb3760a 100644 --- a/src/resources/extensions/gsd/worktree-manager.ts +++ b/src/resources/extensions/gsd/worktree-manager.ts @@ -89,7 +89,9 @@ function normalizePathForComparison(path: string): string { */ export function resolveGitDir(basePath: string): string { const gitPath = join(basePath, ".git"); - if (!existsSync(gitPath)) return join(basePath, ".git"); + if (!existsSync(gitPath)) return gitPath; + // In a normal repo .git is a directory — skip the file read (#3597) + if (lstatSync(gitPath).isDirectory()) return gitPath; try { const content = readFileSync(gitPath, "utf-8").trim(); if (content.startsWith("gitdir: ")) { @@ -98,7 +100,7 @@ export function resolveGitDir(basePath: string): string { } catch (e) { logWarning("worktree", `.git file read failed: ${(e as Error).message}`); } - return join(basePath, ".git"); + return gitPath; } export function worktreesDir(basePath: string): string { From b8536a896dc570c1d3e608ae5f3e659494df87df Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:26:28 -0700 Subject: [PATCH 2/2] test: add regression test for worktree expected-condition warning suppression Co-Authored-By: Claude Opus 4.6 (1M context) --- .../tests/worktree-expected-warnings.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts diff --git a/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts b/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts new file mode 100644 index 000000000..60c2dc064 --- /dev/null +++ b/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts @@ -0,0 +1,38 @@ +/** + * worktree-expected-warnings.test.ts — #3665 + * + * Verify that auto-worktree.ts and worktree-manager.ts suppress expected + * ENOENT and EISDIR conditions instead of logging misleading warnings. + */ + +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const autoWorktreeFile = join(__dirname, "..", "auto-worktree.ts"); +const worktreeManagerFile = join(__dirname, "..", "worktree-manager.ts"); + +describe("worktree expected-condition warning suppression (#3665)", () => { + const autoSource = readFileSync(autoWorktreeFile, "utf-8"); + + test("auto-worktree.ts checks for ENOENT before logging unlink warning", () => { + assert.match(autoSource, /code\s*!==\s*["']ENOENT["']/); + }); + + test("auto-worktree.ts checks for EISDIR before logging unlink warning", () => { + assert.match(autoSource, /code\s*!==\s*["']EISDIR["']/); + }); + + test("auto-worktree.ts references issue #3597", () => { + assert.match(autoSource, /#3597/); + }); + + const managerSource = readFileSync(worktreeManagerFile, "utf-8"); + + test("worktree-manager.ts checks isDirectory() before reading .git file", () => { + assert.match(managerSource, /lstatSync\(gitPath\)\.isDirectory\(\)/); + }); +});