From ea10dbae53a627b8213b9bdc8bde5f42d47a01dd Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Mon, 12 Oct 2026 19:17:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=20gitlint=20=E9=97=A8=E7=A6=81?= =?UTF-8?q?=E5=9C=A8=20CI=20=E7=A9=BA=E8=BD=AC=EF=BC=9Arunner=20=E6=97=A0?= =?UTF-8?q?=20git=20=E8=BA=AB=E4=BB=BD=E8=87=B4=20254=20=E8=A2=AB=E8=AF=AF?= =?UTF-8?q?=E6=8A=A5=E6=88=90=E4=BF=A1=E6=81=AF=E8=BF=9D=E8=A7=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task 31(首次真跑出的 L0)失败与提交信息无关:actions/checkout 把 HOME 指向每次任务的临时目录,SYSTEM 账号没有全局 git 身份,gitlint 在读到任何 规则之前就以 Missing git configuration: please set user.name 退出——254 是配置错误,不是规则违规。旧脚本还把失败原因写死成"标题超 100 字符",加 上该步 exit 1,后面 26 个钩子一个都没跑。 改三处:① 先探测身份,缺失则用 GIT_CONFIG_COUNT/KEY/VALUE 注入一次性身 份,只进子进程环境、不写任何文件,开发者机器不受影响;② 254 与真实违规 分开报(2 = 门禁没跑起来,1 = 信息确实违规),不再替 gitlint 猜规则; ③ 钩子输出改走 ForEach-Object { "$_" },避免 PS 5.1 把原生 stderr 包成 ErrorRecord 污染日志。 实测:空 HOME 下同一条命令改前 254、改后 0 failed;正常环境 10 条提交全 过;脚本纯 ASCII、解析 0 错。同批文档:§7 排障表新增本坑与"新建分支首 次 push 不触发 run",两份变更记录各加一行。 --- docs/CI/README.md | 1 + docs/CI/远程CI完整指南.md | 4 ++- scripts/ci_gitlint_range.ps1 | 65 ++++++++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/docs/CI/README.md b/docs/CI/README.md index 12740906..aed237e6 100644 --- a/docs/CI/README.md +++ b/docs/CI/README.md @@ -81,6 +81,7 @@ | 日期 | 变更 | |---|---| +| 2026-10-12 | 🐛 **首次真跑出 run**:`L0.5` **成功**(`10 runner(s), 647/647, fail=0`,与本地逐字一致);`L0` **假失败**——gitlint 因 runner 无 git 身份而 `exit 254`(`Missing git configuration: please set user.name`),旧文案把它说成"提交信息违规",且该步 `exit 1` 连带让 26 个钩子全部没跑。已在 `ci_gitlint_range.ps1` 用 `GIT_CONFIG_*` 注入一次性身份(不写文件)并区分 254/1 退出码。另记:**新建分支的首次 push 不触发 run**,需 `workflow_dispatch` 或再推一个非文档提交。详见指南 §7 | | 2026-10-12 | 🔀 本轮 CI 修复随 `master` **合入 `v2-on-ui`**(该分支另有 21 个功能提交;冲突仅 `AGENTS.md` 与 `docs/README.md` 两处,解法是"保留其新版结构 + 把 CI 指针搬进对应位置")。因该分支门禁更多(**26 钩子 / 10 单测套件** vs master 的 18 / 3),指南里写死的数字已改为**规则 + 分分支快照**,见指南 §6 / §7 / §10 | | 2026-10-12 | ✅ 双看门狗缺陷**实测收敛**(管理员 `-StartNow` 清掉 3 个残留 → 单实例、链路恢复、runner online)。另修两处:daemon 日志走 stderr 被 PS 5.1 `2>&1` 包成 ErrorRecord(2 行真日志写成 8 行,每 4 行 3 行噪声)→ 启动器改 `ForEach-Object { "$_" }`;`-Status` 诚实化(非管理员读不到 SYSTEM 进程命令行 → 明说需提权而非谎报"无看门狗";重启循环判据改看进程存活时长,不再靠不轮转日志的尾部字符串报假警)。详见指南 §7.3 | | 2026-10-12 | 🔴 **修复"双看门狗抢 `.runner.lock`"缺陷**:`Unregister-ScheduledTask` 不停旧实例进程 → 孤儿看门狗养着旧 daemon 占锁,新看门狗每 10 秒失败一次,而 runner 网页照样 online、`-Status` 也说"在跑"。现 `-StartNow` 先清残留进程再启动、`-Status` 报重复实例与锁冲突。同日清理机器侧:`config.yaml` 头部错误指引("改用 sc.exe / New-Service")改为看门狗口径,删除已废弃的 `deploy_runner_task.ps1` 与 `run_runner.cmd`,`check_env.ps1` 提示同步改指 bootstrap | diff --git a/docs/CI/远程CI完整指南.md b/docs/CI/远程CI完整指南.md index 2270fb06..5ee784bb 100644 --- a/docs/CI/远程CI完整指南.md +++ b/docs/CI/远程CI完整指南.md @@ -1059,7 +1059,8 @@ date +%s # 对比 last_online | `l0-gates` 报 `no Python with a SYSTEM-visible pre-commit` | runner 是 SYSTEM:PATH 上的 python 无 pre-commit、用户级 site 不可见、`.venv` 未入库(§4.1 三重坑) | `ci_runner_bootstrap.ps1 -SetEnv` 写机器级 `DTU_PRECOMMIT_PYTHON`,或把 pre-commit 装进机器级解释器;**改完必须重启 runner** | | `l0-gates` 报 `runner missing cppcheck` | 机器 PATH 无 Cppcheck 且无机器级 `DTU_CPPCHECK` | `-SetEnv`(会自动发现 `C:\Program Files\Cppcheck`)或装 `Cppcheck.Cppcheck` | | **钩子条数对不上**(CI 报告条数 = 定义数 − 1;2026-10-12 实测 `master` 18 定义 / 17 报告,`v2-on-ui` 26 定义 / 25 报告——**增删钩子的分支必须重算这个差值**) | `gitlint` 的 `stages: [commit-msg]` 写在**上游** `.pre-commit-hooks.yaml`(项目 `.pre-commit-config.yaml` 里 grep 不到,别据此断定"没限制"),`pre-commit run --all-files` **跑不到它**,且表现为全绿——静默空转 | 已由 `scripts/ci_gitlint_range.ps1` 单独校验;**以后增删钩子必须核对"定义数 == 日志条数 + gitlint"** | -| push 后 Actions 页**空白/无任务** | ① 服务端开关未开 ② workflow 没 push 到远程 ③ 只改了 `docs/**`(被 `paths-ignore` 滤掉) | 逐条核对 §1 / §5.1 / §5.2 | +| gitlint 步骤 **Failed 但提交信息其实合规**(同一条命令在本地全绿),日志里是 `exit code: 254` + `Missing git configuration: please set user.name` | `actions/checkout` 把 HOME 临时指向每次任务的临时目录,而 SYSTEM 账号没有全局 git 身份 → gitlint **在读到任何规则之前**就因配置错误退出(**254 不是规则违规**)。旧脚本末尾那句 `::error::...rule: single-line Chinese title, < 100 chars` 是**写死的猜测文案**,把环境问题伪装成"提交信息不合格";且该步 `exit 1` 会让后面的 `--all-files` **整个不跑**——看着像"门禁拦住了",实际 26 个钩子一个都没执行 | `ci_gitlint_range.ps1` 先探测身份,缺失就用 `GIT_CONFIG_COUNT/KEY/VALUE` 注入一次性身份(**不写任何文件**,开发者机器不受影响),并把 254 与真实违规分开报(**2 = 门禁没跑起来**,1 = 信息确实违规)。实测同一命令:空 HOME 下改前 254、改后 `0 failed`(2026-10-12 task 31 首次暴露) | +| push 后 Actions 页**空白/无任务** | ① 服务端开关未开 ② workflow 没 push 到远程 ③ 只改了 `docs/**`(被 `paths-ignore` 滤掉) ④ **新建分支的首次 push**(实测 2026-10-12:创建 `v2-on-ui-ci` 那次 push 没产生 run——runner 在线空闲、job 日志零新增;紧接着一次普通 push 立刻出 task。原因:`before` 是全零 SHA,变更文件集算不出来,`paths-ignore` 求值时被滤空) | ①②③ 逐条核对 §1 / §5.1 / §5.2;④ 用 `workflow_dispatch` 手动触发,或再推一个非文档路径的提交 | | 任务一直 **Waiting / 排队** | 没有 label 匹配 `windows` 的 runner | 检查 `--labels "windows:host"`;Settings→Runners 是否 Idle | | `l0-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) | | `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §5.3 | @@ -1350,6 +1351,7 @@ powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -DryRun | 日期 | 变更 | |---|---| +| 2026-10-12 | 🐛 **第一次真跑出 run,当场暴露 gitlint 门禁空转**:把本轮修复推到新分支 `v2-on-ui-ci` 后拿到 task 31/32——**L0.5 成功**(`10 runner(s), 647/647 assertions passed, fail=0`,与本地逐字一致),**L0 失败**却与提交信息毫无关系:`exit code: 254` + `Missing git configuration: please set user.name`。根因是 `actions/checkout` 把 HOME 指向每次任务的临时目录、SYSTEM 账号没有 git 身份,gitlint 在读到规则之前就配置错误退出;旧脚本还把失败原因**写死**成"标题超 100 字符",加上该步 `exit 1` 让后面 26 个钩子**一个都没跑**。修法:脚本先探测身份,缺失就用 `GIT_CONFIG_COUNT/KEY/VALUE` 注入一次性身份(不写文件、不碰开发者机器),并区分 254(门禁没跑起来 → `exit 2`)与真实违规(→ `exit 1`)。另记一条新坑:**新建分支的首次 push 不产生 run**(§7 排障表 ④) | | 2026-10-12 | 🔀 **随 `master` 合入 `v2-on-ui`:数字口径改为分支中立**。本指南原先写死 master 的数字(18 钩子 / 17 报告、3 runner / 288 断言),而 `v2-on-ui` 是 **26 钩子 / 10 套件**——同一篇文档在两个分支上必然有一个是错的。§6 验收第 8 行、§7 排障表、§10 验收标准三处改为讲规则(**报告数 = 定义数 − 1**;**runner 数 = `test/host/` 下 `run_unit_test.py` 个数**)+ 分分支实测快照。本次合并只带文档与脚本,**零 C 代码改动**;合并后本分支 `@build` 0 错 0 警、`ci_local.py` 全绿(L0 25 钩子 + L0.5 10 runner / 647 断言) | | 2026-10-12 | ✅ **双看门狗缺陷实测收敛** + 修掉日志里的 PS 5.1 stderr 包装噪声与 `-Status` 假警。① 管理员 `-StartNow` 杀掉 3 个残留(孤儿看门狗 26900/19716 + 占锁 daemon 53848)→ `daemon up: pid 40360 (single instance)`,监管链 `svchost → 看门狗 → daemon`,看门狗日志 25 秒零增长、runner 回 online。② 新坑:daemon 日志写 **stderr**,PS 5.1 的 `2>&1` 把每行包成 ErrorRecord → `gitea-runner.exe : ` 前缀 + 4 行 `CategoryInfo` 块,实测 2 行真日志写成 8 行(**每 4 行有 3 行是噪声**,且就在文档让大家第一个打开的文件里)。启动器改为 `2>&1 \| ForEach-Object { "$_" } \| Out-File -Encoding utf8`(实测与 cmd 包装等价但免引号剥离坑,`$LASTEXITCODE` 不受影响);§7.2 方式 B 的示例原本正是 `> log 2>&1`(UTF-16LE + 包装双坑),已改;新增 §7.3 汇总这两条。③ `-Status` 两处诚实化:非管理员**读不到 SYSTEM 进程命令行**(`CommandLine` 为 null → 过滤器静默匹配 0 个),改为明说"需提权"而不是谎报"没有看门狗";重启循环判据不再靠"日志尾部有没有错误串"(文件不轮转,修好后旧失败仍在尾部 → 对健康 runner 报假警),改看 **daemon 进程存活时长**(CIM `CreationDate` 非管理员可读,`Process.StartTime` 对 SYSTEM 进程不可读) | | 2026-10-12 | 🔴 **实测揪出"双看门狗抢锁"缺陷并修复** + 机器侧清理。① 现象:runner 网页 online、`-Status` 说 daemon 在跑,但 `runner-watchdog.log` 每 10 秒刷 `starting daemon → daemon exited, exit code 1`,daemon 日志 `runner file is already locked by another process`。② 根因:`Unregister-ScheduledTask` **不停旧实例启动的进程**——13:22 重跑 bootstrap 时旧看门狗(12:53 起)成孤儿并继续养着旧 daemon、占住 `.runner.lock`,新看门狗永远抢不到锁,任务实际谁也没管,日志以约 20 MB/天在刷。③ 修复:`ci_runner_bootstrap.ps1` 的 `-StartNow` 现在**先停孤儿看门狗与残留 daemon 再启动**并回显单实例确认;`-Status` 新增重复实例计数与锁冲突检测。④ 机器侧清理:`config.yaml` 头部那句**错误指引**("常驻改用 Windows sc.exe / New-Service",正是 §3.0 证伪的路)改为看门狗口径并指向本文;删除已废弃的 `deploy_runner_task.ps1` 与 `run_runner.cmd`(§3.2),`check_env.ps1` 指向它们的提示同步改指 bootstrap。⑤ 核实:`config.yaml` 改后 YAML 可解析且**所有配置值与改前逐项一致**(只动注释);`check_env.ps1` 带 BOM 且语法 0 错;删除前确认计划任务动作目标是 `run_runner_watchdog.ps1`,删除不影响在跑 runner | diff --git a/scripts/ci_gitlint_range.ps1 b/scripts/ci_gitlint_range.ps1 index 13a45d6b..9f917784 100644 --- a/scripts/ci_gitlint_range.ps1 +++ b/scripts/ci_gitlint_range.ps1 @@ -6,12 +6,13 @@ .DESCRIPTION Why this file exists (2026-10-11 CI audit): - .pre-commit-config.yaml defines 17 hooks, but CI reported 16 - (passed=15 failed=0 skipped=1). The missing one was `gitlint`: its hook - definition declares `stages: [commit-msg]`, while `pre-commit run - --all-files` only runs the `pre-commit` stage. So the commit-message rule - from AGENTS.md (single-line Chinese title < 100 chars) was enforced on - developer machines and NEVER in CI -- a silent gate gap, not a failure. + CI always reports ONE hook fewer than .pre-commit-config.yaml defines + (measured 2026-10-12: master 18 defined / 17 reported, v2-on-ui 26 / 25). + The missing one is `gitlint`: its hook definition declares + `stages: [commit-msg]`, while `pre-commit run --all-files` only runs the + `pre-commit` stage. So the commit-message rule from AGENTS.md (title + < 100 chars, body lines <= 80) was enforced on developer machines and + NEVER in CI -- a silent gate gap, not a failure. pre-commit cannot check "the commits of this push" on its own, so this script feeds each commit message to the hook through @@ -78,6 +79,35 @@ try { $PythonExe = "$PythonExe".Trim() Write-Host "gitlint: using python $PythonExe" + # --- git identity guard --------------------------------------------- + # Measured on CI 2026-10-12 (task 31): actions/checkout points HOME at + # a per-job temp dir and the SYSTEM account has no global identity, so + # gitlint aborted with "Missing git configuration: please set + # user.name" and exit code 254 BEFORE reading a single rule. Every push + # failed this gate no matter how good the message was, and the log made + # it look like a message violation. + # Inject a throwaway identity through GIT_CONFIG_* (git >= 2.31): it is + # inherited by the child git processes only, so NO file is written and + # a developer machine keeps its own identity untouched. gitlint needs + # an identity merely to start; it does not judge the author. + $savedPref = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + $idName = git config user.name 2>$null + $idMail = git config user.email 2>$null + $ErrorActionPreference = $savedPref + if (-not "$idName".Trim() -or -not "$idMail".Trim()) { + if ($env:GIT_CONFIG_COUNT) { + Write-Host '::warning::gitlint: GIT_CONFIG_* already set by the caller -- leaving it alone' + } else { + $env:GIT_CONFIG_COUNT = '2' + $env:GIT_CONFIG_KEY_0 = 'user.name' + $env:GIT_CONFIG_VALUE_0 = 'dtu-ci' + $env:GIT_CONFIG_KEY_1 = 'user.email' + $env:GIT_CONFIG_VALUE_1 = 'dtu-ci@invalid.local' + Write-Host '::warning::gitlint: no git identity here -- injected a throwaway one via GIT_CONFIG_* (needed only so gitlint can start)' + } + } + # --- resolve the commit range --------------------------------------- $range = '' $cleanBase = "$BaseSha".Trim() @@ -115,6 +145,7 @@ try { $checked = 0 $failed = 0 + $gateBroken = $false foreach ($sha in $shas) { $sha = "$sha".Trim() $checked++ @@ -134,12 +165,22 @@ try { $savedPref = $ErrorActionPreference $ErrorActionPreference = 'Continue' - & $PythonExe -s -m pre_commit run --hook-stage commit-msg ` - --commit-msg-filename $msgPath gitlint 2>&1 + # ForEach-Object { "$_" } unwraps the ErrorRecords that PS 5.1's + # 2>&1 builds for every native stderr line, without disturbing + # $LASTEXITCODE (docs/CI guide, section 7.3). + $hookOut = & $PythonExe -s -m pre_commit run --hook-stage commit-msg ` + --commit-msg-filename $msgPath gitlint 2>&1 | ForEach-Object { "$_" } $rc = $LASTEXITCODE $ErrorActionPreference = $savedPref + $hookOut | ForEach-Object { Write-Host $_ } - if ($rc -ne 0) { + if ($rc -eq 254 -or ($hookOut -match 'Missing git configuration')) { + # The gate itself could not run. Never report that as a + # message violation -- it is a runner/environment fault and + # needs a different fix (see the identity guard above). + Write-Host "::error::gitlint: GATE BROKEN (exit $rc, git config error) at $($sha.Substring(0, 8)) -- NOT a message violation" + $gateBroken = $true + } elseif ($rc -ne 0) { $failed++ Write-Host "::error::gitlint: commit $($sha.Substring(0, 8)) rejected -- `"$titleLine`"" } @@ -152,8 +193,12 @@ try { Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $summary -Encoding utf8 } + if ($gateBroken) { + Write-Host '::error::commit message gate could not RUN -- fix the runner git identity; do NOT read this as a message violation' + exit 2 + } if ($failed -gt 0) { - Write-Host '::error::commit message gate failed -- rule: single-line Chinese title, < 100 chars (see docs/ pre-commit usage guide + .gitlint)' + Write-Host '::error::commit message gate failed -- see the gitlint output above for the exact rule id (rules in .gitlint; AGENTS.md: title <= 100 chars, body lines <= 80)' exit 1 } exit 0