From 40fc681b28cbd69632f78c58c26d2c3d7e38015d Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 3 Apr 2026 12:14:45 -0400 Subject: [PATCH] fix: use realpathSync.native for session projectId hash on Windows (#1593) (#1601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows, path.resolve returns whatever case the caller supplied while fs.realpathSync.native returns the OS-canonical case. These produce different SHA-1 hashes and therefore different session tmpdir slots — the test checks one slot while the implementation writes to another, causing pointer lifecycle assertions to always fail. Fix: use realpathSync.native with a fallback to path.resolve when the planning directory does not yet exist. Co-authored-by: Claude Sonnet 4.6 --- get-shit-done/bin/lib/core.cjs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 13422bc8a..4d1f6d9fb 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -695,9 +695,20 @@ function getSessionScopedWorkstreamFile(cwd) { const sessionKey = getWorkstreamSessionKey(); if (!sessionKey) return null; + // Use realpathSync.native so the hash is derived from the canonical filesystem + // path. On Windows, path.resolve returns whatever case the caller supplied, + // while realpathSync.native returns the case the OS recorded — they differ on + // case-insensitive NTFS, producing different hashes and different tmpdir slots. + // Fall back to path.resolve when the directory does not yet exist. + let planningAbs; + try { + planningAbs = fs.realpathSync.native(planningRoot(cwd)); + } catch { + planningAbs = path.resolve(planningRoot(cwd)); + } const projectId = crypto .createHash('sha1') - .update(path.resolve(planningRoot(cwd))) + .update(planningAbs) .digest('hex') .slice(0, 16);