Repository navigation
fix(runtime): keep Windows lock records out of route discovery - #6658
Conversation
A Windows lock acquisition keeps a released holder record beside the global registry (`registry.global.json.lock.holder`, legacy `.lock.holder.json`) plus a timeout incident channel, and route discovery only knew the `.lock` and `.ts-effect.lock` spellings. Goal configuration locks every candidate runtime registry before writing, so on a Windows machine whose other default root was still empty the untouched root held only lock metadata -- and the route read that metadata back as machine state, reported a phantom two-root conflict, and refused every implicit-default command (`loopx status`, `update check`, `usage-ping status`, the last one with an unhandled ValueError traceback). f955e83 fixed the same failure for the lock spellings it knew; the holder record Windows keeps is the remaining gap. Classify the documented lock-metadata family (docs/reference/protocols/file-lock-acquisition-v0.md) as observations on every platform, so an archive restored on another host cannot read a sidecar back as a second authority either. Redirected entries, unknown files and real registries stay authoritative for discovery. Cover the regression with the spelled sidecar names, the observation list, and a real `exclusive_file_lock` round trip that pins the producer (`file_lock`) to the consumer (route discovery); the new guards fail on the unfixed code and pass on POSIX and Windows. Drafted with AI assistance at the maintainer's request, under their direction and review. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…cation (issue loopx-project#6657, PR loopx-project#6658) Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
JasonBuildAI
left a comment
There was a problem hiding this comment.
Independent subagent review — exact head 5ccf12f2f
An independent review agent (fresh context, author summary not trusted) verified this head against the repository, the issue and the sandbox. Six-question verdicts:
| # | Question | Verdict |
|---|---|---|
| 1 | Defect real and correctly root-caused | PASS |
| 2 | Fix correctness, no masking, no missed producer spelling | PASS |
| 3 | Tests catch the defect (negative case) | PASS |
| 4 | Boundary and scope (files, DCO, base/head, Closes) |
PASS |
| 5 | Collisions (issues, open PRs, coordination registry) | PASS |
| 6 | Honesty of the PR's evidence claims | PASS |
Key independent evidence:
- Extracted the base tree (
b2aca67d7) to scratch and reran the sandbox there:lock + released holder/legacy holder.json/incidents.jsonl→conflicton base,legacyon this head; the control case and.ts-effect.lockwere unchanged. Both runs printed the source tree they imported. - Ran the real producer (
exclusive_file_lock,exclusive_cross_runtime_file_lock, and a real timeout path) against a scratch registry and evaluated every resulting file throughpaths._is_route_observation: the Windows holder record and the incident channel are recognized; POSIX writes only the already-recognized lock file. - Sensitivity: the head tests against the unfixed source →
5 failed, 2 passed; an unknown file, an unknown subdir, a recognized name as a directory, and a real registry all still flip a metadata-only root to a conflict (no masking). - Focused suites:
7 passed(route guards); wider focused run14 failed, 101 passed, 3 skipped, and the 14 failures reproduce on the unfixed base (host artifacts:WinError 1314symlink privilege, Windows path-quoting/separator rendering). - Scope:
b2aca67d7..5ccf12f2fis exactly one signed-off commit touching onlyloopx/paths.pyandtests/test_local_state_migration.py; an open-PR scan found no other PR touching either file; the coordination-registry entryagent-t8m5matches this head and distinguishes the earlier unrelatedagent-t6m2refusal claim.
Residual risks recorded by the reviewer (non-blocking):
- Crash-only lock artifacts (
.claim.*/.stale.*/.released.*, write-probe temps) can still resurrect the phantom conflict after a hard kill; the direction is fail-safe (the route refuses) and pre-existing. - The classifier ignores lock metadata even for a live holder, consistent with the pre-existing
.lockhandling where the kernel lock is authoritative. - POSIX behavior is code-evident (no
os.namebranch; plain-file tests) but was not executable on this Windows-only host; the real-hostloopx statusexit code was verified as "selection no longer raises" rather than rerunning the command against user state.
Review performed by an independent agent session at my request; verdicts above are its findings, sanitized of host-specific paths.
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max
动机
使用默认运行目录、曾在另一个默认目录留下 Windows 锁记录的 LoopX 使用者。用户已经把真正的配置放在一个默认目录,另一个目录仅有已释放的锁记录;原来后续 status 仍被误判为两个权威目录而拒绝。当前提案识别这组自有锁元数据,使原命令直接回到真正的配置;发现陌生文件或目录重定向时仍要求明确选择。
隔离目录中的真实隐式 status,在 base 因锁旁文件返回冲突;当前源码、普通 wheel 和 sdist 均选择原有 registry 并返回 0。添加陌生文件后仍拒绝,移除该文件后同一命令恢复,无需删除锁记录。
本次修复目录发现的错误分类,不清理磁盘文件,不改锁所有权,也不把尚未注册的空机器声称为健康;原生 Windows 内核锁与 junction 测试没有在本机执行。
评审对象为完整 head 5ccf12f2fd51744131ded56abb020fe2608c3e25,基准为实际 merge base b2aca67d76af45e554c620818a982a81f8d2d52b。先读原始问题与既有协议,再独立检查整个提案;作者已发布的自评只作为待核实的材料,不继承其结论。验收来源:#6657,版本 2026-10-16T14:23:32Z,依据原文的 Expected。
改动思路
这是现有运行目录发现器对既有锁生产者的分类遗漏,最小修复应留在该读边界;精确文件名集合与既有重定向检查组合即可,不需要删除记录或引入第二套路由状态。
当前 PR 交付锁旁记录、跨宿主恢复记录的识别及真实命令的拒绝和恢复验证;锁实现、目录迁移、异常崩溃清理与其他命令的报错格式保持各自原有维护边界。
判定依据是文件的既有生产者和路径类型,而不是记录内容是否“看起来无害”。名字恰好匹配还不够:重定向检查必须先发生,且对象须为普通文件。这样既能恢复正常默认路径,也能保留相反方向的安全边界:陌生状态不能因为旁边有合法锁记录就被隐藏。
协议基准为 docs/reference/protocols/file-lock-acquisition-v0.md 在 0ab7feb223f35b4a74175b5aee2909fd55aebd4a 的 holder 与 incident 约定:Windows 释放后保留 holder 是预期行为,内核锁才是所有权,读者仍需兼容旧 .holder.json。该 PR 修复读者分类,不能删除锁文件来“解决”冲突。
具体改动
_is_route_observation() 继续在最前面调用 _is_redirected_path(),然后只识别当前根自身 registry.global.json 的五种既有元数据文件:.lock、.ts-effect.lock、.lock.holder、.lock.holder.json、.lock.incidents.jsonl。只有 is_file() 为真才是观察记录。没有使用后缀通配、包含某段文字的黑名单或依赖 holder 内容的主观判断。_runtime_root_has_machine_state() 仍遍历所有直接项,default_runtime_route() 仍用同一 RuntimeRouteStatus 处理真实双根状态与无效根。
测试扩展了已有观察目录案例及自有锁参数化案例,并增加真实 exclusive_file_lock 生产者往返,覆盖新机器两个候选根只留下锁的实际起点。这里复用现有迁移测试文件与真实写入者;因 file_lock 反向导入 paths,局部列出稳定生产者文件名有明确原因,提取一个新注册中心反而会扩大这次范围。总共两个改动文件,没有新命令、开关、持久状态或新的一套 TypeScript 路由权威。
独立 base/head 宽测试分别为 102/106 通过、各 5 个同条件原生 Windows 跳过。把 head 的七个相关守卫测试原样交给 base 产品,得到 4 失败、3 通过,四处失败都对应新增元数据被当作第二个状态根;相同测试在 head 通过。macOS 上真实 POSIX 锁生产者仍使空根保持 fresh;真实 portable holder 写入函数加跨宿主归档样本则验证 Windows/旧 holder/incident 三种拼写的消费者规则。这里没有把 POSIX 内核锁执行声称为 Windows 内核锁执行。
更关键的是用户入口:在独立 home、真实 global registry 上,经正常隐式 loopx status 及原 registry codec,base 因旁文件报双根冲突,head 与普通 wheel/sdist 均回到原 legacy registry、退出 0。加入陌生文件后退出 1 并保留真实冲突说明,移除它后同一命令重新退出 0。命令没有额外传 registry/runtime 参数绕过缺陷,也没有清理锁记录。包内改动产品文件与源码、归档及安装逐字节相同。
还验证了陌生 .json、看似相近的 holder 后缀、合法名字却为目录、指向外部的 holder 符号链接,均继续声明真实状态或冲突;后加入的未知项同样不能逃过检查。外部目标内容未改。仅有锁的新机器虽然路由已正确回到 fresh,尚无注册表的 status 仍报告缺少 registry;这不是假冒健康,也没有被汇总成“所有命令都成功”。
对主干的风险
核心风险是把真正的第二个状态根过滤掉。精确对象名、普通文件限制及提前重定向检查共同收紧范围;真实第二 registry 的原测试、陌生目录与链接负例,以及添加真实状态后拒绝、删除该状态后恢复的实测,覆盖了修复误拒绝时容易漏掉的另一方向。已有文件内容、内核锁所有者及历史记录都不被本 PR 改写,跨平台归档识别也没有引入新的选择权限。
本机五个 Windows kernel/路径/junction 相关用例继续按原条件跳过,作者的 Windows 运行记录不作为本机执行计数。真实路由读者、portable metadata 生产者与隐式 CLI 已在本机实测;全部 Windows API 兼容性不在这一结论内。异常中止生成的其他未知临时项仍会保守地形成状态,未扩大精确名单去吞掉它们;这属于原有单独维护边界。
head 的语义 advisory 先于完整语义检查,正常 Chat 构建、wheel/sdist 构建与独立安装、改动文件编译和 diff 检查都通过。native canary 为 5 项直接检查、2 项选择检查,无阻塞失败;1 项 DSH 维护性 advisory 保留。独立运行 ratchet 在 base/head 的失败身份、完整内容及未改因果文件摘要一致,均为 _run_dsh_operation_host 超大函数,没有调阈值。最初旧忽略资源造成包构建拒绝,随后按正常 Chat 构建刷新再打包成功,未改产品源码或断言。没有查询、轮询、等待 GitHub CI,也没有在真实用户根目录做故障注入。
我的整体评价
APPROVE。它直接解除普通配置后持续存在的误拒绝,且真实用户命令、普通安装包和恢复步骤都有对应证据;未知状态与重定向仍保持原拒绝边界。未来整理已检查:继续复用当前小型分类 helper 与已有 RuntimeRouteStatus,暂不引入新抽象,保留有实际存量消费方的旧文件名兼容。原 issue 的 Expected 在这个明确边界内满足;不把代码批准当作锁迁移、全宿主兼容性或合并授权。
English verdict: APPROVE - exact head 5ccf12f2fd51744131ded56abb020fe2608c3e25. Own persisted lock metadata no longer creates a phantom route conflict. Actual implicit CLI source/wheel/sdist readback and unknown/redirected-state refusal/recovery pass; native Windows kernel coverage remains separate.
Goal
Close the remaining Windows case of the phantom two-root conflict that
f955e8305fixed for.lock/.ts-effect.lock: a released Windows lock-holder record (or an incident log) beside a root's own global registry must not be read back as machine state, so a Windows machine whose work has settled on one default runtime root stops refusing every implicit-default command (loopx status,update check,usage-ping status).Closes #6657.
What changed
loopx/paths.py:_is_route_observationnow recognizes the documented lock-metadata family beside the root's own global registry —registry.global.json.lock,registry.global.json.lock.holder, the legacyregistry.global.json.lock.holder.json,registry.global.json.lock.incidents.jsonl,registry.global.json.ts-effect.lock— on every platform, so an archive restored on another host cannot re-create the conflict either. Redirected entries, unknown files and real registries keep their authority.tests/test_local_state_migration.py: extendstest_own_registry_locks_do_not_declare_a_second_authorityandtest_observations_and_consumer_locks_do_not_claim_a_second_runtimewith the holder / legacy-holder / incident spellings, and addstest_a_real_lock_round_trip_keeps_the_default_route_fresh, which drives the realexclusive_file_lockproducer — the writer behind Goal configuration — so the producer→consumer contract is pinned instead of hand-placed files.Evidence
Before (pre-fix tree, sandbox with both default roots redirected to a disposable temp tree):
After (this head):
Negative case — the new guards against the unfixed
paths.py(source fix stashed, tests kept):Real-host readback on the reporting machine (read-only, no state written):
status: legacy,selected_runtime_root: %USERPROFILE%\.codex\loopx, andloopx statuswith implicit defaults completes with exit 0. Pre-fix, the same host refused with the two-root conflict message, andloopx usage-ping statusdied with an unhandledValueErrortraceback (both in #6657).Validation
At exact head
5ccf12f2fon Windows 11 (10.0.26100), Python 3.12.12:uv run --extra test python -m pytest tests/test_local_state_migration.py tests/test_file_lock.py tests/test_doctor_installation_scope.py→14 failed, 101 passed, 3 skipped. The 14 failures are host artifacts (symlink privilegeWinError 1314, Windows path-quoting/separator rendering), not new: the pristine run of this file alone gave the identical14 failed, 67 passed, and neitherloopx/paths.pynor this test file changed between that baseline488e4438eand the push baseb2aca67d7(git diff 488e4438e b2aca67d7 -- loopx/paths.py tests/test_local_state_migration.pyis empty). The four new guards are among the passes (-krun: 7 passed; the same run against the unfixed source fails 5, see above).uv run python -m ruff check loopx/paths.py tests/test_local_state_migration.py→ cleanuv run python scripts/generate_semantic_inventory.py --changed-from HEAD(on the working diff) →changed sources: 1 paths, 1 snapshots; supported candidates: 0— no new vocabulary carriersScope
loopx usage-ping statusunhandled-traceback surface (that refusal behavior is a separate defect already carried by another agent's coordination claim); thewindows-powershelllane file selection ([Bug]: native Windows lane never runs the control-plane suites the project hardened and fixed for Windows, so Windows-only regressions ship qualified #6638 / ci(windows): qualify host-hardened control-plane suites on native Windows #6639 owns the workflow).uv.lockstays uncommitted (generated by localuv sync).Checklist
git commit -s)Drafted with AI assistance at my request, under my direction and review.