Skip to content

fix(oknte): 配置还原不再停在半删状态 - #617

Open
qiyinxi wants to merge 1 commit into
AUTO-MAS-Project:devfrom
qiyinxi:fix/sentry-oknte-20260908
Open

fix(oknte): 配置还原不再停在半删状态#617
qiyinxi wants to merge 1 commit into
AUTO-MAS-Project:devfrom
qiyinxi:fix/sentry-oknte-20260908

Conversation

@qiyinxi

@qiyinxi qiyinxi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #535

问题

PermissionError: [WinError 5] ... 'working/configs.tmp' -> 'working/configs'(Sentry AUTO-MAS-BACKEND-4H)。

OK-NTE 里有 4 处完全相同的序列:

shutil.rmtree(tmp_dst, ignore_errors=True)
shutil.copytree(src, tmp_dst, dirs_exist_ok=True)
shutil.rmtree(dst, ignore_errors=True)   # ← 失败被静默吞掉
tmp_dst.rename(dst)                      # ← dst 还在,Windows 上必然 WinError 5

ok-nte 进程若仍持有 configs 下任一文件的句柄,第三行会删掉能删的、留下被占用的,然后不报错返回dst 因此仍然存在,第四行往一个已存在的目录 rename,在 Windows 上必然失败。

结果是用户的配置目录停在半删状态,备份留在 .tmp 里没有还原回去——属于会丢用户数据的路径。

改动

新增 app/task/OkNte/tools/config_swap.py,用 replace_config_dir(src, dst) 统一这 4 处:

  • 删除 dst短重试(默认 5 次 × 0.5s),等 ok-nte 刚被结束后 Windows 释放句柄——manager.final_task 里的还原紧跟在子任务 kill 之后,这个竞态是主因;
  • 实在删不掉时不再 rename,改为把备份 copytree(dirs_exist_ok=True) 就地覆盖回 dst,把前面 rmtree 已经删掉的部分补回来。宁可留下几个多余文件,也不让配置停在半删状态;
  • 不再用 ignore_errors=True 掩盖删除失败,删不掉会写一条 warning 说明原因。

替换的 4 处:manager.py _restore_script_config_from_tempAutoProxy.py set_oknteScriptConfig.py 载入 GUI 配置与 final_task

#456 的关系

#456 开着,改的是同一片代码,但解的是另一个根因:它用 force_rmtree 清只读属性,并把还原包进 try/except。只读属性和句柄占用是两回事——force_rmtree 解不了被打开的文件,占用时 rmtree 照样半删、rename 照样 WinError 5(#456 的 try/except 会让它不再中断任务,但配置仍停在半删状态)。

两者可以叠加,合并时会有冲突。建议先合本 PR:它把这 4 处收敛成一个函数,#456force_rmtree 之后只要改 config_swap.py 一处即可。如果你们更愿意先合 #456,我来跟着改。

本地验证

.venv/Scripts/python.exe -m pytest tests -q
682 passed, 3 skipped, 151 subtests passed in 8.87s

.venv/Scripts/python.exe -m ruff check app/task/OkNte    # All checks passed

另外在本地写了三条针对 replace_config_dir 的测试并跑过(按 tests/AGENTS.md,bug 边界测试不提交):

用例 结果
正常替换:内容换新、任务期新增的多余文件被清掉、.tmp 不残留 通过
真造一个被打开的文件句柄:断言 dst 没有半删、备份内容确实还原回去、.tmp 不残留 通过
dst 本就不存在 通过

3 passed in 8.97s。第二条是这个 bug 的核心场景,在 Windows 上用 open(file, "rb") 持有句柄复现的。

没有做真机手测:需要装 ok-nte 并让它在还原时刚好没退干净,本地没有环境。

请审阅

@Jungle178 OK-NTE 专项是你写的,请你看看「删不掉就就地覆盖」这个取舍是否可接受——它的代价是多余文件可能残留一轮。确认后请你自行合并,我不代合。

Sourcery 摘要

