Skip to content

fix(service): re-register missing daemon services - #245

Open
Monsooooon wants to merge 2 commits into
mainfrom
shimu/fix-updater-service-reregistration
Open

fix(service): re-register missing daemon services#245
Monsooooon wants to merge 2 commits into
mainfrom
shimu/fix-updater-service-reregistration

Conversation

@Monsooooon

Copy link
Copy Markdown
Collaborator

What changed

  • Re-register collector and updater services independently when their service definition is missing or cannot start.
  • Cover launchd, systemd user/system scopes, init.d, and Windows Task Scheduler while preserving legacy unmanaged fallback when no service manager exists.
  • Verify process liveness before reporting restart success and propagate service registration failures.
  • Keep background self-heal elevation non-interactive without changing normal interactive install behavior.
  • Prevent open-source packages without updater-daemon.js from creating an empty updater service.
  • Prevent a delayed concrete service-manager start from racing with a second nohup process.

Why

Collector and updater share one init-type state file but have separate service definitions. After collector self-heal persisted a concrete manager, updater restart could incorrectly assume its own service was registered, fail to start, and emit pid file is missing; no matching process found.

Impact

Restart self-heal now repairs only the missing daemon service and reports success only after the process is observable. Open-source installs continue to omit the updater service when the updater payload is not shipped.

Validation

  • Shell syntax check
  • Service re-registration tests: 16/16
  • Updater watchdog tests: 14/14
  • TypeScript typecheck
  • Full build
  • Diff whitespace check

Validation boundaries

  • Real Linux init-manager, launchd, and Windows Task Scheduler environments were not available locally.
  • Install smoke did not start because .env.e2e is absent.
  • Broad tests have unrelated existing Hermes/OpenClaw failures; targeted tests and build pass.

@Monsooooon

Copy link
Copy Markdown
Collaborator Author

Code review summary

Verdict: PASS. No unresolved Critical, High, or Medium findings.

The review covered service ownership, lifecycle recovery, privilege boundaries, open-source packaging compatibility, and Windows parity. Two High findings found during review were fixed before publication:

  • missing open-source updater payload can no longer create an empty updater service;
  • detecting a concrete manager can no longer fall through to nohup after delayed liveness, avoiding double processes.

Validation passed: Shell syntax, 16/16 service re-registration tests, 14/14 updater watchdog tests, typecheck, build, and diff check.

Residual Low risks are limited to existing cross-edition updater payload residue and the absence of live Windows/init-manager E2E in this local environment.

@Monsooooon
Monsooooon marked this pull request as ready for review August 12, 2026 06:15

@ralf0131 ralf0131 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.

Summary

Fix daemon service re-registration when service definitions are missing or fail to start, covering launchd, systemd user/system, init.d, and Windows Task Scheduler. Includes comprehensive test coverage for all re-registration paths.

Highlights

  • Error propagation: All critical installer commands now use || return 1, ensuring failures are caught instead of silently continuing
  • Liveness verification: After service start, process liveness is verified before reporting success
  • Independent re-registration: Collector and updater are re-registered independently, preventing one from blocking the other
  • Non-interactive sudo scoping: _PILOT_SUDO_NONINTERACTIVE flag keeps self-heal elevation non-interactive without affecting normal install behavior
  • Updater guard: Checks for updater-daemon.js existence before attempting updater service install (important for open-source packages that omit the updater)

LGTM — solid fix with excellent test coverage.


Automated review by github-manager-bot

fi
# A concrete manager now owns this daemon. Even if its first liveness
# check is slow, starting a nohup copy could create duplicate collectors.
if [ "$_restarted" = false ] && [ "$_new_init" != "none" ]; then

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.

[High] :716 守卫条件 [ "$_restarted" = false ] && [ "$_new_init" != "none" ] 未区分「注册成功但进程未起来」与「注册失败」,而注释声称的前提是 A concrete manager now owns this daemon —— 注册失败时该前提不成立,却同样 exit 1

