From 499409876f4c0639a61944bc87872506160cd844 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 06:56:57 +0000 Subject: [PATCH 1/2] Fix 7 Windows-only test failures in nightly CI The scheduled CI run has been red on main for five nights. Its `test (windows-latest, 1|2)` legs (nightly-only) fail seven tests that pass on Linux and macOS, all from test-side assumptions about Unix paths: - an_absolute_patch_dir_inside_the_repository_is_read built its package.json with format!, so the Windows path's backslashes were invalid JSON escapes; build it with serde_json instead. - five assertions matched `/`-separated substrings in user-facing detail text that Windows prints with `\`; compare after mapping `\` to `/`. - pnpm_workspace_file_above_the_repository_fails_closed expected the canonical `\\?\` path, but governing_workspace_file deliberately strips that prefix; expect without_verbatim_prefix(file). No production code changes; every assertion still checks the same path on every OS. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_011f9aeRDbxjatib28cpxPqQ --- .../tests/hosted_memory_engine.rs | 4 +++- .../src/patch/user_patches.rs | 23 +++++++++---------- .../src/utils/cargo_build_cache.rs | 8 ++++++- .../src/utils/repo_root_upward_tests.rs | 3 ++- .../src/vex/discover/user_patched.rs | 3 ++- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/crates/socket-patch-cli/tests/hosted_memory_engine.rs b/crates/socket-patch-cli/tests/hosted_memory_engine.rs index cc00ffc4f..af468e149 100644 --- a/crates/socket-patch-cli/tests/hosted_memory_engine.rs +++ b/crates/socket-patch-cli/tests/hosted_memory_engine.rs @@ -2281,7 +2281,9 @@ async fn user_patched_npm_packages_are_never_pinned() { .unwrap_or_else(|| panic!("{fixture} {file}: {:?}", project.skipped)); assert_eq!(skip.purl, "pkg:npm/left-pad@1.3.0"); assert!( - skip.detail.as_deref().is_some_and(|d| d.contains(file)), + skip.detail + .as_deref() + .is_some_and(|d| d.replace('\\', "/").contains(file)), "{fixture}: {:?}", skip.detail ); diff --git a/crates/socket-patch-core/src/patch/user_patches.rs b/crates/socket-patch-core/src/patch/user_patches.rs index 5e17d12b9..a1f2c140e 100644 --- a/crates/socket-patch-core/src/patch/user_patches.rs +++ b/crates/socket-patch-core/src/patch/user_patches.rs @@ -2058,14 +2058,14 @@ mod tests { assert!( left_pad .iter() - .any(|s| s.ends_with("fixes/left-pad+1.3.0.patch")), + .any(|s| s.replace('\\', "/").ends_with("fixes/left-pad+1.3.0.patch")), "{left_pad:?}" ); let is_number = global.conflicts("pkg:npm/is-number@7.0.0"); assert!( - is_number - .iter() - .any(|s| s.ends_with("shared/a/patches/is-number+7.0.0.patch")), + is_number.iter().any(|s| s + .replace('\\', "/") + .ends_with("shared/a/patches/is-number+7.0.0.patch")), "{is_number:?}" ); assert!(global.conflicts("pkg:npm/right-pad@1.3.0").is_empty()); @@ -2116,14 +2116,13 @@ mod tests { let repo = std::fs::canonicalize(tmp.path()).unwrap(); write(&repo, ".git/HEAD", "ref: refs/heads/main\n"); let fixes = repo.join("fixes"); - write( - &repo, - "package.json", - &format!( - r#"{{"scripts":{{"postinstall":"patch-package --patch-dir {}"}}}}"#, - fixes.display() - ), - ); + // Built as JSON so a Windows path's backslashes are escaped. + let manifest = serde_json::json!({ + "scripts": { + "postinstall": format!("patch-package --patch-dir {}", fixes.display()) + } + }); + write(&repo, "package.json", &manifest.to_string()); write(&repo, "fixes/left-pad+1.3.0.patch", "diff\n"); let index = UserPatchIndex::discover(&repo).await; assert!( diff --git a/crates/socket-patch-core/src/utils/cargo_build_cache.rs b/crates/socket-patch-core/src/utils/cargo_build_cache.rs index 116888c1b..bcbd3bcab 100644 --- a/crates/socket-patch-core/src/utils/cargo_build_cache.rs +++ b/crates/socket-patch-core/src/utils/cargo_build_cache.rs @@ -437,7 +437,13 @@ mod tests { .unwrap(); let warnings = invalidate_project(ReadScope::Workspace, &app, purls); assert_eq!(warnings.len(), 1, "{warnings:?}"); - assert!(warnings[0].1.contains(".cargo/config.toml"), "{warnings:?}"); + assert!( + warnings[0] + .1 + .replace('\\', "/") + .contains(".cargo/config.toml"), + "{warnings:?}" + ); // A global run reads it as before. assert!(invalidate_project(ReadScope::Global, &app, purls).is_empty()); } diff --git a/crates/socket-patch-core/src/utils/repo_root_upward_tests.rs b/crates/socket-patch-core/src/utils/repo_root_upward_tests.rs index f7f22b82c..fc4571069 100644 --- a/crates/socket-patch-core/src/utils/repo_root_upward_tests.rs +++ b/crates/socket-patch-core/src/utils/repo_root_upward_tests.rs @@ -135,7 +135,8 @@ fn pnpm_workspace_file_above_the_repository_fails_closed() { write(&file, "packages:\n - other\n"); assert_eq!( crate::utils::pnpm_workspace::governing_workspace_file(&l.app), - Some(file) + // Reported without Windows' `\\?\` prefix, which `layout` keeps. + Some(crate::utils::pnpm_workspace::without_verbatim_prefix(file)) ); let plain = layout(false); write( diff --git a/crates/socket-patch-core/src/vex/discover/user_patched.rs b/crates/socket-patch-core/src/vex/discover/user_patched.rs index 7fd7fcede..7258c233d 100644 --- a/crates/socket-patch-core/src/vex/discover/user_patched.rs +++ b/crates/socket-patch-core/src/vex/discover/user_patched.rs @@ -328,6 +328,7 @@ mod tests { assert!( out.unattested[0] .detail + .replace('\\', "/") .contains("patches/left-pad+1.3.0.patch"), "{}", out.unattested[0].detail @@ -420,7 +421,7 @@ mod tests { all_marked(&out, what); let member = files[files.len() - 1].0.rsplit_once('/').unwrap().0; assert!( - out.unattested[0].detail.contains(member), + out.unattested[0].detail.replace('\\', "/").contains(member), "{what}: {}", out.unattested[0].detail ); From c40cda63fb7bd7a6517d726b26836d14809e1821 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 07:01:52 +0000 Subject: [PATCH 2/2] Read a user-typed absolute --patch-dir as in-repo On Windows the bound's repository root is canonical (`\\?\C:\...`) while a script names its patch directory as users type it (`C:\...`), and Path::starts_with treats the verbatim and plain disk prefixes as different. Every absolute in-repo `--patch-dir` was reported as outside the repository, protecting every npm package. Compare both sides without the verbatim prefix, and make the test spell the directory the way a user does (quoted, unprefixed). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_011f9aeRDbxjatib28cpxPqQ --- .../socket-patch-core/src/patch/user_patches.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/socket-patch-core/src/patch/user_patches.rs b/crates/socket-patch-core/src/patch/user_patches.rs index a1f2c140e..700b05b2b 100644 --- a/crates/socket-patch-core/src/patch/user_patches.rs +++ b/crates/socket-patch-core/src/patch/user_patches.rs @@ -1232,7 +1232,12 @@ impl Bound { ) } Bound::Disk { root, repo } => { - !normalize_lexically_keeping_escapes(&root.join(rel)).starts_with(repo) + // `repo` is canonical (`\\?\C:\...` on Windows) while a + // script names a directory as users type it (`C:\...`), and + // `starts_with` treats the two prefixes as different. + use crate::utils::pnpm_workspace::without_verbatim_prefix; + !without_verbatim_prefix(normalize_lexically_keeping_escapes(&root.join(rel))) + .starts_with(without_verbatim_prefix(repo.clone())) } } } @@ -2115,11 +2120,14 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let repo = std::fs::canonicalize(tmp.path()).unwrap(); write(&repo, ".git/HEAD", "ref: refs/heads/main\n"); - let fixes = repo.join("fixes"); - // Built as JSON so a Windows path's backslashes are escaped. + // Spelled as a user types it: on Windows without the `\\?\` prefix + // `canonicalize` adds to `repo`, quoted in case the temp dir has a + // space, and built as JSON so its backslashes are escaped. + let fixes = + crate::utils::pnpm_workspace::without_verbatim_prefix(repo.clone()).join("fixes"); let manifest = serde_json::json!({ "scripts": { - "postinstall": format!("patch-package --patch-dir {}", fixes.display()) + "postinstall": format!("patch-package --patch-dir \"{}\"", fixes.display()) } }); write(&repo, "package.json", &manifest.to_string());