修复 AGC 最近项目「检查失败」被钉死:失败退避重试 + 按轮重查(#490) #492
Reference in New Issue
Block a user
Delete Branch "fix/agc-recent-project-status-retry"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
关联 issue:#490
问题
AGC 卡住一次之后,项目列表所有行变成「检查失败 + 待识别」,首页「最近项目」变成「暂无最近项目」,后端恢复也不会自愈,只能重启客户端。
根因
前端把一次性的目录检查失败当成了终态,且没有任何重试入口:
useRecentProjects.ts每个最近项目的检查只有 5s 预算,失败被catch吞成null。next[workspace] = current[workspace] ?? null把上一轮的null原样搬进新一轮。model.ts把null渲染成「检查失败」,projectKind落成unknown→ 「待识别」。canOpen为真的行,而canOpen依赖Boolean(directoryStatus)→ 同一批null让首页变空态。[recentWorkspaces, recentWorkspaceRefreshKey],而refreshKey只有「打开/新建项目」才会 +1;没有重试、没有 focus 重查。⇒ 一次 5s 量级的抖动就能把整个会话的列表与首页钉死。
现场证据(2026-09-23 本机 dev 客户端):11:24:12 两个平台接口 15s 超时、11:24:26 关闭项目回启动页后 Rust 日志静默 32 分钟;11:56 在同一进程里逐条复跑
inspect_local_project_directory(8 个项目)全部 OK、0ms,而界面仍是全「检查失败」。改动前后对照
null原样搬过去invoke类型NonNullable<ReturnType<typeof resolveTauriInvoke>>app/types.ts既有TauriInvoke,不再新增同名类型不改的东西:5s 单次超时口径、Rust 侧命令、行状态文案、公开契约与持久化格式。
验证证据
npx vitest run apps/ai-game-creator-shell/tests/recentProjectsHook.test.tsx:4 passed(新增 3 个用例)git stash对照:1 failed)。npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts apps/admin-web/src/pages/AdminAgcTemplatesPage.test.tsx apps/ai-game-creator-shell/tests/recentProjectsModel.test.ts:合计 233 passed / 9 skipped(home.suite里「检查失败」断言改为等待最终状态,因为失败状态现在要等重试结束才落成)。npm run agc:typecheck、npm run check:encoding、git diff --check、eslint、prettier --check全部通过。decision-log.md(新增 2026-09-23 条目 + 修正 09-20 条目措辞)、pitfalls.md(新增条目)、【技术方案】AGC客户端稳定版生命周期大切换-2026-09-14.md、【技术方案】AGC异步操作可恢复闭环-2026-09-14.md(行为契约 + 验收矩阵)。4 片评审发现与处置
本 PR 用 4 个只读 agent 分片评审(合并完整性 / 主逻辑 / 测试与文档 / 资源与安全),结论与处置:
attempted_targets是每次 invoke 新建的局部 Vec(config.rs:2330-2352),Start-Process -Verb RunAs -Wait无超时(config.rs:2824),Managed 作用域无需 file-picker 授权(config.rs:910-1041)→ 用户点「否」(exit 1223)正好命中重试条件。最坏4 轮 × 8 项目 × 2 次 = 64次 invoke(改前 8 次)config.rs同口径判据区分不可重试失败并跳过refreshRecentWorkspace不重试、不排重查,该行仍会被钉成终态TauriInvoke与app/types.ts:1052既有同名类型重复,注释却写「唯一入口类型」chat/generated/*与 master 一致、3 个 Rust 测试文件逐字节等于 master、platform-agent/Cargo.lock未入库未做(记录待评):「Rust 侧 per-target 提权冷却」是更根本的闸门(进程级记忆,避免任何调用方重复触发提权),但要改 ACL/提权行为,需单独评审 + 真机验证,不塞进本 PR。
CI 过程与当前状态
04b738755):7 个 job 成功;Backend tests、Repository checks因 base-commit gate(pull request head does not contain the latest base commit)失败——是分支基线落后 master,与本 PR 改动无关。AI game creator shell Rust lane 1/2、lane 2/2变红。核对确认是 master 自身的问题(run 2806 / 2809 同样只有这两条红),首个红 commit =f789db7f2(#491 移除提示词里的runId/instanceId/sessionId时漏同步 6 处断言)。25d331ca0):两条 Rust lane 转绿;同一 base-commit gate 再次把Repository checks/Backend tests判红。6b4c6b8a6) 修了同一批断言(把contains(run_id)换成「请求包含该 run 的任务文本」)→ 冲突按 master 版本解决,25d331ca0的内容由合并结果覆盖,不重复引入。016356e5、f42694470、e8d987c20、5bc5328c0…),每次推送前都重新合入,当前 head 已包含最新 master。b48953f87)。未覆盖 / 风险
command_exec::tests::project_command_runs_project_build_and_git_read_inside_sandbox失败(Linux 沙箱进程组回收,间歇性);本地 Windows 上response_stream::provider_retry_waiting_final_reply_compaction_resumes_without_new_tool_plan(response_stream.rs:3419-3421的 100ms 负向窗口断言,2026-07-21 引入)稳定失败、Linux CI 通过。审查结论:未发现阻断合并的问题,通过审查,建议合并。
审查范围:当前 PR 提交
a1dd382489相对 master 的全部 6 个变更文件。非阻断建议(P3):apps/ai-game-creator-shell/src/features/app-shell/useRecentProjects.ts 第 120–122 行的 300ms 退避不会随旧检查轮次失效而取消。首次检查失败后,若用户在退避期间移除项目、触发新轮次或组件卸载,旧循环仍会再发出一次目录检查。现有 disposed/generation 保护会丢弃结果,不会恢复已删除项目或污染列表;影响仅为额外 IPC 和磁盘访问。建议后续在重试前检查轮次是否仍有效,此项不阻断合并。
实际验证:
验证边界:未运行全量测试或真实 Tauri 联调。测试输出有 DOM 嵌套、act 和 mock 警告,未导致失败。15/45/120 秒自动重查链路缺少直接测试覆盖,建议后续补充。
评审报告(4 片只读 agent + 本地复现)
评审对象:head
7157a90d3(人工 review 的那版),已在b48953f87上完成收口。评审切分:合并完整性 / 主逻辑 / 测试与文档 / 资源与安全。高(本 PR 自己的重试设计引入,值得单独看一眼)
提权被拒 → 自动重试 → 自己驱动 UAC
catch吞掉错误文本,300ms 后再发一次(useRecentProjects.ts:70-71,113-128)。attempted_targets是每次调用新建的局部 Vec(config.rs:2330-2352);Start-Process -Verb RunAs -Wait无超时(config.rs:2824)。.agent/manifest.json祖先 / AGC AppData)无需 file-picker 授权、无 TTL(config.rs:910-1041)→ 纯首页列表检查即可弹 UAC;用户点「否」(exit 1223)恰好命中重试条件。4 轮 × 8 项目 × 2 次 = 64次 invoke(改前 8 次)。处置:按
config.rs的windows_acl_error_may_need_elevation同口径识别不可重试失败(DACL / 权限 / error 5 / 安全对象不属于当前用户 / 特权 / 1300 / AGC ACL 提权修复未成功),不重试、不驱动整表重查,并在用户主动打开/新建项目或重命名刷新前跳过该项目。未做:「Rust 侧 per-target 提权冷却」(进程级记忆)是更根本的闸门,会动 ACL/提权行为,建议单独评审+真机验证后再做。另:是否给这类项目一个「需要提权」状态词与一键重试入口,也值得单独决策。
中 / 低(均已收口)
refreshRecentWorkspace不走重试也不排重查 → 该行仍会被钉成终态(唯一调用点ProjectCreation.tsx:237-239)failureStreakRef只在整轮零失败时清零)AGENTS.md明确要求)decision-log(新条目 + 修正 09-20 条目措辞)、pitfalls,以及两份技术方案的行为契约与验收矩阵TauriInvoke与app/types.ts:1052既有同名类型重复,注释还写「唯一入口类型」(两处签名可互相赋值,编译器不会报错)chat/generated/*与 master 一致、3 个 Rust 测试文件逐字节等于 master、platform-agent/Cargo.lock未入库验证
git stash对照:提权用例1 failed);改后recentProjectsHook4 +appSurface217(9 skipped)+admin-web20 +recentProjectsModel1 = 233 passed。npm run agc:typecheck、npm run check:encoding、git diff --check、eslint、prettier --check全绿。结论
主修复方向评审无异议(主逻辑切片结论亦为「方向正确」);上面 6 条是收口问题,已在
b48953f87落地。可以 pass,建议带上收口提交(特别是「高」那条,否则会把「被拒提权自动重试」带进 master)。停顿层根因仍未定位,继续在 issue #490 用采证探针跟踪。