使 OK-NTE 配置恢复能够抵御 Windows 文件句柄竞争问题,并避免目录处于部分删除状态。

错误修复:

  • 防止在文件仍处于打开状态时,OK-NTE 配置目录在 Windows 上保持部分删除状态或恢复失败。
  • 当目标目录无法被完全移除时,就地恢复配置内容,以便在句柄释放竞争期间保留用户数据。

增强功能:

  • 将四种 OK-NTE 配置目录替换路径集中到一个共享辅助函数中,并通过短暂的删除重试和移除失败警告来处理删除操作。
Original summary in English

Summary by Sourcery

Make OK-NTE configuration restoration resilient to Windows file-handle races and avoid leaving directories in a partially deleted state.

Bug Fixes:

  • Prevent OK-NTE configuration directories from remaining partially deleted or failing to restore on Windows when files are still held open.
  • Restore configuration contents in place when the destination cannot be fully removed, preserving user data during handle-release races.

Enhancements:

  • Centralize the four OK-NTE configuration directory replacement paths in a shared helper with short deletion retries and warnings for removal failures.

_restore_script_config_from_temp 等四处都是同一序列:
rmtree(dst, ignore_errors=True) 之后 tmp.rename(dst)。ok-nte 进程若仍持有
configs 下任一文件的句柄,rmtree 会删掉能删的、留下被占用的,然后不报错返回;
dst 因此仍然存在,下一行往一个已存在的目录 rename 在 Windows 上必然 WinError 5。
结果是用户配置目录半删、备份留在 .tmp 里没还原回去。

新增 tools/config_swap.replace_config_dir 统一这四处:
- 删除 dst 带短重试,等 ok-nte 刚被结束后句柄释放
- 删不干净时不再 rename,改为把备份就地覆盖回 dst,把 rmtree 已删掉的部分补回来
- 不再用 ignore_errors=True 掩盖删除失败

Closes AUTO-MAS-Project#535

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@qiyinxi qiyinxi added bug Something isn't working Sentry daily Sentry daily issues labels Sep 8, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @qiyinxi, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 4 days and 3 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@qiyinxi
qiyinxi requested a review from Jungle178 September 8, 2026 08:32
@sourcery-ai

sourcery-ai Bot commented Sep 8, 2026

Copy link
Copy Markdown

审查者指南

新增统一的异步配置目录替换工具,通过删除重试和删除失败时就地覆盖,解决 Windows 句柄占用造成的配置目录半删问题,并将 OK-NTE 中四处重复实现迁移到该工具。

具备容错能力的 OK-NTE 配置替换时序图

sequenceDiagram
    participant Caller as OKNTE_Task
    participant Swap as replace_config_dir
    participant FS as Windows_FileSystem
    participant Process as OKNTE_Process

    Caller->>Swap: replace_config_dir(src, dst)
    Swap->>FS: copytree(src, dst.tmp, dirs_exist_ok=True)
    loop up to retries
        Swap->>FS: rmtree(dst)
        alt destination removed
            FS-->>Swap: success
        else destination locked
            FS-->>Swap: OSError
            Swap->>Swap: asyncio.sleep(delay)
        end
    end
    alt dst removed
        Swap->>FS: rename(dst.tmp, dst)
    else dst remains locked
        Swap->>FS: copytree(dst.tmp, dst, dirs_exist_ok=True)
        Swap->>FS: rmtree(dst.tmp)
    end
    Swap-->>Caller: replacement complete
Loading

文件级变更

变更 详细信息 文件
集中实现配置目录替换的竞态恢复逻辑,避免 Windows 文件句柄占用导致目录半删除和备份无法还原。
  • 先复制到同级临时目录,再以有限次数和间隔重试删除目标目录。
  • 删除仍失败时改用就地覆盖,并记录 warning;成功删除后通过重命名完成整体替换。
  • 保留临时目录清理,并支持目标目录不存在的情况。