影响: collector 在 :630/:632-645 已被 pkill + kill PID_FILE 杀掉;注册失败后 :720-742 的 nohup 兜底变成不可达代码,collector 保持死亡。相对 base 是行为回归 —— base/head 对照实验:base ⚠️ collector restarted (nohup fallback, self-heal failed) exit=0 进程存活;head ❌ Service manager failed to restart collector (init_type=systemd-user) exit=1 进程不存活。

两个放大因素:

  1. exit 1 发生在 :751-759setsid ... restart-updater 调度之前 → updater 也不会重启,出现「版本指针=新、进程=旧」的不一致。
  2. 调用方 src/updater/updater.ts:1043logger.warn 不重抛,:284/:290 仍上报 collector_restarted 并把 consecutiveFailures 归零;而 src/metrics/metrics-writer.ts:265 只在 init_type ∈ {nohup, unknown} 时告警 → 无告警、无重试,采集停止无人知晓。

:864 的 updater 侧同构。测试 :318-349it.each 只覆盖「注册成功」这一支,本场景零覆盖。

建议: 用「是否真的注册成功」而非「detect 出了具体管理器」作为守卫依据:

_registered=false
if _PILOT_SUDO_NONINTERACTIVE=true autostart_install_collector_only "false" 2>>"$LOG_FILE"; then
    _registered=true
    sleep 1
    ...
fi
if [ "$_restarted" = false ] && [ "$_registered" = true ]; then
    echo "❌ Service manager failed to restart collector (registered as $_new_init)" >&2
    exit 1
fi

也可复用已有的接管探测(is_managed_by_launchd / is_managed_by_systemd_user / is_managed_by_systemd_system / is_managed_by_initd:321-340)作为「确实被接管」的判据。


Generated by LoongSuite-Pilot Code Review Agent

_new_init=$(detect_init_system "false")
if [ "$_new_init" != "none" ]; then
if _PILOT_SUDO_NONINTERACTIVE=true autostart_install_collector_only "false" 2>>"$LOG_FILE"; then
sleep 1

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.

[Medium] 自愈后只等 sleep 1 就用 is_running 判活,但 is_running:120-130只读 PID 文件、无 pgrep 兜底,而 cmd_run:387-404)把 PID 文件写在最后一步(:402)—— 之前还要跑 resolve_node,后者对每个候选执行 "$bin" --version_node_is_suitable:186)。

影响: 本机实测 6 个候选探测约 3.3s,远超 1s 窗口。「1s 后没起来」是常态而非异常,健康安装会被误判并推入破坏性自愈路径,叠加 :716 守卫后直接 exit 1。测试里 sleep 被 mock,CI 永远看不到这个时序。

建议: 改为有界轮询,或让 is_running 在 PID 文件缺失时回退 pgrep

for _i in $(seq 1 15); do
    is_running && break
    sleep 1
done

Windows 侧已有现成范式可抄:Wait-ForCollectorHeartbeat -TimeoutSeconds 30scripts/loongsuite-pilot.ps1:323)。


Generated by LoongSuite-Pilot Code Review Agent


case "$init_system" in
launchd)
launchctl unload -w "$LAUNCHD_PLIST" 2>/dev/null || true

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.

[Medium] 自愈块被提到 case "$init_type" 之外后,launchd 分支对健康安装也会执行 launchctl unload -w,而 -w 在 macOS 上写入的是持久化 Disabled override(跨重启/重新登录生效)。

影响: 常见路径是二次 kill + plist churn(KeepAlive 通常能恢复);尾部风险是 unload -w 成功、_write_launchd_plistload -w 失败 → agent 既未加载、又被持久禁用,重新登录也不会回来。base 对具体 init-type 从不触碰 plist,这是本 PR 新引入的可达路径。

建议: 先用 launchctl print gui/$(id -u)/<label> 或已有的 is_managed_by_launchd 判断服务定义是否真的缺失,缺失才重写;若必须重载,用不带 -wunload/load(或 bootout/bootstrap),并在 load 失败时显式 launchctl enable 回滚 Disabled 状态。


Generated by LoongSuite-Pilot Code Review Agent

