Three parallel diagnostic agents (G: sdk/install path-sep, H: hook scripts,
I: worktree/workspace) characterised the remaining windows-22 failures on
23b52f1a. Patches by class:
CRLF in test parsers / file-content reads
- bug-2136-sh-hook-version: shebang check split('\n') → split(/\r?\n/)
- bug-3542-executor-git-stash-prohibition: strip \r from read content
(git rewrites stashed text with CRLF on win autocrlf=true checkout)
- workspace: BOM-strip + explicit \r strip in parseCommandFile (BOM at
byte 0 defeats /^---/ anchor → fmMatch null → fm.name undefined)
Windows path-separator / 8.3-shortname normalization
- bug-3491-nested-git-worktree: use fs.realpathSync.native to expand
%TEMP% RUNNER~1 → runneradmin; normalize sep before path-equality
- prune-orphaned-worktrees: normalize \\→/ before substring includes
(git emits forward-slash in --porcelain on Windows even when
path.join produced backslashes)
- bug-3017-codex-hook-absolute-node: accept POSIX path OR path with
drive-letter prefix in hookPath equality assertion
- bug-3126-global-skills-base-runtime-path: use path.join for expected
/xdg/<runtime> values (production calls path.join → \xdg\… on win32)
Test under-specified platform / forgot win32 env
- bug-2979-hook-absolute-node: pass {platform:'linux'} to
buildHookCommand + rewriteLegacyManagedNodeHookCommands so the
POSIX-branch tests don't pick up the #3393 GitBash code path
- bug-3288-model-catalog + bug-3571-config-manifest: also set
USERPROFILE alongside HOME so os.homedir() on win32 redirects to
the test fixture instead of the runner's real ~
External-cmd resolution
- bug-2647-outer-tarball-sdk-dist: use npm.cmd + {shell:true} on win32
so execFileSync resolves PATHEXT (literal `npm` is ENOENT)
ESM loader: tests/runtime-bridge-sync-smoke.test.cjs already migrated to
pathToFileURL in 23b52f1a (cluster E).
Explicit per-test/describe skip on win32 (with required string reasons
to satisfy no-unconditional-win32-skip guard)
- feat-3347-graphify-auto-update-hook: 3 describes — harness spawns
bash/kill/sleep + the hook itself is bash
- feat-3595-fs-fault-injection: move \t and \n filenames into the
POSIX-only branch (NTFS forbids 0x00–0x1F in filenames)
- bug-2775/2829/3033/3231/3359: POSIX shim under ~/.local/bin with
chmod 0o755 — not how Windows install works
- bug-3211: single subtest where cp.execSync reassignment isn't
picked up on win32 (POSIX coverage via the mock; live windows
behavior covered by 3211-D which keeps running)
- install-path-detection: parses sh-style export PATH= rc files;
Windows has no rc files (registry Path)
- worktree-safety-policy: single test using POSIX /repo/wt fixture
paths that can't be expressed under win32 path.resolve
Validated: plex2 (ubuntu docker) 11224/0 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: tighten sdk-first architecture seams
Refs #3312
* refactor: finish state document seam cleanup
Refs #3312
* test: harden minimal install cleanup assertion
* ci: support sdk-scoped package lock
* fix(3316): restore root package-lock.json and align changeset pr ref
Reverts dec57a83 ("ci: support sdk-scoped package lock") and restores
the root package-lock.json that c249d34d deleted. The deletion was the
wrong direction:
- The root package.json declares its own runtime and dev deps
(@anthropic-ai/claude-agent-sdk, ws, c8). Without a root lockfile,
`npm install --no-package-lock` resolves whatever satisfies semver at
install time — CI today and CI in six months can install different
transitive trees, defeating reproducibility.
- The lockfile has been part of every release on this repo (long
history on main); removing it loses the npm audit / Dependabot
target without compensating benefit.
- The CI workaround pattern (cache-dependency-path: sdk/package-lock.json
+ `npm install --no-package-lock`) papered over the symptom rather
than fix the cause.
Also fix the changeset pr: from 3312 (issue) to 3316 (PR). CONTEXT.md
flags this exact failure mode as a recurring CodeRabbit finding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address coderabbit review findings
* fix: close remaining coderabbit threads
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#3017): codex SessionStart hook uses absolute node, not bare 'node'
PR #3002fixed#2979 for settings.json-based managed JS hooks (Claude
Code, Gemini, Antigravity) by routing through buildHookCommand() →
resolveNodeRunner(), emitting the absolute Node binary path so hooks
resolve under GUI/minimal-PATH runtimes (/usr/bin:/bin:/usr/sbin:/sbin)
where nvm/Homebrew/Volta-installed node is not on PATH.
The Codex install path bypassed both helpers — line 7935 of bin/install.js
wrote `command = "node ${path}"` directly into config.toml. So Codex
SessionStart hook still failed with exit 127 ("node: command not found")
under the same minimal-PATH conditions PR #3002 was meant to close.
Fix:
- Add buildCodexHookBlock(targetDir, { absoluteRunner, eol }) — a pure
helper that emits the toml hook block with the absolute runner. Returns
null when absoluteRunner is null so the caller skips registration with
a warning instead of writing a broken bare-node hook.
- Add rewriteLegacyCodexHookBlock(content, absoluteRunner) — mirror of
rewriteLegacyManagedNodeHookCommands for the toml surface, so
reinstall migrates a 1.39.x bare-node config.toml to the absolute form.
Uses basename equality (CODEX_MANAGED_HOOK_BASENAMES set) so user-
authored bare-node hooks are left alone.
- Replace the inline string-concat at line 7935 with a call to the new
helper, threaded with the detected line ending so CRLF files stay CRLF.
- On the codex reinstall path, call rewriteLegacyCodexHookBlock first so
existing bare-node entries get migrated before the new entry is added.
Tests:
- bug-3017-codex-hook-absolute-node.test.cjs (9 tests, all typed-IR):
- buildCodexHookBlock emits absolute runner, parses to expected fields
- returns null on missing runner (caller skips)
- integrates with resolveNodeRunner() in the live process
- rewriteLegacyCodexHookBlock migrates managed bare-node entries
- leaves user-authored bare-node hooks alone (basename allowlist)
- leaves entries with absolute runner unchanged (idempotent)
- returns content unchanged when absoluteRunner is null
- codex-config.test.cjs e2e expectation updated to match new shape:
parsed.hooks.SessionStart[0].hooks[0].command now equals
'"<process.execPath>" "<hookPath>"' instead of 'node <hookPath>'.
Verification:
- 9/9 pass on the new regression test
- 179/179 pass across all codex-touching test files
- 6767/6767 pass on full suite, lint-no-source-grep clean
- Adheres to typed-IR / CONTRIBUTING.md "Prohibited: Raw Text Matching":
parseCodexHookBlock returns a typed record; assertions are on
structured fields (runner, hookPath, type, hasMarker), not stdout regex.
Closes#3017
* test(#3017): tighten runner assertions to exact process.execPath (CR)
CodeRabbit on PR #3022 (3 findings, 2 actionable + 1 nitpick):
1. .changeset/codex-bare-node-fix.md:3 — replace `pr: TBD` with
`pr: 3022` so changeset metadata is traceable.
2. tests/bug-3017-codex-hook-absolute-node.test.cjs:81-146 — the test
asserted `parsed.runner !== 'node'` and `parsed.runner.includes('/node')`,
which would false-positive on any absolute path containing '/node'
(e.g. /Users/x/notnode/foo). Tightened to compare against the EXACT
absolute path supplied by the caller (after stripping toml + JSON
escape layers via a new unescapeRunner() helper). The live-process
integration test now compares against process.execPath exactly. The
rewriteLegacyCodexHookBlock test also uses exact-equality.
3. Nitpick (skipped): use repository's TOML parser for parsing instead
of bespoke regex. The hand-rolled parser is small, scoped, and
fully tested by these structural assertions; pulling in a TOML lib
for tests would create a circular dependency on the SUT (the
installer's own parser). Leaving as-is.
Verification: 9/9 pass on regression test, 6767/6767 full suite, lint clean.