Skip to content

fix(canary): keep a passing smoke's verdict when a Windows fixture holds read-only git objects - #6716

Open
JasonBuildAI wants to merge 5 commits into
loopx-project:mainfrom
JasonBuildAI:codex/agent-x5p2
Open

JasonBuildAI wants to merge 5 commits into
loopx-project:mainfrom
JasonBuildAI:codex/agent-x5p2

Conversation

@JasonBuildAI

@JasonBuildAI JasonBuildAI commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

Author Declaration

  • Written by: model_agent — an AI agent running in DeepSeek Harness (model
    deepseek-v4.1-flash).

AI disclosure: this change was drafted with AI assistance at my request, under my direction
and review. I set the constraints (one host behaviour, both owners it reaches, no unrelated
cleanup or refactor) and reviewed the diff, the failing-before evidence and the check output
before submission.

Implemented against

  • Specification and revision: no accepted RFC or protocol document covers smoke-fixture
    teardown; the basis is the reproduced defect and its acceptance criteria in
    #6710, against base revision
    fdf03ec61.
  • Criteria:
Criterion (spec clause) Disposition Symbol / path Test or command
A check whose command exited 0 is reported as passed implemented loopx/canary/runner.py::_remove_fixture_root tests/canary/test_smoke_fixture_cleanup.py::test_a_passing_check_survives_a_read_only_fixture_member; real_entrypoint row
The owned fixture root is removed instead of retained implemented loopx/extensions/process_runtime.py::remove_owned_tree same test asserts the reported root no longer exists
Cleanup that cannot be confirmed still fails the check and retains the tree implemented loopx/canary/runner.py cleanup-error path tests/canary/test_smoke_fixture_cleanup.py::test_the_runner_still_reports_cleanup_that_cannot_be_confirmed
The issue-fix packet's temporary repository teardown no longer raises implemented loopx/capabilities/issue_fix/acceptance_loop.py::_remove_temporary_git_workspace tests/capabilities/test_issue_fix_temporary_workspace.py; python examples/issue-fix-workflow-e2e-smoke.py
POSIX deletion behaviour is unchanged implemented remove_owned_tree acts only on os.name == "nt" for attributes; a linked root is refused on every host the new regression tests pass on POSIX with and without the fix; no POSIX-only path changed
Teardown never changes permissions outside the owned tree implemented remove_owned_tree linked-root guard; _clear_windows_read_only_files skips linked directories tests/extensions/test_process_runtime.py symlink and native-Windows junction no-mutation regressions
A shared hard-linked object is never modified by teardown implemented _clear_windows_read_only_files leaves any file with st_nlink > 1 untouched and lets rmtree fail closed tests/extensions/test_process_runtime.py hard-link regression; tests/canary/test_smoke_fixture_cleanup.py real-runner regression
Public smokes that carried their own copy of the teardown use the shared owner implemented examples/issue-fix-reviewer-recommendation-smoke.py, examples/issue-fix-reviewer-request-smoke.py loopx canary smoke-suite --suite full-public --script … sweep row
  • Self-check before submission: read the changed call sites and the pre-merge classification
    that attaches canary-runner to control-plane diffs; ran the real entrypoints before and
    after; confirmed the three remaining tests/canary failures and the four WinError 32
    smoke failures are present on the untouched base revision (evidence below) and deliberately
    left them out of scope.

  • Placement rationale for the new owner: remove_owned_tree sits in
    loopx/extensions/process_runtime.py beside terminate_process_tree, the module that
    already owns teardown of an owned, previously isolated host resource and is imported by
    loopx/canary/runner.py, loopx/control_plane/runtime/validation_command.py,
    loopx/chat_agent.py and loopx/zcode_goal_mode/diagnostics.py. A new top-level module
    would spend part of the pinned top-level module budget (see [Bug]: the pinned top-level module budget is already exceeded on main (150 measured against 147) #5685) for one stdlib-only
    function, and a home under loopx/canary/ would make a capability import the gate it is
    validated by.

Scope And Continuation

  • Completed scope and remaining work: complete within this scope. Named items deliberately not
    touched:

  • Slice boundary / successor: N/A — the reproduced defect is closed; the remaining items above
    have their own owner or are named as separate work.

  • Independent review, and what it changed: an independent adversarial review of revision
    97756955e (value, scope, logic and edge cases, abstraction, test sufficiency, check
    plausibility) returned APPROVE WITH NOTES and reproduced the before/after verdict, the two
    smoke entrypoints, the facade and the pre-existing failures on the untouched base. Its two
    findings are fixed here: (1) the three regression suites ran on no hosted Windows lane, so
    the guard was inert where the bug reproduces — .github/workflows/python-tests.yml now
    qualifies them on windows-latest; (2) one canary test monkeypatched a symbol this PR
    introduces and therefore tested nothing pre-fix — it is now a fail-closed contract test over
    the teardown seam (remove_owned_tree), and the tautological shutil.rmtree mock test was
    dropped. The reviewer's residual nits are deliberately not applied: the pre-existing
    ten-attempt loops in the two example smokes and the five-attempt loop in the issue-fix
    teardown keep their transient-race handling, and each attempt now re-walks the tree, which is
    bounded by the size of the owned fixture.

  • Second independent review, and what it changed: a review of d1062324c returned
    CHANGES_REQUESTED with one P2 — os.walk starts at a root that is itself a directory symlink
    or Windows junction, so the Windows attribute pass cleared read-only bits in the link target
    before shutil.rmtree refused the link (base/head counterfactual through the real runner:
    target file 0444 throughout on base versus 0444 → 0666 on head). Fixed at this head:
    remove_owned_tree refuses a linked or junction root before any attribute change, and the
    Windows walk now prunes linked directories so a nested junction is not rewritten either. New
    regressions cover a directory symlink and a native Windows junction and assert the external
    file keeps both its bytes and its read-only attribute. The reviewer's native-Windows and
    junction qualification limits are addressed for junction roots by running that regression on
    the shipped windows-powershell lane; a directory-symlink case still skips on hosts without
    symlink privilege, where the junction case is the discriminator.

  • Third independent review, and what it changed: a review of 5c7f2d9c1 returned
    CHANGES_REQUESTED with one P2 — a normal local git clone hard-links loose objects, so the
    clone object and the source object are one file with one read-only attribute; the Windows
    attribute pass cleared that shared bit outside the fixture while teardown still reported
    success. Fixed at this head: _clear_windows_read_only_files leaves every file with
    st_nlink > 1 untouched, so shutil.rmtree fails closed and the caller retains the fixture
    instead of changing another owner's object. Regressions cover the helper with a hard-link
    fixture and the real canary runner (command exits 0, cleanup failure is reported, the fixture
    is retained, and the external object keeps 0444 and its bytes). Native-Windows
    counterfactual: the pre-fix helper returned success and moved the source object 0444 →
    0666; the fixed helper raises PermissionError and leaves it 0444. Disclosed tradeoff:
    a fixture that leaves read-only hard-linked objects behind now reports cleanup failure and is
    retained; that is the fail-closed boundary. The shipped issue-fix repository fixture creates
    its own objects with git init (link count 1), so its passing path is unchanged.

Validation

  • Tested revision: 803c0e242 (base f9bdae675). Independent adversarial reviews were
    performed at 97756955e, d1062324c and 5c7f2d9c1; every finding is fixed in this head and
    named under Scope And Continuation.
  • Run state: finished
  • Input classes: synthetic, public_fixture
Check kind Result Public-safe evidence / limitation
static passed python -m ruff check over every touched file: All checks passed!. scripts/generate_semantic_inventory.py --changed-from HEAD: no supported new vocabulary carriers (the change introduces no enum, literal or closed set).
unit passed tests/canary/test_smoke_fixture_cleanup.py 3 passed; tests/capabilities/test_issue_fix_temporary_workspace.py 1 passed; tests/extensions/test_process_runtime.py 9 passed / 27 skipped (the 26 existing POSIX-only process-group regressions plus the directory-symlink case, which needs symlink privilege on this host; the native junction and hard-link cases run and pass here).
regression_parity passed A hard link to a read-only object outside the fixture, run through the real remove_owned_tree on native Windows: pre-fix the call returned success and the source object went 0444 → 0666; at this head the call raises PermissionError, the source object stays 0444 with its bytes unchanged, and the fixture is retained. test_remove_owned_tree_never_clears_a_shared_hard_link_attribute fails on the pre-fix helper and passes here; test_a_shared_hard_link_is_not_rewritten_outside_the_fixture pins the same input through run_canary_smoke_check (returncode 0, ok: false, cleanup reason, retained root).
regression_parity passed A root junction to a directory holding a read-only .git/objects-shaped file, run through the real remove_owned_tree on native Windows: pre-fix the file went 0444 → 0666 and the helper then raised Cannot call rmtree on a symbolic link; at this head the same call raises refusing to remove linked tree root before the walk and the file stays 0444 with its bytes unchanged. test_remove_owned_tree_refuses_a_junction_root_without_touching_its_target therefore fails on the pre-fix helper and passes here; the symlink case pins the same contract cross-host.
regression_parity passed Substituting a plain shutil.rmtree at the two call sites (the pre-fix behaviour) on native Windows makes test_a_passing_check_survives_a_read_only_fixture_member and test_temporary_git_workspace_teardown_removes_read_only_objects fail with PermissionError [WinError 5] on a .git/objects path, and makes the pytest-facade check fail with assert False is True. With the owner they pass. On POSIX the same tests pass either way, because unlink ignores the read-only bit there; the Windows host is the discriminator.
real_entrypoint passed loopx canary smoke-suite --suite full-public --script examples/canary/catalog-planner-smoke.py: before status failed, ok false, returncode 0, reason fixture cleanup failed … [WinError 5] … .git\objects\…; after status passed, ok true, failure_count 0, stdout_tail catalog-canary-planner-smoke ok.
real_entrypoint passed python examples/issue-fix-workflow-e2e-smoke.py: before PermissionError [WinError 5] from acceptance_loop._remove_temporary_git_workspace; after issue-fix-workflow-e2e-smoke: ok, exit 0.
real_entrypoint passed python -m pytest tests/test_smoke_suite.py --loopx-smoke-script examples/canary/catalog-planner-smoke.py: before 1 failed / 1 passed with assert False is True; after 2 passed.
integration passed Native Windows sweep of the 33 of 514 full-public checks that invoke a git object write (loopx canary smoke-suite --suite full-public --jobs 4): failures 20 → 18; checks whose failure mentioned read-only unlink 4 → 0; verdicts changed failure → pass: catalog-planner-smoke and issue-fix-reviewer-recommendation-smoke; no check changed pass → failure. Two checks (issue-fix-acceptance-loop-smoke, issue-fix-reviewer-request-smoke) stay failing for their own, unrelated assertion once the teardown error stops masking it.
manual failed loopx canary premerge --from-git-diff --git-diff-base f9bdae675 on this 9-file diff: 6 direct checks 0 failures (diff hygiene, RFC bilingual, py_compile, module ceiling); risk_profile_run 8 executed / 0 failures; boundary_run 1 executed / 0 failures; catalog_run 8 of 10 passed. The two catalog failures are environment-bound and pre-existing on this host: cli-output-budget-regression-smoke exceeds the 120 s per-check budget (direct run: 202 s, then the known PermissionError [WinError 32] on a temp root held by a live helper, tracked by #6642 / PR #6652), and issue-fix-json-input-boundary-smoke hits a GBK console decode while capturing a UTF-8 child, unrelated to teardown. Aggregate ok: false for those two; neither touches code changed here. The before run (fix absent, scratch control-plane diff) failed the same section with 3 failures, one of which was catalog-planner-smoke reporting returncode 0 as failed with the read-only reason.
static passed python -m mypy --platform linux: Success: no issues found in 17 source files (the Windows junction attribute is read with getattr, so the cross-platform stat_result type is not widened). ruff check over every touched file: All checks passed!; the semantic inventory advisory reports no supported new vocabulary carriers. .github/workflows/python-tests.yml adds one windows-powershell step that runs the three regression files on windows-latest, and scripts/ci/test_windows_lane_coverage.py still passes (2 passed, 12 subtests). Without that step the new suites pass trivially on Ubuntu and never run on the host where the defect reproduces.
unit failed tests/canary on this Windows host: 3 pre-existing failures (test_example_symlink_cannot_escape_selected_checkout, test_telemetry_isolation.py::test_update_smoke_minimal_environment_keeps_opt_out, test_telemetry_isolation.py::test_smokes_isolate_routes_and_home_in_serial_and_parallel). Proof they are not from this diff: the same 3 tests fail the same way on the untouched base revision fdf03ec61 in a clean worktree (unprivileged symlink creation, a POSIX-HOME assumption in Path.home(), and the unrelated loopx-update-smoke plan assertion).
  • Coverage and gaps: the changed lines are covered by both owners' regression tests plus real
    entrypoint runs on the host where the defect reproduces. Not covered by construction: none of
    the read-only-member tests can discriminate on POSIX, where unlink ignores the read-only bit,
    so the native-Windows lane is the discriminator and this PR adds the three suites to its
    qualification step. The linked-root guard is covered on POSIX by the directory-symlink case,
    and discriminatingly on Windows by the junction case, which needs no symlink privilege. The
    shared-hard-link guard is covered twice: the helper-level hard-link regression and the
    real-runner case, both asserting the external object keeps 0444 and its bytes. Its
    limitation is deliberate: a fixture that retains read-only hard-linked objects fails closed and
    is kept rather than silently rewriting the source. CI on head 5c7f2d9c1 passed
    kernel-static-checks (including mypy), windows-powershell, typescript-core,
    dashboard-browser, stage2c, both build jobs and the other lanes; its test-shard jobs
    were still pending when this head replaced it, and they are rerunning here. The earlier
    d1062324c shard failures were tests/architecture/test_top_level_module_budget.py (a
    main-side budget mismatch: loopx/workflow_skill_install.py is the 146th top-level module
    while the pinned budget is 145, which reproduces on this head without any diff in that path)
    plus two assertions in tests/test_managed_effect_runtime_fixture.py and
    tests/presentation/test_codex_attached_conversation.py that pass locally on this head and
    live in files this diff does not change. This head is rebased onto f9bdae675, so CI reruns
    on the exact requested head.

Frontend / Visual Evidence

  • UI impact: none — no dashboard, website, desktop, documentation chrome or other user-visible
    surface changed.
  • Before: N/A
  • After: N/A
  • States and viewports shown: N/A
  • Source data: none
  • Attention review: N/A — no viewport changed.
  • Craft review: N/A — no showcase surface changed.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Build, packaging, installer, or CI
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Host or runtime integration

Primary area: Build, packaging, installer, or CI (the canary pre-merge/smoke-suite gate); the
shared teardown owner and the issue-fix call site are why Host or runtime integration is also
marked.

Technical Direction

  • Direction / acceptance reference, when applicable: N/A — self-contained reproduced defect;
    no roadmap S/G/R, milestone card or RFC section is claimed.

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A — no TypeScript control-plane or shared Goal Authority
    contract is touched.
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A.
  • Provider conformance arms run: N/A.
  • Read-only legacy/file/PostgreSQL three-arm rehearsal: N/A.

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md). Absolute host paths in the quoted evidence are shown as <TEMP>.
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • Existing English/Chinese RFC pairs (including ledger entries) both have a diff, and the local premerge check python3 scripts/check_rfc_bilingual.py --base origin/main --head HEAD passes, or no paired RFC changed.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

JasonBuildAI added a commit to JasonBuildAI/loopx that referenced this pull request Oct 17, 2026

@JasonBuildAI JasonBuildAI left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Author record of the independent review. No self-approval is implied by this comment.

An independent adversarial reviewer — given only the linked issue, the diff, the repro steps and the check commands — reviewed revision 97756955e and returned APPROVE WITH NOTES after reproducing the central before/after at the real entrypoint:

  • loopx canary smoke-suite --suite full-public --script examples/canary/catalog-planner-smoke.py: base status failed / returncode 0 / reason fixture cleanup failed … [WinError 5] … .git\objects\… → head status passed / failure_count 0.
  • python examples/issue-fix-reviewer-recommendation-smoke.py: base exit 1 with an unhandled PermissionError [WinError 5] → head ok, exit 0.
  • python -m pytest tests/test_smoke_suite.py --loopx-smoke-script examples/canary/catalog-planner-smoke.py: base 1 failed / 1 passed (assert False is True on the rc-0 payload) → head 2 passed.
  • ruff check over every touched file: All checks passed!; the required pytest set matches the PR body.
  • The three tests/canary failures this PR declares pre-existing were reproduced on the untouched base revision fdf03ec61.

Both findings are fixed at head d1062324c, and the PR body records them:

  1. The new suites ran on no hosted Windows lane, so the guard was inert where the defect reproduces (they pass trivially on Ubuntu, where unlink ignores the read-only bit). .github/workflows/python-tests.yml now qualifies all three files in the windows-powershell lane; scripts/ci/test_windows_lane_coverage.py still passes.
  2. tests/canary/test_smoke_fixture_cleanup.py case 2 monkeypatched _remove_fixture_root, a symbol this PR introduces, so it did not exercise the fix. It now patches the teardown seam remove_owned_tree and pins the fail-closed contract instead, and the tautological shutil.rmtree mock test was dropped.

Not reproduced by the reviewer: the 33-check full-public sweep row (failures 20 → 18, checks naming read-only unlink 4 → 0). That row is this author's run on native Windows 11 and stays author-reported evidence.

Re-verified at d1062324c after rebasing onto 555a83df1 (base moved forward since the tested revision): ruff clean; the three focused suites 12 passed / 26 skipped; the lane guard 2 passed / 12 subtests; examples/issue-fix-workflow-e2e-smoke.py and examples/issue-fix-reviewer-recommendation-smoke.py exit 0; the single-check runner verdict status passed / failure_count 0; the pytest facade 2 passed.

JasonBuildAI added a commit to JasonBuildAI/loopx that referenced this pull request Oct 17, 2026
…head d106232

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max.

动机

这项修改针对在 Windows 上运行 canary 和 issue-fix 校验的开发者:检查命令已经成功,但 Git 留下的只读对象无法删除,最终却收到失败结果,还保留了整个临时目录。预期改善是让这类正常检查成功收尾,同时真正无法完成清理时仍保留目录和原命令输出。

本次按完整 head d1062324ca2137233613c5c468c47dec12976c50 审查九个文件,没有继承作者自审结论。原始问题见 #6710;独立边界依据是 docs/development/testing-and-quality.md,固定版本 555a83df107c4711de49ee5451af5e34f4fff558 的 Smoke cleanup:清理不能跟随 namespace 或 locator 符号链接,失败须保留现场。当前仍有一个 P2:新的 Windows 预处理会先修改链接外文件的权限,再失败。这需要在合入前修复。本次不认证原生 Windows 的完整稳定性或持续运行,也不涉及操作员 restart、调度规则或安装升级。

改动思路

先让已有的 managed runtime 清理确认进程退出,再统一删除自有目录;仅 Windows 在删除前清除只读文件属性。这个公共文件系统 helper 放在现有 process_runtime 中合理,canary、issue-fix 和两个已有 smoke 复用它,各自仍保留现有失败/重试责任。没有必要新增能力、配置开关、TS 决策层或通用重试框架。Windows 上的属性处理属于平台 IO,保留 Python 是合适的。

但是文件条目的 islink 检查不足以保护根目录。os.walk(root) 会遍历作为起点传入的目录链接;随后才执行的 shutil.rmtree(root) 虽然拒绝这个链接,已经来不及阻止此前的 chmod。最小修复是在任何属性修改前拒绝根目录链接,并补充外部目标完全不变的测试。保留当前 helper 和调用关系即可,不需要扩大本 PR。

具体改动

  • loopx/extensions/process_runtime.py:新增 _clear_windows_read_only_files 和 remove_owned_tree。前者扫描、跳过文件链接、按需加写权限;后者在 Windows 调用前者再执行 rmtree。异常仍交回调用者,但目前遗漏根目录链接。
  • loopx/canary/runner.py:_remove_fixture_root 先退休 fixture runtime,再调用新 helper;_run_check 保持清理失败时 failed、保留目录和命令输出的行为。这里的失败结果并不能撤销已经发生的外部权限修改。
  • loopx/capabilities/issue_fix/acceptance_loop.py 和两个 issue-fix-reviewer-*.py smoke:替换原来的删除实现,沿用原有有界重试。没有改变候选评审或接受语义。
  • 三个测试文件覆盖只读成员、正常删除和清理不能确认时的失败结果;Windows PowerShell workflow 把这些测试真正纳入平台 lane。这修正了此前“POSIX 测试通过但 Windows lane 未运行新增回归”的覆盖缺口;我没有查询或派发 CI。

独立反例走真实 run_canary_smoke_check:子进程把其自建临时根替换成指向另一个合成目录的链接,外部目录只有一个 0444 普通文件。相同脚本在 base 和 head 上运行,只替换 process_runtime 的平台分支选择,其余 walk/stat/chmod/rmtree、子进程和 canary 结果均为真实调用。base 报清理失败且外部文件保持 0444;head 同样报失败,但文件已变成 0644。这是 Windows 分支的控制实验,宿主是 macOS,不能冒称原生 Windows 复现;它明确定位了新增副作用的因果路径。

对主干的风险

[P2] 在修改属性前拒绝根目录链接。 位置:loopx/extensions/process_runtime.py:253,调用入口在 remove_owned_tree:280–283。触发条件是清理开始时根目录已经成为目录符号链接。当前文件级防护不会检查起点,于是失败的清理仍会修改自有 namespace 之外的只读文件。canary 和 issue-fix 共用此 helper,所以不能依靠某一个调用者最终报错来证明隔离。请增加根链接负例,并断言目标目录及文件权限、内容都不变;保留当前普通文件/文件链接/正常只读成员测试。

验证结果:116 个相关 pytest 通过;Ruff、语义 inventory advisory 后的完整 semantic 检查、DCO 和 diff hygiene 通过;原生 premerge 的 6 个直接检查及 19 个选中检查通过。另有三组相同输入的真实 POSIX base/head 对照:普通文件成功、只读文件成功、命令退出 7;两侧都正确删除自有目录并保留原退出结果。上述绿色结果没有覆盖根链接反例,不能抵销该 P2。

原生 Windows 和 Windows junction 没有独立运行,仍是明确限制;我没有把 junction 当作已证明的新缺陷。初次本地反例命令因脚本路径写成相对路径而未启动,改用绝对路径后才形成上述 base/head 证据,这个启动错误不算产品失败。

我的整体评价

REQUEST_CHANGES。动机成立、复用方向和修改规模合适,当前边界应是“安全地清理自有 fixture 的只读成员”,而不是“能删掉目录即可”。Smoke cleanup 的隔离要求被根链接路径违反,需要先补最小 guard 和敏感回归,再重新核验普通 Windows 只读场景与失败现场保留。

相邻重构检查已做:统一这四个删除调用点有价值,保留各自重试/结果所有权;没有需要另建抽象或扩大迁移的理由。现有 CLI 与前端无需新增导航、复制参数或确认步骤;正常任务仍直接运行、读回结果,错误应准确且不修改外部目录。该评审不授予合并、生产清理或升级权限。

English verdict: REQUEST_CHANGES - d106232: the Windows pre-walk chmods files outside an owned fixture when its root is a directory symlink, before rmtree refuses it. Base/head real-runner counterfactual confirms the added side effect; 116 related tests and 6 direct/19 selected premerge checks pass. Reject the root link before chmod and add a no-external-mutation regression; native Windows qualification remains unverified.

Comment thread loopx/extensions/process_runtime.py Outdated
unlinks a read-only member of a writable directory. Links are skipped so
this walk cannot reach a target outside the owned tree.
"""
for directory, _subdirectories, files in os.walk(root):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 在 walk 前拒绝根目录链接。这里只跳过 files 中的链接,但 os.walk 会跟随作为 root 传入的目录链接:若 smoke 在退出前把自建临时根替换成指向外部目录的链接,新增 Windows 分支先把目标文件从只读改成可写,随后 rmtree 才拒绝链接。真实 canary runner 的同输入 base/head 控制实验中,两者均报 cleanup failed,但 base 保持外部文件0444,head改成0644;仅平台分支选择被替换,其余文件系统和子进程真实,宿主macOS,未冒称原生Windows。请在任何 chmod 前拒绝根链接,并补断言外部文件权限及内容不变的负例,保留普通只读成员可清理的覆盖。

@JasonBuildAI JasonBuildAI Oct 17, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已按 P2 修复,新 head 为 5c7f2d9c1(已 rebase 到 f9bdae675)。

修复

  • remove_owned_tree 在任何属性修改前拒绝根链接:os.path.islink 覆盖目录 symlink;Windows 额外通过 getattr(os.lstat(target), "st_file_attributes", 0) & FILE_ATTRIBUTE_REPARSE_POINT 识别目录 reparse point(junction/mount point,兼容 Python 3.11,os.path.isjunction 3.12 才提供)。拒绝时抛 OSError("refusing to remove linked tree root: ..."),调用方原有的 fail-closed/保留现场语义不变。
  • _clear_windows_read_only_files 在 walk 中同步剪掉链接目录,嵌套 junction 也不会被改权限。

负例(断言外部目录文件内容与只读属性均不变)

  • test_remove_owned_tree_refuses_a_junction_root_without_touching_its_target:原生 Windows 上用无特权 mklink /J 构造根 junction,目标文件 0444 + b"blob" 在调用后保持不变;该用例在修复前的 helper 上失败。
  • test_remove_owned_tree_refuses_a_symlinked_root_without_touching_its_target:目录 symlink 的同一契约;本机无 symlink 权限时 skip,Windows lane 的 junction 用例是判别器。
  • 原有普通只读成员可清理的用例保留。

原生 Windows 对照(本机 Windows 11 / Python 3.14):修复前根 junction 下目标文件 0444 → 0666,随后抛 Cannot call rmtree on a symbolic link;修复后调用直接抛 refusing to remove linked tree root,文件保持 0444 且内容不变。

验证

  • 三个套件 11 passed / 27 skipped(tests/extensions/test_process_runtime.py 8 passed:junction 用例执行,symlink 用例因本机无权限 skip);加上 scripts/ci/test_windows_lane_coverage.py 后 13 passed / 27 skipped / 12 subtests。
  • ruff check 通过;rebase 后已重跑。
  • python -m mypy --platform linux:17 files 通过。首版直接访问 st_file_attributes 会在 Linux 类型检查下报错(上一 head 的 kernel-static-checks 失败原因),现已改为 getattr 读取。
  • PR body 已更新:criteria 增加“teardown 不改变自有树外权限”,并更新 Validation 与限制说明。

附带说明:上一 head 的 test-shard 失败与本 diff 无关——test_top_level_module_budget 是 main 侧 loopx/workflow_skill_install.py 使顶层模块变为 146、而 pinned budget 仍为 145 的既有不一致(本 head 上同样复现);另两处断言在本 head 本地通过,且所在文件未被本 PR 改动。

请在新 head 上重新审阅。

@JasonBuildAI
JasonBuildAI force-pushed the codex/agent-x5p2 branch 3 times, most recently from 2614c84 to 3b91cf8 Compare October 17, 2026 09:40
…ad-only git objects

On native Windows the canary smoke runner could not remove its own per-check
fixture root whenever the smoke created a git repository inside it: git marks
every loose object and pack read-only, and Windows refuses to unlink a read-only
file, so `shutil.rmtree` raised `PermissionError` [WinError 5]. The runner then
recorded the check as failed although its command had exited 0, retained the
tree, and reported the cleanup error as the check's own reason, so a contributor
could not tell a real regression from harness noise.

Owned tree removal now goes through
`extensions.process_runtime.remove_owned_tree`, which clears the Windows
read-only attribute of the files in the tree before removing it. POSIX behaviour
is unchanged: there a read-only member of a writable directory never blocks
unlink, so no extra code runs. A removal that still cannot complete raises, so a
caller that must retain the tree for diagnosis keeps observing it.

The same host behaviour aborted the issue-fix packet's temporary git workspace
teardown from its `finally` block after five pointless retries, and in two public
reviewer smokes it masked a real failure behind the cleanup error; those call
sites now use the same owner.

Refs loopx-project#6710

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
The canary-runner and issue-fix teardown regressions fail on native Windows with
`PermissionError` [WinError 5] when the fix is absent, and pass on POSIX either
way because a read-only member never blocks unlink there; the pre-fix behaviour
was reproduced by substituting a plain `shutil.rmtree` at the two call sites.

Refs loopx-project#6710

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
The new regression suites pass on POSIX with or without the fix, because unlink
there ignores the read-only bit, and the curated `windows-powershell` lane ran
none of them, so the native-Windows behaviour they encode had no guard on the
host where it reproduces. Point that lane's qualification at them.

Refs loopx-project#6710

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
os.walk starts at a root that is itself a directory symlink or Windows
junction, so the Windows pre-pass could clear the read-only attribute of a
file in the link target before shutil.rmtree refused the link. Refuse a
linked root first, and skip linked directories inside the walk, so teardown
never changes permissions outside the owned tree.

Cover the rejection with a no-external-mutation regression for both a
directory symlink and a native Windows junction.

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max

动机

在 Windows 上运行 canary 或 issue-fix 校验的开发者,需要检查结果准确,并且清理只作用于自己的临时目录。

以前只读 Git 对象可能使成功命令在清理时被判失败;现在统一处理只读属性并提前拒绝根链接,但普通本地 Git 克隆的硬链接仍会把源仓库对象改成可写。

根目录链接反例在当前 head 已保持外部文件不变;新的真实 runner 对照中,两边都成功清理克隆目录,只有 head 改变了目录外三个源对象的权限。

本次不认证原生 Windows 的完整稳定性、junction 或持续运行,不涉及操作员 restart、生产清理或安装升级。

仍需保护共享硬链接的源文件,补充普通本地克隆的无外部属性修改回归,然后复评新 head。

改动思路

平台文件系统处理复用现有 process_runtime,调用者保留重试和失败结果;修复硬链接隔离即可,无需新增能力或重试框架。

本 PR 的边界是安全地清理自有 fixture 的只读成员,外部文件的属性与内容必须保持不变。

同一准确head的源码关系如下;这是解释表,不替代执行证据:

清理对象 原路径 当前结果
自有普通只读成员 Windows的plain rmtree可能失败 先处理属性,再删除;普通POSIX正反例保持原命令结果
根目录symlink 上轮先walk外部目标再失败 本head提前拒绝,外部0444与内容不变
本地clone中的普通硬链接对象 删除克隆链接不修改源对象属性 本head把共享对象chmod为可写,源仓库也改变,最终却报告通过

硬链接是同一文件的另一个目录项;它不是symlink或junction。因此根链接guard修复成立,但不能证明每一个普通文件都由临时目录独占。平台IO留在Python是合适的;判定自有资源的边界不能由一个成功清理结果反推。

具体改动

完整head 5c7f2d9c13db3d7b5a10d84988cd9bb87b60340a,实际base f9bdae67596c185cebc81dc555dd6a3e0242e9c5,9路径、+328/-10。本轮先遇到2614→324881aa4,再遇到当前5c7f2d9c1;每次重新取得native exact-head plan并读取frame增量。最后一处源码差异是通过getattr读取Windows专有属性,已重新执行公共runner反例和当前head检查,没有把旧head声明当作新执行。

独立依据是原始问题 #6710 及改动前 docs/development/testing-and-quality.md 的 Smoke cleanup:清理不能跟随namespace/locator链接,无法确认时保留现场和原命令输出;现有隔离合同不授予目录外文件效果。按这些criteria:原问题 Expected behavior 的普通只读成员/失败报告路径已覆盖,根链接无外部修改已验证,共享硬链接的无外部属性修改 not_met。不是用本PR新docstring定义正确性。

关键代码讲解

  1. _is_link_or_junction(loopx/extensions/process_runtime.py:246)先检测symlink;Windows再由lstat的directory/reparse属性拒绝junction/mount。remove_owned_tree:318–323 在任何属性处理前检查根。这补上上一轮的真实root-link缺口,并保留caller失败/现场留存。
  2. _clear_windows_read_only_files:283–298 剪掉目录链接,跳过文件链接,然后对其余只读文件调用 os.chmod。问题位于294–296:普通硬链接会通过这些检查;chmod改变的是共享文件,而不只是这一个目录项。
  3. _remove_fixture_root(loopx/canary/runner.py:290)先退休自有runtime,再调用公共删除helper,_run_check 仍在真正清理失败时保留目录、命令returncode和输出。新硬链接反例没有抛异常,故这个error分支看不见已经发生的越界属性修改。
  4. _remove_temporary_git_workspace(loopx/capabilities/issue_fix/acceptance_loop.py:259)以及两个现有reviewer smoke复用helper,各自保留既有有界重试。统一文件系统规则有价值,不能靠重复重试撤销外部chmod。

三个测试文件覆盖read-only成员、cleanup fail-closed及root symlink/junction;Windows PowerShell lane真实包含它们,避免只在POSIX上验证一个不具平台区分力的成功结果。没有新增共享状态、CLI、持久化字段、额度或第二个TS/Python决策owner;其它未使用这个helper的旧清理点不因形状相似而被强制纳入本次。

对主干的风险

[P2] 修改只读属性前保护共享硬链接。 loopx/extensions/process_runtime.py:294–296。触发不需要竞态或恶意目录替换:smoke对子目录外的本地合成Git仓库执行普通 git clone --local --no-checkout,Git在克隆目录内创建硬链接对象。新清理把克隆对象加写权限时,源仓库的同一对象也被修改。成功结果不能授权这个外部效果。

独立控制实验走真实 run_canary_smoke_check、真实Git子进程和walk/stat/chmod/rmtree:两端相同输入,清理前源/克隆对象inode相同且link count均为2;base与head均returncode0、status passed、删除自有root,但只有head把目录外三个源对象从0444改为0644,原内容和inode不变。宿主是macOS,只选择helper的Windows分支;当前源码已经用getattr兼容该属性,因此本次lstat也保持真实调用,其余IO真实。这是分支因果实验,不是原生Windows或NTFS证明,仍精确定位新增副作用,不能用native平台尚未执行把已观测路径标绿。

最小修复是对可能共享的文件保持fail-closed,或先在自有路径解除共享再处理属性;不能把“不是symlink”当成独占。请补真实本地clone或硬链接fixture,断言源文件内容和属性不变,并保留根链接拒绝、普通只读删除、失败现场、命令失败结果与Windows lane。新head至少重跑 uv run --extra test python -m pytest -q tests/canary/test_smoke_fixture_cleanup.py tests/extensions/test_process_runtime.py tests/capabilities/test_issue_fix_temporary_workspace.py,并通过公共runner复现该输入。

独立本地验证:119 passed、1 skipped、12 subtests;唯一skip为native Windows junction。Ruff覆盖所有改动Python、advisory后full semantic、9路径公开扫描、DCO/diff/bilingual通过;标准premerge为6 direct+19 selected全过,真实issue-fix e2e exit0。另有普通文件成功、只读文件成功、只读文件命令exit7的三组真实POSIX base/head对照,均正确清理并保留原命令状态。所有绿色项仍遗漏这个新硬链接反例。根链接当前已拒绝并保持外部0444,未重复报告已修复的旧finding;未查询、轮询或派发CI。

我的整体评价

REQUEST_CHANGES — 5c7f2d9c13db3d7b5a10d84988cd9bb87b60340a,保留一项上述P2。原始用户价值和共享helper归属合理,necessary/proportionate,architecture retain;long_horizon 为 regression,因为重复验证可无声修改共享源对象,minimum repair是补硬链接隔离;user_experience 为 preserved,仅指原命令/导航/失败读回未增加准备或确认,不能抵消外部副作用。

未来改动的简化检查已应用:合并四处平台删除规则,并保留不同caller的重试/结果所有权;不需要泛化资源管理器或无关语言迁移。协议名 remove_owned_tree 与所需语义相称,当前实现未守住该名称要求的ownership边界;OS条件是平台行为,不是默认关闭的可选能力。语义词表保持local_only,未新增共享状态枚举;现有独立隔离义务是必须守住的invariant,不是指导建议。原生Windows读写属性、junction、长期稳定性仍未独立qualified,修复后需准确head复评;本评审不授予merge或真实生产清理权限。

English verdict: REQUEST_CHANGES — 5c7f2d9. Root-link rejection is fixed, but the Windows chmod pre-pass still modifies shared hard-linked Git objects outside the owned fixture after a normal local clone, while the real runner reports success. Immutable-base/current-head branch-controlled comparison confirms the added effect;119 tests and6 direct/19 selected checks pass but miss it. Protect shared files and add a no-external-mutation regression; native Windows remains independently unverified.

Comment thread loopx/extensions/process_runtime.py Outdated
try:
mode = os.stat(target).st_mode
if not mode & stat.S_IWRITE:
os.chmod(target, mode | stat.S_IWRITE)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] chmod 前保护共享硬链接。普通 git clone --local --no-checkout 可让临时克隆与目录外源仓库共享只读对象;它们不是 symlink/junction,会走到这里的 chmod。独立真实 canary runner 同输入 base/head 对照:清理前 source/clone inode相同且 nlink=2,两边均报告passed并删除自有root,只有head把三个外部源对象0444改为0644,内容与inode不变。宿主macOS,仅选择Windows分支;lstat、Git、子进程和文件系统操作均为真实调用,非nativeWindows证明。请在改属性前拒绝或安全隔离可能共享的文件,补本地clone/硬链接无外部属性修改负例,并保留普通只读删除、根链接拒绝和失败现场回归。

A git clone --local hard-links loose objects, and Windows keeps the
read-only attribute on the shared file rather than on one directory entry.
The Windows pre-pass therefore cleared the source repository object too,
while the teardown still reported success. Leave every file with more than
one link untouched so rmtree fails closed and the caller retains the fixture
instead of mutating an object outside it.

Cover the fixture entry and the real canary runner with a hard-link
regression that asserts the external object keeps its bytes and read-only
attribute.

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
@JasonBuildAI

Copy link
Copy Markdown
Contributor Author

已在 head 803c0e242 修复本轮 P2(共享硬链接)。

修复

  • _clear_windows_read_only_files 现在对 os.stat(target).st_nlink > 1 的文件保持原样、不再 chmod;随后 shutil.rmtree 失败,runner 按 fail-closed 保留 fixture 并报告 cleanup failure,因此不会改动 fixture 外同一 inode 的源对象。
  • 根链接拒绝、普通只读成员可清理、失败现场保留和 Windows lane 覆盖都保留。

负例

  • tests/extensions/test_process_runtime.py::test_remove_owned_tree_never_clears_a_shared_hard_link_attribute:硬链接 fixture;Windows 上断言 PermissionError、fixture 保留、外部文件保持 0444 与 b"blob";POSIX 上断言删除成功且外部不变。
  • tests/canary/test_smoke_fixture_cleanup.py::test_a_shared_hard_link_is_not_rewritten_outside_the_fixture:走真实 run_canary_smoke_check,子进程在 fixture 内 os.link 到外部只读对象;Windows 上命令 exit 0,但结果为 ok: false、原因 fixture cleanup failed; retained …,外部对象保持 0444 和原内容。

原生 Windows 对照(本机 Windows 11 / Python 3.14):修复前 remove_owned_tree 返回成功,源对象 0444 → 0666;修复后抛 PermissionError,源对象保持 0444,fixture 保留。

验证

  • 三个套件 13 passed / 27 skipped(新增两个 hard-link 用例;junction 用例执行,directory-symlink 用例因本机无权限 skip);ruff check 通过;python -m mypy --platform linux 17 files 通过。
  • 本 head 的 scoped premerge(9-path diff):6 direct checks 0 failures、risk_profile 8 executed / 0 failures、boundary 1 executed / 0 failures;catalog 8/10,两处为既有环境问题(cli-output-budget-regression-smoke 的 WinError 32、issue-fix-json-input-boundary-smoke 的 GBK decode),均与本 diff 无关。
  • CI 在上一 head 5c7f2d9c1 上 kernel-static-checks(含 mypy)与 windows-powershell 已通过;本 head 已重新入队。

权衡(已写入 PR body):包含只读硬链接对象的 fixture 现在会 fail-closed(报告失败并保留现场),这是“外部文件属性与内容不变”要求下的边界。现有 issue-fix repository fixture 用 git init 创建自有对象(link count 1),正常通过路径不变。

PR body 已同步更新(criteria、review 记录、Validation 与限制)。请复评新 head。

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max

动机

在 Windows 上运行 canary 或 issue-fix 校验的开发者,需要成功命令得到准确结果,同时清理只影响自己的临时目录。

旧路径遇到 Git 的只读对象会清理失败;上一版修复又会把普通本地克隆共享的源对象改为可写,尽管 runner 报告成功。

当前补丁保留普通只读成员清理,并在修改属性前跳过共享硬链接;真实 runner 对照中,外部源对象保持原字节和只读权限。

本次没有独立认证原生 Windows、NTFS junction、持续运行或整机测试健康,也不涉及生产目录清理、安装升级或合并。

改动思路

这轮修复成立。文件硬链接是同一文件的另一个目录项;检查它不是 symlink,还不能证明临时目录独占该文件。st_nlink > 1 时不清除共享属性,保留 rmtree 与 caller 的失败报告。Windows 无法删除的只读共享链接因此保留现场;POSIX 可以直接删除自己的目录项,源文件也不变。

平台文件属性规则统一放在已有 process_runtime,调用者继续拥有进程退休、重试和检查结果,不新增资源管理框架。

当前 PR 边界是安全清理自建 fixture 的普通只读成员,并拒绝会改动外部共享文件的属性处理;共享只读对象在 Windows 上允许明确失败并保留现场。

这是必要的安全权衡:做不到安全删除时应报告失败,不能为了把检查标绿而修改其他仓库。直接忽略 cleanup error 会隐藏泄漏;只在一个 smoke 内修复会留下 issue-fix 等相同入口。共用删除规则、保留 caller 各自重试已经是较小方案。这里的比较表只是源码解释,执行证据见下文。

输入 旧问题或上一版 当前边界
自有普通只读对象 Windows unlink 失败 单链接文件可先清属性再删除
根目录 symlink/junction 曾先改外部目标再拒绝 在 walk/chmod 前拒绝
本地 clone 的共享硬链接 上一版连同源仓库一起 chmod 不改共享属性;不能安全删除时失败并留存
普通 POSIX 成功/命令失败 原命令结果与清理分别处理 保留原 returncode,自己的 namespace 正常删除

具体改动

完整 head 803c0e242fce52aeaf0e97f087a7c2f39a1e404a,实际 base f9bdae67596c185cebc81dc555dd6a3e0242e9c5,9 个路径、+450/-10;本轮同时审查完整 PR 与上一评审 5c7f2d9c13db3d7b5a10d84988cd9bb87b60340a 到当前 head 的增量,没有继承旧结论。

独立依据先于 diff 阅读:原问题 #6710 的 Expected behavior,以及改动前 docs/development/testing-and-quality.md 在上述完整 base 的 Smoke cleanup:自有 namespace、禁止跟随链接、无法确认清理时保留 fixture 和原命令输出;不认证原生间歇故障或持续运行。原问题的普通只读删除路径保留;共享文件的无外部修改由本轮反例验证,不能用新 docstring 自证正确。

关键符号与所有改动面:

  1. remove_owned_tree(loopx/extensions/process_runtime.py:311)在任何属性处理前检查根链接,Windows 才进入 _clear_windows_read_only_files:274,最后仍调用真实 shutil.rmtree。_is_link_or_junction:247 兼容 Python 3.11:动态读取目录 reparse 属性,避免依赖 3.12 新 API。
  2. 属性遍历在 process_runtime.py:297 读取真实 stat,跳过 st_nlink > 1 的文件;目录链接会先从 walk 中剪掉。它不扩展文件所有权,也没有复制或改写外部源对象。
  3. _remove_fixture_root(loopx/canary/runner.py:290)先退休该 fixture 的 runtime,再删除目录。_run_check 保留失败原因、命令输出和原 returncode;不能确认删除时不会二次强删。
  4. issue_fix/acceptance_loop.py::_remove_temporary_git_workspace 和两个现有 reviewer smoke 改用同一 helper,保留原来的五次/十次有界重试。没有把各 caller 的业务结果合并成新的状态 owner。
  5. 三个回归文件覆盖普通只读成员、无法确认清理、根链接/junction 和共享硬链接;.github/workflows/python-tests.yml 的 Windows PowerShell lane 实际列入这三套测试。工作流阅读不代表这次独立执行过 Windows,也没有查询 CI。

独立控制实验采用同一合成输入,走真实 run_canary_smoke_check、Git 子进程、walk/stat/chmod/rmtree 和结果读回:普通 git clone --local --no-checkout 使 fixture 与外部源对象共享 inode,清理前 link count=2。base、上一版和当前版都完成 POSIX 删除;只有上一版把外部三个只读对象从0444改为0644,当前版保持0444、内容及 inode 不变。另测根链接:当前版拒绝且目标保持0444。宿主是 macOS,只有 helper 平台分支选择为 Windows;不是原生 Windows 证明。 不能据此声称 Windows 上共享对象一定已经删除;预期恰好是保持属性、必要时失败。

另有 base/上一版/head 三组真实 POSIX 对照,每组包含普通成员成功、只读成员成功、只读成员命令 exit7;九条路径都正确清理并保留命令结果。原先根链接 finding 没有重复保留,共享硬链接 finding 也已由本轮相同输入反证修复。

对主干的风险

没有发现当前范围内的阻断项。最主要的剩余限制是 Windows 的真实文件属性、junction 和长期运行仍未由我独立执行;作者提供的原生 Windows 报告保留为作者证据,不能当作本轮执行。当前 source guard、真实共享 inode 反例、原有进程/结果处理与平台 lane 接线共同支持这个有界修复。

独立本地验证:116 passed、1 skipped、12 subtests;skip 是原生 Windows junction。Ruff 覆盖所有改动 Python,advisory 后完整 semantic、公开边界扫描、五个提交的 DCO 与 diff hygiene 通过。标准 premerge 的6个直接检查及19个选中检查全部通过,包含真实公共 canary/issue-fix 入口与 pytest facade;未查询、派发或等待 CI。公开扫描有两个隔离目录中缺本地 registry 的警告,0 error,不能称零警告。起初一个测试命令写错不存在的路径,未运行测试;使用实际文件后才得到上述完整结果。

删除规则在 Windows 自动生效,属于修复既有平台行为,不是新可选策略;原 POSIX 分支保持。只读共享文件留存是公开说明的失败边界,而不是“成功清理”的无条件保证。不要据这次通过结论扩大到其他 shutil.rmtree 入口或解除进程占用问题,它们有不同 owner。

我的整体评价

APPROVE — 当前准确 head 的安全修复和全部9路径已重新核验。原来的根链接和硬链接问题都不再构成本 head 的 blocker;Windows 平台 qualification 和全机长期健康仍单独保留。Smoke cleanup 与 Expected behavior 的本次适用要求满足,未把源码通过升级成完整平台稳定性。

相邻的未来改动检查已应用:统一四个调用点的文件属性规则,保留进程退休、重试及失败读回各自 owner;当前没有需要新增抽象、状态机或 TS 重写的同域修复。普通 CLI 校验依旧直接运行并读回,已有意图与授权下零新增确认轮次;错误保持可见。没有新前端操作或 Lark 配置需要补界面,也没有增加持久化状态、授权、额度或调度规则。此通过结论不授予 merge、dismissal 或生产清理权限。

English verdict: APPROVE - 803c0e2. The shared-hard-link mutation is fixed: a real Git/canary comparison preserves external object bytes and0444 modes at this head, while the previous head changed them.116 tests and6 direct/19 selected checks pass; native Windows/junction qualification remains independently unverified, and unsafe shared read-only cleanup may fail closed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants