Apply 28 review fixes: log retention, Unknown state, autostart isolation, CI & docs - #1
Conversation
补充:自启动设置功能检查结果按需求对自启动功能做了功能级验证(在临时 HKCU 测试键上往返,不触碰真实自启动项): 基线(修复前代码)
修复后(#7 + #13 + #20 生效后)
结论:自启动的写入/读取/清除链路本身工作正常;本次修复补齐的是**多目录值名冲突(#7)与陈旧条目误判已启用(#13)**两个隐患。相关单测已加入 |
[3700f53] fix(#1):run.cmd 实时日志改为追加写入并合并 stderr问题
修复 改为经 $redirectCommand = '""{0}" >> "{1}" 2>&1"' -f $RunCmdPath, $RunCmdLiveLogFile
$runCmdProcess = Start-Process -FilePath $env:ComSpec -ArgumentList '/c', $redirectCommand ...
验证 在带空格的路径上实测两次运行:标记 + FIRST + SECOND 输出全部保留、stderr 行合并、退出码 0/7 正确透传。 |
[074c17a] fix(#2):进程路径不可验证时显示 Unknown,不再误报 Stopped问题
修复
附带:README 状态说明同步更新。 |
[b5e5e21] fix(#3):WaitForExit 加超时,杜绝永久挂起问题
修复 if (-not $runCmdProcess.WaitForExit(15000)) {
# 再强杀一次,仍不退出则放弃等待
Stop-Process -Id $runCmdProcess.Id -Force -ErrorAction SilentlyContinue
[void]$runCmdProcess.WaitForExit(10000)
}并顺带修复一个隐患: |
[02786e6] fix(#4):修复日志查看器“无日志”分支永不触发问题
但 修复 function Get-RunCmdLiveLogPath {
if (-not (Test-Path -LiteralPath $RunCmdLiveLogFile)) { return $null }
return $RunCmdLiveLogFile
}文件创建后(宿主启动 run.cmd 时)查看器 3 秒刷新会自动切换到实际内容。 |
[24bad38] fix(#5):宿主 PID 文件增加身份校验,防 PID 复用误杀问题
修复 读取 PID 后增加两级校验:
校验失败即删除陈旧 PID 文件并写宿主日志,返回 null。这样停止流程只会作用于真实的宿主进程。 |
[43a4ad3] fix(#6):启动等待支持失败快速返回问题 原实现固定等 20 秒 Idle:
修复
|
[cf9c48b] fix(#7):自启动注册表值名按目录哈希隔离问题 自启动值名固定为 修复
验证:在临时注册表键上往返测试通过;相关单测见 |
[1b4abbd] fix(#8):托盘菜单显示所管控的 runner 目录问题 一台机器上多 runner 并存时,托盘只显示状态,无法分辨这个托盘管的是哪个目录。 修复 在状态行下方新增只读菜单项: $dirItem = $contextMenu.Items.Add("Runner directory: $ScriptRoot")
$dirItem.Enabled = $false信息随菜单即开即见,不占托盘 tooltip 的 63 字符上限。 |
[6d086da] fix(#9):托盘全局异常处理问题 所有菜单回调与 5 秒刷新定时器都没有异常保护:任何一次 throw(例如权限变化、文件被占用)都会静默杀死托盘进程,用户只看到图标消失。 修复
|
[879c419] fix(#10):启动早期失败不再无声无息问题
修复 整个 CLI 派发(
这样隐藏窗口启动时的失败也能被看到、被排查。 |
[d7b8812] fix(#11):托盘图标按状态缓存问题
修复 if (-not $script:IconCache.ContainsKey($state)) {
$script:IconCache[$state] = New-StatusIcon -State $state
}
$notifyIcon.Icon = $script:IconCache[$state]Stopped/Idle/Busy/Unknown 各缓存一份,状态不变直接复用;托盘退出时统一 |
[c5d9965] fix(#12):宿主/实时日志按大小滚动问题
修复 新增 if ($item.Length -gt $MaxBytes) {
Remove-Item "$Path.1" -Force -ErrorAction SilentlyContinue
Rename-Item $Path -NewName (Split-Path -Leaf "$Path.1")
}调用点: |
[bc63fb0] fix(#13):自启动仅当存储命令与当前脚本一致时视为已启用问题
修复 return ($currentValue.Trim() -ieq (Get-AutostartCommand))只有存储命令与 验证:临时注册表键往返 + 陈旧命令判禁用,全部通过;单测见 |
[30a20d6] fix(#14):双击托盘打开最新 runner 日志问题 双击托盘弹出一个状态 MessageBox,不符合 Windows 托盘交互直觉(双击通常用于“打开主界面/日志”)。 修复 双击改为:
查看日志是排查 runner 问题的最常用入口,放双击最顺手。 |
[b552cdb] fix(#15):状态切换气泡通知(可关闭)问题 任务开始(Idle → Busy)没有任何提示,只能靠盯图标颜色变化,容易错过。 修复
|
[d168f12] fix(#16):声明进程 DPI awareness问题 未调用 修复
[DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();
|
[3e63bed] fix(#17):Stop runner 前确认问题 菜单点一下 Stop runner 就立即强杀 listener/worker——若正有任务执行会直接中断,且 README 明确提示过这一点。 修复 菜单路径先弹 Yes/No 确认: $confirm = [MessageBox]::Show('Stop the GitHub Actions runner? Any job currently running will be interrupted.', ..., YesNo, Warning)
if ($confirm -ne [DialogResult]::Yes) { return }命令行 |
[21c093e] fix(#18):添加 MIT LICENSE问题 仓库没有任何 LICENSE,第三方复用/商用存在法律不确定性。 修复
|
[e373234] fix(#19):新增 CI(PSScriptAnalyzer + Pester)问题 没有任何自动化质量门禁,PowerShell 脚本改动只能靠人肉。 修复
测试文件用“裁剪 dispatch + 加载函数”的方式加载脚本,不会启动托盘。由于 Pester 5 在 CI 上对文件顶层定义与 It 块作用域隔离,每个用例都在自身作用域内完整加载脚本函数。最终套件已在 CI(Windows PowerShell 5.1 与 PowerShell 7)以及本机等价断言脚本上全部通过。 |
[e83db6e] fix(#20):无 Windows PowerShell 时回退 pwsh问题
修复 if (Test-Path -LiteralPath $WindowsPowerShellExe) {
$PowerShellExe = $WindowsPowerShellExe
} else {
$pwshCommand = Get-Command 'pwsh' -ErrorAction SilentlyContinue
$PowerShellExe = $pwshCommand ? $pwshCommand.Source : $WindowsPowerShellExe
}验证:回归时本机(仅 PowerShell 7)生成的命令正确使用 |
[24bc199] fix(#21):新增 install.ps1 部署脚本问题 部署全靠手工复制到 修复
.\install.ps1 # 默认 C:\actions-runner
.\install.ps1 -RunnerDir D:\my-runner # 指定目录
验证:临时目录冒烟测试通过(复制 + 备份 + 完成提示)。README 部署章节见 #24。 |
[a83305b] fix(#22):扩展 -SelfTest 覆盖范围问题
修复 新增检查项:
这样一条 |
[7982d82] fix(#23):README 补充安全说明问题 README 未提及 修复 新增“安全说明”章节:
|
[04a371e] fix(#24):README 整体更新问题 README 与修复后的实际行为脱节(旧菜单列表、旧状态描述、无部署/CI 章节)。 修复 整体重写,同步以下内容:
|
Start-Process -RedirectStandardOutput truncates the target file, so every run.cmd restart wiped run-cmd-live.log history (including the 'Starting run.cmd' marker). Launch run.cmd through cmd.exe with an append redirect (>>) and merge stderr via 2>&1 so the live log keeps full history and error output.
.Path throws for processes owned by another account; the old code swallowed that and reported 'Stopped', making Start/Stop controls act on wrong information. Now: try CIM as fallback, and if the directory still cannot be verified, track the process as unresolved and report an 'Unknown' state (gray icon, controls disabled, guidance to run elevated) instead of a false 'Stopped'.
WaitForExit() without a timeout can hang forever if the process does not release its handles. Use WaitForExit(15000) and, if still alive, kill again with a second bounded wait; fall back to exit code -1 instead of reading ExitCode on a live process.
The log viewer decides between 'No runner log found yet.' and 'Current file: ...' from the resolver's return value, but this function always returned the path, so the empty branch was dead code and the label showed a path even before the file existed. Check Test-Path first.
A PID file is only trustworthy while the PID maps to our own PowerShell host. Validate that the process is powershell/pwsh and (best effort) that its command line references -RunnerHost; otherwise treat the file as stale. Prevents Stop-RunnerControl from force-killing an unrelated process after a PID reuse.
Menu callbacks and the refresh timer ran with no protection, so any throw silently killed the tray process. Wrap every handler in try/catch routed through Show-TrayError (host log + message box) and register Application.ThreadException / AppDomain.UnhandledException loggers as a final safety net.
The cmd launcher starts the script hidden, so any failure before the tray appears (Add-Type, registry, etc.) was invisible. Wrap the whole dispatch in try/catch: write the exception to %TEMP%\\github-runner-trayicon-boot.log and, when possible, show a message box with the failing detail.
refreshUi rebuilt and disposed a fresh bitmap/icon every 5 s even when the state had not changed. Cache one Icon per state (Stopped/Idle/Busy/Unknown) and reuse it until the state actually changes; dispose the cache on exit.
runner-host.log and run-cmd-live.log grew without bound over long uptimes. Add Test-LogRollover: when a log exceeds 5 MB, rename it to <name>.1 (overwriting the previous generation) and start a fresh file. Applied in Write-HostLog and before each run.cmd live-log append.
…ches Test-AutostartEnabled only checked that the registry value was non-empty, so a stale entry (directory moved, old fixed value name) showed the checkbox as enabled even though nothing functional would run. Compare the stored command with Get-AutostartCommand before reporting enabled.
Double-clicking the tray icon showed a status message box, which is not what users expect. Make it open the latest Runner_*.log diagnostic file (falling back to the status message when none exists yet).
Add a 'Show state notifications' menu item (default on) that fires a balloon tip when the detected state changes (e.g. Idle -> Busy when a job starts). The first refresh only establishes the baseline and does not notify.
…lays Without SetProcessDPIAware the tray icon and viewer windows are bitmap- scaled and blurry on high-DPI monitors. Call user32 SetProcessDPIAware() from Initialize-UiAssemblies (best effort, ignored on failure).
Stop force-kills the listener/worker and interrupts any running job, but clicking the menu item acted immediately. Add a Yes/No confirmation dialog; the -StopRunner CLI switch keeps its scriptable behavior.
The repo had no license, which leaves reuse legally ambiguous. Add the standard MIT license (copyright NEVSTOP-LAB, 2026) and whitelist it in .gitignore.
Add a GitHub Actions workflow (windows-latest): a static-analysis job that runs PSScriptAnalyzer at Error severity plus a parser syntax check, and a test job running the Pester suite on both Windows PowerShell 5.1 and PowerShell 7. Tests load the script's functions without executing the dispatch and cover the autostart round-trip (on a scratch registry key), stale-command detection, the Stopped/Idle/Busy/Unknown state machine and log rotation. All assertions were exercised locally with the equivalent logic (Pester 5 is not installable on this machine).
The script hardcoded \C:\Program Files\PowerShell\7\\powershell.exe for the host process, the autostart entry and the STA relaunch. When the machine only has PowerShell 7 (pwsh), the relaunch/host would target a nonexistent binary. Detect powershell.exe first, then pwsh via Get-Command.
Deployment was manual copy-into-C:\\actions-runner with no script. Add install.ps1 that copies runner-tray.ps1/cmd/README/LICENSE into the target runner directory (default C:\\actions-runner), backs up existing copies as <file>.bak and warns when the target does not look like a runner directory.
-SelfTest only verified icon creation and dumped info. Add checks for run.cmd and bin directory presence, a state-directory write probe, the autostart registry value name, and generate the Unknown icon too, so a quick self-test covers the pieces the tray depends on.
Clarify the ExecutionPolicy Bypass trade-off (signing / RemoteSigned as an alternative) and the account/elevation model behind the Unknown state, so users know why the tray may show Unknown and what to do about it.
General documentation pass: deployment via install.ps1 (arbitrary runner directory), per-directory autostart value name, appended run.cmd live log with stderr, log rotation, Unknown state, notification toggle, double-click behavior, stop confirmation, pwsh fallback, SelfTest scope, and the CI / test section.
fc46638 to
04a371e
Compare
Follow-up review: the install script is not needed. Remove install.ps1, its .gitignore whitelist entry and the README deployment section; the README now documents the plain copy of runner-tray.ps1/runner-tray.cmd into the runner directory.
Follow-up review: double-clicking the tray icon should open the run.cmd live output window, not the latest runner diagnostic log. Reuse the same Show-LogViewerWindow path as the "Live view run.cmd output" menu item and update the README.
[438f275] fix(#21):移除 install.ps1(跟进调整)反馈:不需要安装脚本。 调整
理由:本工具只有两个交付文件,复制即用;安装脚本反而多一层维护面(备份策略、参数、警告逻辑都要长期维护)。删除后仓库交付物更精简。 验证:README 已无任何 install 引用;CI(PSScriptAnalyzer 根目录 |
[be79d1e] fix(#14):双击托盘打开 run.cmd 实时输出窗口(跟进调整)反馈:双击 trayicon 应该打开的是 run.md 的输出窗口(即 run.cmd 实时输出)。 调整 双击处理器改为与菜单 Live view run.cmd output 完全一致的路径: $notifyIcon.Add_DoubleClick({
try {
Ensure-StateDirectory
Show-LogViewerWindow -PathResolver { Get-RunCmdLiveLogPath } -Title 'GitHub Runner - run.cmd Live Output'
} catch {
Show-TrayError -ErrorRecord $_
}
})
说明:如仍需要“打开最新诊断日志”,可用菜单 Open latest runner log;双击的定位现在是查看当前任务输出。 |
There was a problem hiding this comment.
Pull request overview
This PR batches a large set of review-driven fixes to the PowerShell-based GitHub Actions runner tray tool, focusing on safer process/state detection, improved logging/retention, more robust autostart behavior, and adding CI + tests to keep these behaviors stable.
Changes:
- Improve runner process/state detection (including an explicit
Unknownstate) and safer stop/start behavior. - Improve logging behavior (append+stderr merge for
run.cmd, log rotation, better “no log yet” handling) and error surfacing. - Add CI (PSScriptAnalyzer + Pester) and Pester test coverage; update docs and add MIT license.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/runner-tray.Tests.ps1 |
Adds Pester tests covering autostart command/registry, state detection, and log rotation. |
runner-tray.ps1 |
Implements the functional fixes: Unknown state detection, autostart hashing, log rotation, safer process handling, UI hardening, DPI awareness, and CLI/self-test enhancements. |
README.md |
Updates deployment + behavior documentation to match the new runtime behavior and safety notes. |
LICENSE |
Adds MIT license text to the repository. |
.gitignore |
Ensures CI workflow, tests, and license/docs are not accidentally ignored. |
.github/workflows/ci.yml |
Adds Windows CI jobs for PSScriptAnalyzer, syntax validation, and Pester on both Windows PowerShell and pwsh. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
$PSHOME points at the current host's install directory, so when the script runs under PowerShell 7 the derived $PSHOME\powershell.exe never exists and the code always fell back to pwsh - the "prefer Windows PowerShell" behavior was dead code in that case. Resolve the canonical %WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe instead (with a $PSHOME fallback when %WINDIR% is unset).
… review) Two related autostart gaps found in review: - Test-AutostartEnabled only checked the new per-directory value name, so a pre-hashing upgrade that still has the legacy fixed-name value (pointing at this script) was reported as disabled although Windows still launches the tray at startup. It now checks both names and reports enabled when either holds the exact expected command. - Set-AutostartEnabled only removed the legacy value when disabling. If a legacy value was present and the user enabled autostart again, Windows would end up with both values and double-launch the tray. Enabling now removes the legacy value first. Two Pester tests cover both behaviors (legacy value counts as enabled; enabling migrates/cleans the legacy value).
修改列表
按审查编号逐项修复,每项一个提交(
fix(#N): ...),共 24 项修复 + 2 个跟进调整 + 2 个 Copilot review 修复,合计 28 个提交。3700f53>>追加重定向并合并 stderr,实时日志不再被截断074c17ab5e5e2102786e624bad3843a4ad3cf9c48b1b4abbd6d086da879c419d7b8812c5d9965bc63fb030a20d6be79d1e)b552cdbd168f123e63bed21c093ee373234e83db6e24bc199438f275)a83305b7982d8204a371e跟进调整(人工反馈)
438f275be79d1eCopilot review 修复
9f00eb7%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe(原$PSHOME在 pwsh 下失效)1bf89a9每个提交的详细说明见各条 PR 留言。