VS Code 版 Codex 任务完成后,自动通知安卓手机:Windows + ntfy + PowerShell 实战

📅 2026/7/1 15:29:51
VS Code 版 Codex 任务完成后,自动通知安卓手机:Windows + ntfy + PowerShell 实战
一、需求背景我想实现这样一个效果 VS Code 里的 Codex 插件在任务执行完成后自动给我的安卓手机发一条通知。 我使用的是 - Windows - VS Code - VS Code 内的 Codex 插件 - 安卓手机 - ntfy 作为推送通道 本文所有路径和主题名都已经脱敏示例中统一使用占位符 - Windows 用户目录C:\Users\YourUser - Codex 目录C:\Users\YourUser\.codex - ntfy 主题YOUR_NTFY_TOPIC二、先说结论一开始我以为只要在 config.toml 里配置 notify [...] 就够了但在 **Windows VS Code 插件版 Codex** 这个组合下实际会遇到一个坑 内置 notify 确实会触发但 **任务完成时传给脚本的参数可能过长**从而导致 Windows 侧执行失败日志里会出现类似 text after_agent hook failed ... hook_namelegacy_notify ... (os error 206)所以更稳的方案不是继续死磕内置 notify而是监听 C:\Users\YourUser\.codex\sessions 里的 task_complete 事件再主动调用自己的通知脚本。三、我的环境本文对应的设备和软件环境如下操作系统Windows编辑器VS CodeAI 插件CodexCodex 工作目录C:\Users\YourUser\.codex通知方案ntfy手机AndroidVS Code 设置里未启用 WSL 模式四、先验证 ntfy 链路是否打通先不要碰 Codex先确认 Windows - ntfy.sh - 安卓手机 这条链路是通的。curl.exe -v -d hello from windows https://ntfy.sh/YOUR_NTFY_TOPIC如果手机能收到通知说明 ntfy 本身没问题。五、先准备一个最小通知脚本在下面这个位置创建文件C:\Users\YourUser\.codex\codex_ntfy_notify.ps1内容如下param($Json) $log $env:USERPROFILE\.codex\notify_log.txt Add-Content -Path $log -Value ( (Get-Date).ToString(yyyy-MM-dd HH:mm:ss) ) Add-Content -Path $log -Value (ARG: $Json) $topic YOUR_NTFY_TOPIC $url https://ntfy.sh/$topic $body Codex task done. Check VS Code. try { curl.exe -s -H Title: Codex Done -H Priority: high -H Tags: computer -d $body $url | Out-Null Add-Content -Path $log -Value SEND: OK } catch { Add-Content -Path $log -Value (SEND: ERROR $_.Exception.Message) }手动测试 C:\Users\YourUser\.codex\codex_ntfy_notify.ps1 {type:agent-turn-complete,last-assistant-message:test}如果手机收到通知并且 notify_log.txt 有新增记录说明这个脚本是正常的。六、为什么内置 notify 在 VS Code 插件版里不稳我做完排查后的结论是VS Code 版 Codex会读取C:\Users\YourUser\.codex\config.tomlVS Code 版 Codex也支持notify真正的问题不是“不支持”而是Windows 启动 notify 时参数过长也就是说不是ntfy有问题不是 PowerShell 脚本有问题而是 Codex 结束任务时传给notify的那段 JSON 可能太长导致 Windows 报os error 206所以我最后改成了外部 watcher 方案。七、最终方案结构最终结构如下VS Code Codex 插件 - 写入 C:\Users\YourUser\.codex\sessions\...\rollout-*.jsonl - PowerShell watcher 轮询这些 jsonl - 发现 task_complete - 提取短摘要 - 调用 codex_ntfy_notify.ps1 - ntfy.sh - 安卓手机收到通知这个方案的好处不依赖内置notify的超长参数直接监听结构化的task_complete事件不需要修改项目代码对 VS Code 插件版 Codex 更稳八、主 watcher 脚本创建C:\Users\YourUser\.codex\codex_task_complete_watch.ps1内容如下param( [string]$CodexHome $env:USERPROFILE\.codex, [int]$PollIntervalMs 1200 ) $sessionRoot Join-Path $CodexHome sessions $notifyScript Join-Path $CodexHome codex_ntfy_notify.ps1 $watchLog Join-Path $CodexHome codex_watch_log.txt $stateDir Join-Path $CodexHome tmp $seenPath Join-Path $stateDir codex_task_complete_seen.json if (-not (Test-Path -LiteralPath $stateDir)) { New-Item -ItemType Directory -Path $stateDir | Out-Null } if (-not (Test-Path -LiteralPath $seenPath)) { Set-Content -LiteralPath $seenPath -Value [] } $seenTurns () try { $seenTurns (Get-Content -LiteralPath $seenPath -Raw | ConvertFrom-Json) } catch {} $fileOffsets {} function Write-WatchLog($msg) { Add-Content -Path $watchLog -Value ([ (Get-Date).ToString(yyyy-MM-dd HH:mm:ss) ] $msg) } function Save-SeenTurns($items) { $json ConvertTo-Json -InputObject ($items | Sort-Object -Unique) -Compress [System.IO.File]::WriteAllText($seenPath, $json, [System.Text.UTF8Encoding]::new($false)) } Write-WatchLog Watcher started while ($true) { $files Get-ChildItem -LiteralPath $sessionRoot -Recurse -File -Filter *.jsonl -ErrorAction SilentlyContinue foreach ($file in $files) { if (-not $fileOffsets.ContainsKey($file.FullName)) { $fileOffsets[$file.FullName] 0L Write-WatchLog (Tracking new file: $file.FullName) } $stream [System.IO.File]::Open($file.FullName, Open, Read, ReadWrite) try { $offset [long]$fileOffsets[$file.FullName] if ($offset -gt $stream.Length) { $offset 0 } $stream.Seek($offset, [System.IO.SeekOrigin]::Begin) | Out-Null $reader New-Object System.IO.StreamReader($stream) while (-not $reader.EndOfStream) { $line $reader.ReadLine() try { $entry $line | ConvertFrom-Json -ErrorAction Stop if ($entry.type -eq event_msg -and $entry.payload.type -eq task_complete) { $turnId [string]$entry.payload.turn_id if ($seenTurns -contains $turnId) { continue } $summary [string]$entry.payload.last_agent_message $summary ($summary -replace \s, ).Trim() if ($summary.Length -gt 160) { $summary $summary.Substring(0, 160) ... } $payload { type task_complete source codex_session_watcher turn_id $turnId summary $summary detected_at (Get-Date).ToString(s) } | ConvertTo-Json -Compress $notifyScript $payload $seenTurns ($seenTurns $turnId) Save-SeenTurns $seenTurns Write-WatchLog (Notified turn_id $turnId) } } catch {} } $fileOffsets[$file.FullName] $stream.Position $reader.Dispose() } finally { $stream.Dispose() } } Start-Sleep -Milliseconds $PollIntervalMs }九、启动和停止脚本启动脚本C:\Users\YourUser\.codex\codex_task_complete_watch_start.ps1$watcher $env:USERPROFILE\.codex\codex_task_complete_watch.ps1 $pidPath $env:USERPROFILE\.codex\codex_task_complete_watch.pid $proc Start-Process -FilePath powershell.exe -ArgumentList ( -NoProfile, -ExecutionPolicy, Bypass, -File, $watcher ) -WindowStyle Hidden -PassThru Set-Content -LiteralPath $pidPath -Value $proc.Id Write-Output (Watcher started. PID $proc.Id)停止脚本C:\Users\YourUser\.codex\codex_task_complete_watch_stop.ps1$pidPath $env:USERPROFILE\.codex\codex_task_complete_watch.pid if (Test-Path -LiteralPath $pidPath) { $watchPid [int](Get-Content -LiteralPath $pidPath -Raw).Trim() $proc Get-Process -Id $watchPid -ErrorAction SilentlyContinue if ($proc) { Stop-Process -Id $watchPid } Remove-Item -LiteralPath $pidPath -ErrorAction SilentlyContinue }十、使用方式启动 watcher C:\Users\YourUser\.codex\codex_task_complete_watch_start.ps1停止 watcher C:\Users\YourUser\.codex\codex_task_complete_watch_stop.ps1十一、运行后会生成哪些文件运行后通常会看到这些文件通知日志C:\Users\YourUser\.codex\notify_log.txtwatcher 日志C:\Users\YourUser\.codex\codex_watch_log.txt已处理 turn_id 去重文件C:\Users\YourUser\.codex\tmp\codex_task_complete_seen.jsonwatcher PID 文件C:\Users\YourUser\.codex\codex_task_complete_watch.pid十二、最终效果我这边最终验证通过的点有watcher 能后台启动watcher 能识别task_completewatcher 能调用 ntfy 通知脚本安卓手机能收到通知notify_log.txt有SEND: OKcodex_watch_log.txt有Notified turn_id...codex_task_complete_seen.json能写入 turn id避免重复通知十三、结论如果你用的是 Windows VS Code 插件版 Codex又想在任务完成后收到手机通知那么最稳的做法是不要依赖内置 notify 去直接承接长参数事件而是监听 .codex\sessions 里的 task_complete 事件再转发给 ntfy。这样做的核心价值是绕开 Windowsos error 206不改项目代码兼容 VS Code 插件版 Codex实现简单可控性高