app/task/OkNte/tools/config_swap.py
将 OK-NTE 的四个目录还原或同步路径统一改用新的替换函数。
  • 替换调度器恢复脚本配置、代理启动时同步配置、GUI 配置载入和 final_task 保存配置中的重复实现。
  • 通过异步调用共享逻辑,覆盖配置目录模式下的所有相关路径。
app/task/OkNte/manager.py
app/task/OkNte/AutoProxy.py
app/task/OkNte/ScriptConfig.py
更新项目版本元数据。
  • 调整版本 JSON 内容以反映本次修复。
res/version.json

可能关联的问题


提示和命令

与 Sourcery 交互

  • 触发新的审查: 在拉取请求中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 根据审查评论生成 GitHub issue: 回复审查评论,请 Sourcery 根据该评论创建 issue。你也可以在审查评论中回复 @sourcery-ai issue,根据该评论创建 issue。
  • 生成拉取请求标题: 在拉取请求标题中的任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在拉取请求中评论 @sourcery-ai title,随时重新生成标题。
  • 生成拉取请求摘要: 在拉取请求正文中的任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以在拉取请求中评论 @sourcery-ai summary,随时重新生成摘要。
  • 生成审查者指南: 在拉取请求中评论 @sourcery-ai guide,即可随时重新生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,这项功能会很有用。
  • 驳回所有 Sourcery 审查: 在拉取请求中评论 @sourcery-ai dismiss,即可驳回所有现有的 Sourcery 审查。如果你想从头开始新的审查,这项功能尤其有用——别忘了评论 @sourcery-ai review 来触发新的审查!

自定义你的使用体验

访问你的控制面板以:

  • 启用或禁用审查功能,例如 Sourcery 生成的拉取请求摘要、审查者指南等。
  • 更改审查语言。
  • 添加、删除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

新增统一的异步配置目录替换工具,通过删除重试和删除失败时就地覆盖,解决 Windows 句柄占用造成的配置目录半删问题,并将 OK-NTE 中四处重复实现迁移到该工具。

Sequence diagram for resilient OK-NTE configuration replacement

sequenceDiagram
    participant Caller as OKNTE_Task
    participant Swap as replace_config_dir
    participant FS as Windows_FileSystem
    participant Process as OKNTE_Process

    Caller->>Swap: replace_config_dir(src, dst)
    Swap->>FS: copytree(src, dst.tmp, dirs_exist_ok=True)
    loop up to retries
        Swap->>FS: rmtree(dst)
        alt destination removed
            FS-->>Swap: success
        else destination locked
            FS-->>Swap: OSError
            Swap->>Swap: asyncio.sleep(delay)
        end
    end
    alt dst removed
        Swap->>FS: rename(dst.tmp, dst)
    else dst remains locked
        Swap->>FS: copytree(dst.tmp, dst, dirs_exist_ok=True)
        Swap->>FS: rmtree(dst.tmp)
    end
    Swap-->>Caller: replacement complete
Loading

File-Level Changes

Change Details Files
集中实现配置目录替换的竞态恢复逻辑,避免 Windows 文件句柄占用导致目录半删除和备份无法还原。
  • 先复制到同级临时目录,再以有限次数和间隔重试删除目标目录。
  • 删除仍失败时改用就地覆盖,并记录 warning;成功删除后通过重命名完成整体替换。
  • 保留临时目录清理,并支持目标目录不存在的情况。
app/task/OkNte/tools/config_swap.py
将 OK-NTE 的四个目录还原或同步路径统一改用新的替换函数。
  • 替换调度器恢复脚本配置、代理启动时同步配置、GUI 配置载入和 final_task 保存配置中的重复实现。
  • 以异步调用共享逻辑,覆盖配置目录模式下的所有相关路径。
app/task/OkNte/manager.py
app/task/OkNte/AutoProxy.py
app/task/OkNte/ScriptConfig.py
更新项目版本元数据。
  • 调整版本 JSON 内容以反映本次修复。
res/version.json

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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

Labels

bug Something isn't working Sentry daily Sentry daily issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant