fix(core): resolve symlinks when matching conditional rules and skills - #6371
Conversation
When a file is accessed via a symlinked path (e.g., in git worktrees or monorepos with symlinked directories), conditional rules and skills keyed on the real path would fail to activate. Add resolveSymlinkAwareRelativePaths() that returns both the original and realpath-resolved relative paths, so glob patterns match either form. Resolve both the file path and projectRoot via realpath to handle macOS /private/tmp prefix normalization correctly. Make matchAndConsume() async in both ConditionalRulesRegistry and SkillActivationRegistry to support the realpath I/O. Fixes #6356 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Thanks for the PR! Template looks good ✓ Problem: Observed bug with a linked issue (#6356). The issue describes a specific scenario — conditional rules with Direction: Aligned. Symlink-aware path resolution for conditional rules/skills is a clear competitive-parity fix. The issue references the specific Claude Code version (v2.1.198) and the same affected code paths in qwen-code. No direction concerns. Approach: The scope is tight — resolve symlinks before computing relative paths for glob matching, with graceful ENOENT fallback. The async propagation ( 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到的 bug,有关联 issue(#6356)。issue 描述了具体场景——当文件通过符号链接目录访问时(在 git worktree 和 monorepo 中常见),带 方向:对齐。条件规则/技能的符号链接感知路径解析是明确的竞品对齐修复。issue 引用了具体的 Claude Code 版本(v2.1.198)和 qwen-code 中相同的受影响代码路径。无方向问题。 方案:范围紧凑——在计算 glob 匹配的相对路径前解析符号链接,ENOENT 时优雅回退。异步传播( — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal: I would add an async wrapper around PR's approach matches. No correctness bugs or security issues found. One minor note: Tests: 69/70 pass. The one failure ( Real-Scenario TestingTest project: Before (installed qwen v0.19.6, main branch)Chat log verification: After (dev build, PR branch
|
|
This is a clean, well-scoped fix for a real problem. The before/after test confirms it: the conditional rule silently fails to activate via symlink paths on the current build, and activates correctly with this PR. The implementation is the minimal correct approach — async realpath resolution with graceful fallback, shared between both registries, with thorough test coverage including real-filesystem symlink scenarios. The one test failure ( Ships it. ✅ 中文说明这是一个干净、范围合理的修复,解决了真实问题。before/after 测试确认:当前构建中条件规则通过符号链接路径静默失效,本 PR 修复后正确激活。实现是最小正确方案——异步 realpath 解析配合优雅回退,两个 registry 共享,测试覆盖充分(包括真实文件系统的符号链接场景)。 唯一的测试失败( 可以合并 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
Clean, well-scoped fix for symlink-aware path resolution. The async migration of matchAndConsume() is correctly propagated to all callers, and the new resolveSymlinkAwareRelativePaths() handles edge cases gracefully with appropriate fallback on realpath failure. Test coverage is solid with both mocked unit tests and real filesystem integration tests.
— qwen3.7-max via Qwen Code /review
✅ Verification report — PR #6371 (local build + real-runtime tests)Verdict: the fix is correct and well-covered. Recommend merge. I verified this locally on two isolated git worktrees — PR head [
What the PR does
Evidence summary
Layer 1 — full unit suite passes on the PR (matches the PR's claim of 70)Layer 2 — revert-proof: the PR's own new symlink tests fail on
|
| # | 层级 | main(基线) |
PR #6371 |
|---|---|---|---|
| 1 | PR 单测套件(70 个) | — | ✅ 70/70 通过 |
| 2 | 回归证明:PR 自带的符号链接测试跑在基线源码上 | ❌ 2 个失败 | ✅ 通过 |
| 3 | 产物级 A/B —— 编译后的 dist,真实符号链接(规则和技能) |
❌ 未激活 | ✅ 已激活 |
| 4 | 真实 CoreToolScheduler 集成 —— read_file 回合 → <system-reminder> |
❌ 提醒丢失 | ✅ 提醒注入 |
第 1 层 —— PR 上单测全通过(与 PR 声称的 70 个一致)
✓ src/utils/projectPath.test.ts (12 tests)
✓ src/utils/rulesDiscovery.test.ts (36 tests)
✓ src/skills/skill-activation.test.ts (22 tests)
Tests 70 passed (70)
第 2 层 —— 回归证明:PR 自带的新符号链接测试在 main 上失败
把 PR 的测试文件跑在未打补丁的基线源码上(证明这些测试确实覆盖到了修复,并锁定 bug):
× SkillActivationRegistry … > activates skills when file is reached via symlinked directory
→ expected [] to deeply equal [ 'tsx-helper' ]
× ConditionalRulesRegistry … > activates rules when file is reached via symlinked directory
→ (matchAndConsume 返回 undefined → 无规则)
Tests 2 failed | 56 skipped (58)
("项目根本身是符号链接" 那条用例在 Linux 上也能通过 —— 它的 realpath 归一化只在 macOS /private/tmp 才会触发差异。真正有区分度的是符号链接目录这一条。)
第 3 层 —— 在编译后的 dist 上用真实磁盘符号链接做 A/B
独立脚本(非 PR 自带测试):在两个 worktree 各自构建 core,从各自 dist 实例化真实注册表,访问 symlink-to-src/foo.ts:
──────────── BASE (origin/main, no fix) ────────────
RULE injected? NO ❌ (returned: undefined)
SKILL activated? NO ❌ (returned: [])
──────────── PR#6371 (with fix) ────────────
RULE injected? YES ✅ (returned: "--- Rule from: …/fe.md ---\nFE…")
SKILL activated? YES ✅ (returned: ["ts-helper"])
>>> A/B 确认:bug 在 main 上复现,被 PR #6371 修复 <<<
第 4 层 —— 真实 CoreToolScheduler 集成(CLI 实际运行的代码路径)
通过真实的 CoreToolScheduler + 真实 ConditionalRulesRegistry + 真实磁盘符号链接调度一个真实的 read_file 回合,断言 <system-reminder> 规则信封是否进入工具响应。一个对照组(同一规则,但经真实 src/foo.ts 访问)把符号链接隔离为唯一变量:
### BASE (origin/main, no fix) ###
✓ CONTROL — 经真实 src/foo.ts 访问,规则注入(基线也能过)
× FIX — 经 symlink-to-src/foo.ts 访问,规则注入
→ 期望包含 '<system-reminder>',实际只有 functionResponse
Tests 1 failed | 1 passed (2)
### PR #6371 (with fix) ###
Tests 2 passed (2)
在 main 上,经符号链接访问的文件返回的是不含 <system-reminder> 的裸 functionResponse —— 即模型根本收不到前端规则。本 PR 恰好修复此点,而直连路径的对照组保持不变。
额外检查(happy path 之外)
- 异步无竞态。 两处新调用点都用顺序
for … await循环(非Promise.all),因此按会话去重的集合(injected/activated)不会交错。"每会话只注入一次" 的语义保持正确。 - 性能开销与描述一致。
resolveSymlinkAwareRelativePaths每次文件访问只做一次realpath;仅当路径确实解析到不同结果(符号链接场景)时才对projectRoot再做一次。开销可忽略,与 PR 声明的权衡一致。 - 无遗漏调用方。 全仓
matchAndConsume恰好两个调用方(coreToolScheduler.ts、skill-manager.ts),均已await。 - 失败安全。
realpath出错(ENOENT / 权限)会回退到原始路径;解析到项目根之外的 realpath 会被过滤(新增的projectPath.test.ts已覆盖)。访问路径本身就在项目根之外的文件仍会短路为 "不匹配" —— 由外向内的符号链接刻意不会激活项目规则。
备注
- 纯函数
resolveProjectRelativePath()未改动且仍导出,非符号链接的原有行为保持不变。 - 我确认改动文件可通过类型检查;隔离 worktree 中仅有的诊断来自我使用的
node_modules符号链接捷径(host-dist类型标识问题),在 base 与 PR 上完全相同,且不涉及任何 PR 改动文件。
What this PR does
When a file is accessed via a symlinked path (common in git worktrees and monorepos with symlinked directories), conditional rules and conditional skills keyed on the real path failed to activate. This PR adds symlink-aware path resolution so that glob patterns like
src/**/*.tsmatch both the original symlinked path and the realpath-resolved path.Why it's needed
Developers working in git worktrees or monorepos often have symlinked directories. When a tool touches a file via the symlink path (e.g.,
symlink-to-src/foo.ts), thepaths:globsrc/**/*.tswouldn't match because the relative path was computed from the symlink, not the real directory. This made conditional rules and skills silently fail to activate in these setups.Reviewer Test Plan
How to verify
src/directory and a symlink pointing to it:ln -s src symlink-to-src.qwen/rules/fe.mdwithpaths: ["src/**/*.ts"]symlink-to-src/foo.tsRun tests:
All 70 tests should pass.
Evidence (Before & After)
N/A — non-user-visible change (internal path resolution logic)
Tested on
Environment (optional)
Local dev environment, Node.js 22, vitest
Risk & Scope
realpathcall per file touch during tool scheduling — negligible I/O overheadit.skipIf(process.platform === 'win32'))matchAndConsume()is now async — any external consumers mustawaititLinked Issues
Fixes #6356
中文说明
此 PR 做了什么
当文件通过符号链接路径访问时(在 git worktree 或使用符号链接目录的 monorepo 中很常见),基于真实路径配置的条件规则和条件技能无法激活。此 PR 添加了符号链接感知的路径解析,使
src/**/*.ts等 glob 模式同时匹配原始符号链接路径和 realpath 解析后的路径。为什么需要
在 git worktree 或 monorepo 中工作的开发者经常使用符号链接目录。当工具通过符号链接路径(如
symlink-to-src/foo.ts)访问文件时,paths:globsrc/**/*.ts无法匹配,因为相对路径是基于符号链接而非真实目录计算的。这导致条件规则和条件技能在这些场景中静默失效。审阅者测试计划
如何验证
src/目录的项目和一个指向它的符号链接:ln -s src symlink-to-src.qwen/rules/fe.md中添加条件规则,配置paths: ["src/**/*.ts"]symlink-to-src/foo.ts运行测试:
所有 70 个测试应通过。
证据(修改前 & 修改后)
N/A — 非用户可见的变更(内部路径解析逻辑)
测试环境
环境(可选)
本地开发环境,Node.js 22,vitest
风险 & 范围
realpath调用 — I/O 开销可忽略不计it.skipIf(process.platform === 'win32')跳过)matchAndConsume()现在是异步的 — 任何外部调用者必须await它关联 Issue
Fixes #6356