initd)
_write_initd_script "$target_user"
_write_initd_script "$target_user" || return 1
_register_initd_boot "loongsuite-pilot-${target_user}"

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.

[Medium] initd 分支把「注册成功」写进了 init-type,但两个关键步骤都不校验:_register_initd_boot:1592-1601)在 chkconfig / update-rc.d 都不存在时只 echo 一句警告,恒返回 0:1643/etc/init.d/... start 又是 || true。随后 :1644 照样写入 init-type=initd 并返回 0。

影响: 记录了一个「既没启动本 daemon、也没注册开机自启」的管理器归属。这个假的具体 init-type 会被 detect_init_system:272-285 的白名单无条件短路信任,于是每次 restart 都命中 :716 的硬失败守卫,形成永不回退 nohup 的稳定失败吸引子

对比:同函数的 systemd 分支顺序是正确的(echo > INIT_TYPE_FILEenable --now 之后),initd 分支没有对齐。

建议: _register_initd_boot 在两个工具都缺失时 return 1:1643 的 start 改为 || return 1;并把 echo "initd" > "$INIT_TYPE_FILE" 移到两者都成功之后。:1686-1690 的 updater 侧同构。


Generated by LoongSuite-Pilot Code Review Agent

maybe_sudo systemctl daemon-reload &>/dev/null
maybe_sudo systemctl enable --now "loongsuite-pilot-${target_user}.service" &>/dev/null
echo "systemd-system" > "$INIT_TYPE_FILE"
_write_systemd_system_unit "$target_user" || return 1

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.

[Medium] 自愈路径(:704/:852)新引入了无人值守的提权写入:在 NOPASSWD 主机上,一次失败的 restart 最多触发 5 次 sudo -nmkdir -p /etc/systemd/systemtee unit、daemon-reloadenable --now),且失败后没有任何回滚。

影响: base 在具体 init-type 的 restart 路径上零提权写入,这是新增的写入面(不构成提权,User=<target_user> 未变)。部分失败会在 /etc/systemd/system/ 留下 root 所有、未 enable 的残留 unit,而 init-type 未变 → 后续每次 restart 都重写一遍;updater-watchdog 约每 ≥10 分钟重试,失败的 sudo -n 持续堆积 authpriv 日志。管理员手工改过的 unit 也会被静默覆盖。

建议: 写入前先判断服务定义是否真的缺失(test -f unit + systemctl cat),存在则不重写;tee 写 unit 改为「临时文件 + mv」保证原子性,任一步失败清理残留;给自愈加最小重试间隔(在 $DATA_DIR 记录上次自愈时间戳,10 分钟内不重复重写)。


Generated by LoongSuite-Pilot Code Review Agent

Write-Host "collector self-healed: registered with Task Scheduler"
$restarted = $true
}
try {

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.

[Medium] Windows 侧自愈同样不再受 $initType 限制,于是健康taskscheduler 安装在一次 1s liveness 误判后,就会经历 Stop-OrphanProcesses + schtasks /DeleteInstall-CollectorTask 内部 :407 还会再删一次)+ 重新 Register-PilotTask

影响: Register-PilotTask 先试 Interactive 再试 S4U,在 GPO 限制「登录为批处理作业」或 Access Denied 的机器上两者都会失败;此时任务已被删除、重建失败 → 机器上一个任务都不剩,采集彻底停止且不再开机自启。base 的 $initType 门禁使这条路径不可达。:901-914 的 updater 侧同构。

建议: 重注册前先 Get-ScheduledTask -TaskName <name> -EA SilentlyContinue 判断定义是否真的缺失;确需重建时先 Export-ScheduledTask 备份 XML,Register-PilotTask 全部失败时用备份回滚,并把失败原因写入日志与告警。


Generated by LoongSuite-Pilot Code Review Agent

