Repository navigation
fix(coding-agent): keep vendored rules discovery inside the project root across Windows drives - #568
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 687b5d0adb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
The only remaining gate is the stale Cubic neutral result from the July quota exhaustion. The quota reset date has passed, but the existing reviewed head was never re-evaluated. Please rerun or refresh Cubic for commit 687b5d0adb, or apply the repository quota-exhaustion skip policy if that is the intended path. No code failure is currently reported. |
|
ULW audit verdict: BLOCKED. The production cross-drive containment fix integrates cleanly, but the authored test fails off Windows, the changelog and required contexts are missing, a P1 thread remains unresolved, and case-variant root termination is incomplete. Local evidence: |
…oot across Windows drives
`isSameOrChildPath` in the vendored rules finder rejected an escaping
`relative()` result with `startsWith("/")`. On Windows, `relative()` between
two different drive roots returns an absolute path — `relative("C:\\proj",
"D:\\other")` is `"D:\\other"` — which starts with neither ".." nor "/", so the
containment test accepted it.
`getWalkDirectories` therefore walked the other drive instead of falling back
to the project root, and `findProjectCandidates` collected `AGENTS.md`,
`CLAUDE.md`, `CONTEXT.md` and `.claude/rules` from that unrelated drive as
*project* rules, injecting them into the model context for a project that does
not own them.
The sibling helper in `rules/engine.ts` already uses `isAbsolute()` for the
same test; this aligns the finder with it. POSIX behavior is unchanged, since
`isAbsolute()` and `startsWith("/")` agree there.
Verified on Windows 11 with a real second drive: with a rule planted at
`D:\senpi-qa-518\AGENTS.md` and the project root on `C:`, discovery returned
that file as an `AGENTS.md` project rule at distance 1 before the change and
returns no candidates after it.
…gression
The regression mapped dirname/join/relative/resolve to their win32 variants but
left isAbsolute as the host implementation. posix.isAbsolute("D:\\other") is
false, so off Windows the containment guard never tripped, the cross-drive
AGENTS.md was still collected, and the test failed:
AssertionError: expected [ 'D:\other\AGENTS.md' ] to deeply equal []
Mapping isAbsolute to win32.isAbsolute makes the regression exercise the guard
on any host. The mock factory also stops using an inline dynamic type import,
which the root AGENTS.md forbids, in favour of a top-level namespace import.
getWalkDirectories ended the upward walk with an exact string compare against the project root. Windows drive letters are case-insensitive, so a target under c:\workspace\proj\src never matched a C:\workspace\proj root and the walk continued past it: ["c:\\workspace\\proj\\src", "c:\\workspace\\proj", "c:\\workspace", "c:\\"] findProjectCandidates collects rules from every walked directory and does not re-check containment per directory, so an AGENTS.md two levels above the project root was injected as a project rule. This is the same escape the cross-drive fix closes, reached through drive-letter case instead of a different drive. relative() folds drive-letter case, so terminating on an empty relative path stops at the root on Windows and is a no-op on posix, where the two comparisons already agree.
Adds the [Unreleased] > Fixed changelog entry the changelog gate requires and extends the vendored rules tracker with the getWalkDirectories termination change alongside the existing isSameOrChildPath entry.
687b5d0 to
f74fde4
Compare
|
Rebased onto current 1. The authored test fails off Windows — The mock mapped Mapping 2. The P1 thread remains unresolved — Same defect: that is exactly what the Codex thread called out. Resolved by the same commit. While there I also replaced the factory's 3. Case-variant root termination is incomplete —
Terminating on 4. The changelog is missing — Added the to 5. Required contexts are missing — I read this as the tracker requirement and checked it against the repo's own tooling rather than guessing. Verification on the rebased branch: Real-CLI QA via Windows-specific semantics are covered by the win32-mocked unit tests, since the suite runs on POSIX here. The production diff is 8 added / 3 removed lines in |
…-yeongyu#518) Keep the existing PR changelog entry under Unreleased while preserving released history. Verified root check, 17 rules tests, real CLI tool turn, and 8 CLI smoke assertions.
…fixes code-yeongyu#518) Preserve both Unreleased change records. Verified root check, 17 rules tests, mock CLI tool turn and CLI smoke.
code-yeongyu#518) Preserve both changelog records while integrating main. Verified root check, 17 rules tests, mock CLI tool turn and 8 CLI smoke checks.
…-main # Conflicts: # packages/coding-agent/src/core/extensions/builtin/rules/changes.md
…-main # Conflicts: # packages/coding-agent/CHANGELOG.md
…-main # Conflicts: # packages/coding-agent/CHANGELOG.md
…eleased] v2026.9.28-6 stamped the previous [Unreleased] entries into a released section, which carried this entry with it on merge.
…s-drive-project-scope # Conflicts: # packages/coding-agent/CHANGELOG.md
…s-drive-project-scope # Conflicts: # packages/coding-agent/CHANGELOG.md
…s-drive-project-scope
…nreleased after the 2026.9.29-5 release
…-main # Conflicts: # packages/coding-agent/CHANGELOG.md
|
The one failing check, |
…s-drive-project-scope
…Unreleased] after merging main
|
Merged current The one red job, |
…s-drive-project-scope # Conflicts: # packages/coding-agent/CHANGELOG.md
code-yeongyu
left a comment
There was a problem hiding this comment.
Approving at 977109276e, verified on that head:
- The fix is right.
isSameOrChildPathusesisAbsolute(), matchingrules/engine.ts, so a cross-driverelative()result (D:\other) is no longer accepted as a child. The upward walk stops whenrelative(projectRoot, currentDirectory) === "", soc:\…vsC:\…no longer walks past the root. POSIX behavior is unchanged. - The regression test can fail. Its 2 Windows-semantics cases pass on this head and both fail with
main'sfinder.ts. The rules suites stay green (4 files / 19 tests). - Change-log: the bullet is under
[Unreleased], and the vendored-rule rationale is in its nearestchanges.md.
Thanks for keeping this current since July, @MoerAI.
Summary
Keep vendored project-rule discovery inside its project root on Windows. A
read/edit/writetarget on another drive could make the finder walk that drive and inject unrelatedAGENTS.md,CLAUDE.md, or scoped rule files as project instructions. A target beneath the project with a differently cased drive letter could make the upward walk continue above the project root. Neither escape changes POSIX behavior.Root cause and fix
In
rules/finder.ts,relative("C:\\project", "D:\\other")is an absolute Windows path. The oldstartsWith("/")check accepted it as a child. The containment check now usesisAbsolute()like the sibling helper inrules/engine.ts. The walk also stops whenrelative(projectRoot, currentDirectory) === "", so Windows drive-letter case does not prevent recognition of the root.The regression uses Windows path semantics on every CI platform to test both an unrelated drive and a differently cased drive letter. The vendored-rule rationale is recorded in its nearest
changes.md, and the release note is under[Unreleased]. This is distinct from the already fixedproject-root.tshang in #432 and does not close #518.Current-main update (2026-09-28)
Merged main again at
4c2e53aff; the only conflict waspackages/coding-agent/CHANGELOG.md, resolved by keeping this entry under[Unreleased]as the earlier audit asked. With current main'sfinder.tsswapped in, both cross-drive regressions fail (2/2); with this PR they pass, along with 30 rules tests and the 6/6 CLI rules scenario. Check, build, and the PR changelog gate pass.Reproduction and verification
Before the fix, the original physical Windows 11 reproduction placed a project on
C:and anAGENTS.mdon an unrelatedD:drive. The exportedfindRuleCandidatesreturned theD:rule as a project rule. With the same real inputs after the fix, it returned no off-project candidates. The regression was red before the fix (D:\\other\\AGENTS.mdwas collected) and green after it.Current-main integration: merge commit
311a286b8includesmainatfc8eaac59. The PR-only diff is the finder, its two-case regression, the nearest tracker, and the[Unreleased]changelog entry. Validation on macOS with Node 24:CI=1 bun run --cwd packages/coding-agent test test/rules test/suite/regressions/0000-rules-finder-cross-drive-project-scope.test.ts— 4 files, 19 tests passed; the new regression passed 2/2.CI=1 bun run --cwd packages/coding-agent test test/suite/rules-upstream-sync.test.ts— 10/10 passed.bun run checkandbun run build— both exited 0.node scripts/check-pr-changelog.mjs --base origin/main— passed.readtool: the model received a synthetic rule inside the project and the read result, but no rule planted above the project root. The scenario passed 6/6, removed its temporary project/server, and verified the real auth file was unchanged. The public QA harness also passednode .agents/skills/senpi-qa/scripts/mock-loop.mjs --with-tool --api openai-responses(4/4) andnode .agents/skills/senpi-qa/scripts/cli-smoke.mjs --self-test(8/8), without real provider calls.A full local
bun run testwith Rust available passed the root script tests and progressed through workspace packages, but its coding-agent phase exceeded a 600-second bound; this is not a full-suite pass. The first attempt had failed because Cargo was missing fromPATH; the six native-grep tests passed once the already installed Rust toolchain was onPATH. Hosted PR checks will determine the complete suite result. The physical cross-drive reproduction was performed on Windows for the original PR; this refresh's real-CLI scenario and platform-neutral Windows regressions ran on macOS.