Write-Host "Self-heal failed: $($_.Exception.Message)" -ForegroundColor Yellow
}
if (-not $restarted) {
if ($initType -in @("background", "unknown", "")) {

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.

[Medium] Windows 侧缺少 POSIX :716 的对偶守卫。当 $initType ∈ {background, unknown, ""} 且自愈的 Install + Start 都成功、只是 Get-TaskRunning 在 1s 内为 false 时,控制流会继续落到 Start-Process 兜底。

影响: 已注册并已启动的 Scheduled Task 之外再拉起第二个 collector,形成重复采集 —— MultipleInstances IgnoreNew 只约束 task 实例、不约束 Start-Process 拉起的副本。更糟的是 Set-Content init-type "taskscheduler":811)只在 liveness 为真的分支执行,init-type 保持旧值 → 每次 restart 都会重演。这正是 :504-509 注释所描述的事故类型;而测试 :408expect(body.slice(fallbackGuard)).toContain('Start-Process') 反而把这个兜底钉死成了预期行为。

建议: 与 POSIX 对齐:自愈注册成功后不再走 Start-Process,改用已存在的 Wait-ForCollectorHeartbeat:323)等待,超时则报错退出;并把 Set-Content init-type 移到注册成功后无条件执行,避免状态不收敛。同步修正测试断言,使其钉「注册成功后不 fallback」。


Generated by LoongSuite-Pilot Code Review Agent

expect(result.stderr).toContain('Service manager failed to restart updater (init_type=initd)');
});

it.each([

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.

[Medium] 新增的 16 个用例无法钉住本 PR 的核心行为。变异测试(node 22,16/16 基线全绿)证明:① 把 sh:703 的自愈门禁回滚成「仅 legacy init-type 才自愈」→ 16/16 仍全绿;② 把 sh:666/:813 的 start 退出码检查回滚成 || true16/16 仍全绿;③ 删掉 systemd-user 分支的 || return 116/16 仍全绿

影响: 核心修复可被静默回滚而 CI 全绿。本 it.each 只覆盖「注册成功」这一支,正是 sh:716 回归的漏网场景;uname 被 mock 成 Linux,launchd 与 initd 分支从未执行;Windows 段(:370-411)全是 indexOf/正则的源码文本断言,不执行任何 PowerShell。另外 :189-195id() mock 在 String.raw"\${1:-}" 是字面量,永不等于 -u,该 mock 从未生效。

建议: 补三类真实执行用例:

  1. 自愈注册失败(让 autostart_install_collector_only 返回 1)时断言仍走 nohup 兜底且进程存活;
  2. 注册成功但 liveness 延迟时断言进程数恒为 1;
  3. 参数化 uname 覆盖 Darwin / initd 分支。

Windows 侧改为 pwsh -NoProfile -Command 注入 schtasks / Get-ScheduledTask stub 做行为断言,替换文本断言。

建议把变异测试作为验收门禁:上述三个回滚中任意一个都必须导致用例失败。


Generated by LoongSuite-Pilot Code Review Agent

@linrunqi08

linrunqi08 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

🔍 Code Review Summary

Reviewed at b2c590887c6b19452ee95f3df83a07c22dabf3db (base 58b24404), 3 files / +588 −134.

Severity Count
Critical 0
High 1
Medium 7
Low 8

Lifecycle Verdict

Check Result
资源释放 FAIL
死锁/卡死风险 PASS
状态恢复正确性 FAIL

资源释放 FAIL

  1. collector 在 sh:630/:632-645 被杀后,:716 守卫使 :720-742 的 nohup 兜底不可达;注册失败时进程不再被拉起。base/head 对照:base exit=0 进程存活,head exit=1 进程不存活。
  2. launchctl unload -wsh:1622)与 _write_launchd_plist/load -w 之间存在窗口,失败即留下「未加载 + 持久 Disabled」的 launchd agent。
  3. 提权写入(sh:1635-1637)无回滚,部分失败会在 /etc/systemd/system/ 留下 root 所有的未 enable 残留 unit,且每次 restart 重复写入。

状态恢复正确性 FAIL

  1. initd 分支在既未启动、也未注册开机自启的情况下写入 init-type=initdsh:1642-1644),随后被 detect_init_system 白名单无条件信任,形成永不回退 nohup 的失败吸引子。
  2. sh:718exit 1 早于 :751-759restart-updater 调度 → collector 与 updater 双双未重启,而 updater.ts:284/:290 仍上报 collector_restarted 并把 consecutiveFailures 归零 → 版本指针与实际进程长期不一致且无告警(metrics-writer.ts:265 只对 nohup|unknown 告警)。
  3. Windows 侧 Set-Content init-typeps1:811/:907)只在 liveness 为真的分支执行,状态不收敛,重复进程场景每次 restart 重演。

Merge Gate(合入门禁)

BLOCK ⛔ —— 阻断合入,待修复后重评(阻断级别:Critical / High / MediumLow 不阻断;Lifecycle 任一 FAIL 也阻断)

未解决的阻断项:

# Severity 位置 现状
1 High scripts/loongsuite-pilot.sh:716(及 :864 守卫未区分「注册成功」与「注册失败」→ collector 静默死亡,相对 base 的行为回归
2 Medium scripts/loongsuite-pilot.sh:705 1s liveness 窗口短于 resolve_node 实测耗时(约 3.3s),健康安装被误判入自愈
3 Medium scripts/loongsuite-pilot.sh:1622 launchctl unload -w 对健康 job 可达,失败可留下持久 Disabled
4 Medium scripts/loongsuite-pilot.sh:1642 initd 分支伪造管理器归属(_register_initd_boot 恒返回 0、start 被 `
5 Medium scripts/loongsuite-pilot.sh:1635 无人值守提权写入无回滚、无限流,watchdog 每 ≥10min 重复触发
6 Medium scripts/loongsuite-pilot.ps1:805(及 :901 破坏性重注册,S4U/GPO 失败后可能一个任务都不剩
7 Medium scripts/loongsuite-pilot.ps1:820(及 :916 缺 POSIX :716 的对偶守卫 → 持久重复采集
8 Medium tests/unit/scripts/service-reregistration.test.mjs:318 核心行为零有效覆盖(变异测试证明可静默回滚而 16/16 全绿)

细节见对应行的 inline 评论。

总体结论

修复方向正确:把「本机用哪个服务管理器」与「本 daemon 的服务定义是否存在」解耦,是对根因的准确修复;顺带修掉了「start 失败误报成功」「注册失败仍固化 init-type」两个真实缺陷;systemd → systemd-system 的归一化方向经 97cf16cc 的 git 历史验证正确。

阻断原因集中在为防重复进程而新增的守卫上,两侧问题正好相反:

  • POSIX 侧过严sh:716 的守卫在「自愈注册失败」时也拒绝 nohup 兜底。而此时 collector 已被杀掉,于是保持死亡;上游 updater.ts:1043 又吞掉失败并上报 collector_restarted、把 consecutiveFailures 归零,metrics-writer.ts:265 只对 nohup|unknown 告警 —— 无告警、无重试、采集静默停止。相对 base 这是可复现的行为回归(base 会 nohup 兜底,exit=0 且进程存活)。
  • Windows 侧缺失ps1:820 没有对偶守卫,注册成功 + liveness 误判会用 Start-Process 再拉起第二个 collector,且 init-type 不收敛导致每次 restart 重演。

CI 全绿不构成反证:变异测试显示,回滚本 PR 的自愈门禁(sh:703)、回滚 start 退出码检查(sh:666/:813)、删掉 systemd-user 的 || return 1,三者中任意一个都不会导致任何用例失败(16/16 仍全绿)。uname 被 mock 成 Linux,launchd 与 initd 分支从未执行;Windows 段是源码文本断言,不执行 PowerShell。

需要说明:本 PR 已有一份 APPROVED review(无 inline 评论)。本轮裁决与之存在分歧,分歧点即上述 POSIX 回归,可用 autostart_install_collector_only 返回 1 的对照实验复现。建议至少先修 Finding 1 与测试覆盖再合入。

另有两处不在本 PR diff 范围内、但已成为承重结构的问题,建议同 PR 或紧随其后处理:

  • sh:1698-1758 autostart_install 未加固:无任何 || return 1,每个分支最后一条命令都是 echo "<type>" > "$INIT_TYPE_FILE",因此恒返回 0;调用点 :445 if autostart_install "true"; then 位于 if 条件中(errexit 被挂起)。于是 cmd_start:455-457 会打印「Service registered but collector process not found after 2s」+「the service manager will keep retrying」并 return 0 —— 两句都是假的,而它写下的假具体 init-type 正是 Finding 1 硬失败的燃料。本 PR 加固了两个 *_only 兄弟函数,却漏掉了共享的安装入口。
  • ps1:792-794 collector 主路径在 Start-ScheduledTask 后直接 $restarted = $true,无 liveness 复核(updater 侧 :887 与 POSIX :691-697 都有)。结果是 Windows 最常见的失败形态(任务启动、node 立刻退出)根本进不了新增的自愈逻辑,本 PR 的 Windows 改动大半不可达。

Low(不阻断,建议随手修)

  • sh:717/:865探测到的管理器写成 init_type=,与文件里的真实值可能不同;「注册失败」与「注册成功但没起来」共用同一句文案;自愈 stderr 被 2>>"$LOG_FILE" 吞掉,告警链路拿不到真实原因。
  • sh:1444-1451/:1579-1589return "$cleanup_status" 会用清理动作的退出码覆盖成功结果(已复现:目录不可写时 rm -f 返回 1 → 成功的注册被判失败)。同函数的 heredoc catsed -i.bak 仍未校验。
  • sh:1655-1659 把「无 updater 产物」表达为 return 1,而安装期 autostart_install 对同一事实是「跳过并成功」;开源分发无 updater 产物属正常形态,却会让 watchdog 每 10 分钟报一次误导性失败。
  • _PILOT_SUDO_NONINTERACTIVE 从环境读取(fail-closed,不构成提权),但 VAR=val <function> 前缀在 bash --posix/sh/POSIXLY_CORRECT=1 下会泄漏并保持 export;且 stop(:622/:783)与常规 start(:674/:821)仍是交互式 sudo,非交互化只做了一半。建议函数内 local 化或直接复用 maybe_sudo_n
  • scripts/e2e/test-detect-init-runner.sh:47 的内联 detect_init_system 副本缺 systemd) 归一化分支,与主脚本 :275-284 漂移(仓库既有约定 dc4120fc / fix: remove nohup fallback, auto-cascade init system detection #149 是两处同步)。
  • tests/.../service-reregistration.test.mjs:189-195id() mock 是死代码:String.raw"\${1:-}" 是字面量,永不等于 -u
  • deploy/autostart.sh:44user 级 systemd 分支输出裸 systemd,与本 PR「裸 systemd = 系统级」的归一化方向相反;今天无害(该脚本从不写 init-type),但后续复用会踩坑。
  • updater-daemon.js 存在性检查在 sh 里重复 6 处,建议提取 _updater_payload_existsautostart_remove:1780|systemd 分支已成死代码。

Highlights(正向实践)

  • 自愈块从 case "$init_type"提升出来,正确解耦了两个独立概念 —— 这是对根因的修复,不是打补丁。
  • if <start>; then _restarted=true; fi:656/:666/:674/:682)修掉了 base「start 失败仍打印 ✅」的真实缺陷。
  • autostart_install_*_only 逐步 || return 1,注册失败可传播,不再把错误状态固化进 init-type
  • _write_initd_script 引入显式 install_status,修掉了「返回值被尾部 rm -f 覆盖」的真实 bug。
  • POSIX 测试是真实 bash -c 执行而非文本断言;setsid 延迟触发 restart-updater:751-759)用新进程组规避「被 updater 自己的 stop 杀掉」,考虑周全。

评审报告详见: code-review/pr-245/final-report.md
Generated by LoongSuite-Pilot Code Review Agent

@linrunqi08

Copy link
Copy Markdown
Collaborator

🔍 CLM-Focused Re-Review (Windows Constrained Language Mode)

Reviewed at b2c590887c6b19452ee95f3df83a07c22dabf3db (base 58b24404). This is a targeted re-review focusing on Windows CLM/WDAC impact.

Language-Level CLM Safety: PASS ✅

All PS1 constructs in the diff are CLM-compatible:

  • Cmdlet calls (Install-CollectorTask, Start-ScheduledTask, Get-TaskRunning, Register-PilotTask, etc.) — always allowed in CLM
  • CimInstance parameter passing (New-ScheduledTaskPrincipalRegister-ScheduledTask) — no method calls on CimInstance, only parameter binding
  • $_.Exception.HResult / .Message — instance property reads, allowed on any type in CLM
  • [TimeSpan]::Zero / [int] — core types, in ALLOWED_STATIC_ACCESS whitelist
  • String methods (.Trim(), .ToLower(), .Contains()) — [string] is a core type
  • Start-Job -ScriptBlock { $using:... } — cmdlet + language scope modifier, scriptblock only uses & and Start-Sleep
  • Start-Process -FilePath "powershell.exe" background fallback — external process, inline script only uses env var assignments + & operator
  • Comments in diff are ASCII-only

Static CLM test: 33/33 passed (tests/unit/deploy/installer-ps1-clm-safe.test.mjs).

WDAC Environmental Concern: Destructive Re-Registration [Medium]

Not a CLM violation, but a behavioral risk specific to WDAC-enforced machines.

The PR removes the $initType -in @("background", "unknown", "") gate, so Install-CollectorTask now runs for any $initType including "taskscheduler". On WDAC machines, this matters because:

  1. WDAC environments typically also have restrictive GPO settings
  2. Register-PilotTask tries Interactive → S4U; S4U requires "Log on as a batch job" right, commonly restricted by GPO on WDAC-managed machines
  3. Install-CollectorTask deletes the existing task first (schtasks /Delete at :513-514) before calling Register-PilotTask
  4. Both logon types fail → throw → caught at :816 → existing task already gone → falls to :820 but $initType = "taskscheduler" doesn't match @("background", "unknown", "")exit 1 at :837

Result on WDAC+GPO machine: Working taskscheduler task deleted → re-registration fails → no fallback available → collector dead.

Compare with Cmd-Start (:665-721) which has Start-CompatibleExistingCollectorTask as a secondary fallback when registration fails — the self-heal path lacks this.

Suggested fix: Pre-check before destructive re-registration:

if (-not (Get-TaskExists $TASK_NAME_COLLECTOR)) {
    $ok = Install-CollectorTask $nodeBin
    # ... existing logic
}

Only re-register when the task is genuinely absent. This preserves the PR's intent (repair missing task definitions) while avoiding destructive churn on healthy WDAC installs where the liveness check was just slow.

This reinforces Finding #6 from the previous review round — the CLM/WDAC angle makes the "delete then fail to re-register" scenario more probable because GPO restrictions and WDAC policy verification overhead are co-located.

Minor: -Encoding UTF8 gap on init-type file [Low, pre-existing]

Set-Content -Path $INIT_TYPE_FILE -Value "taskscheduler" (:811, :907) and Get-Content $INIT_TYPE_FILE (:804, :900) lack -Encoding UTF8. Node reads this file at src/metrics/metrics-collector.ts:444 with readFileSync(..., 'utf-8').

Current risk is negligible — "taskscheduler" is pure ASCII. But it violates the codebase convention (AGENTS.md §编码) requiring explicit -Encoding UTF8 for all PS↔Node shared files. The ps1-json-encoding.test.mjs ratchet only covers ConvertFrom-Json/ConvertTo-Json patterns, so init-type falls through.

Minor: 1s liveness window vs WDAC startup overhead [Low]

Start-Sleep -Seconds 1 (:809, :905) before Get-TaskRunning may be insufficient on WDAC machines where Task Scheduler startup involves policy evaluation at each exec boundary (wscript.exe → VBS → node.exe). The existing Wait-ForCollectorHeartbeat (:323, 30s timeout) would be more appropriate.


CLM-focused review report: code-review/pr-245/clm-focused-review.md
Generated by LoongSuite-Pilot Code Review Agent

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants