-
Notifications
You must be signed in to change notification settings - Fork 0
ZH Windows Service
推荐使用系统内置的任务计划程序 Task Scheduler,并以当前用户身份创建任务。
用普通用户打开 PowerShell:
npm install -g copilot-relay@latest
copilot-relay auth$node = (Get-Command node).Source
$main = Join-Path (npm root -g) "copilot-relay\dist\main.js"
$action = New-ScheduledTaskAction -Execute $node -Argument "`"$main`" start"
$trigger = New-ScheduledTaskTrigger -AtLogOn
$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-ExecutionTimeLimit ([TimeSpan]::Zero) `
-RestartCount 10 `
-RestartInterval (New-TimeSpan -Minutes 1) `
-MultipleInstances IgnoreNew `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries
Register-ScheduledTask `
-TaskName "copilot-relay" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-Description "Run copilot-relay for Claude Code" `
-Force
Start-ScheduledTask -TaskName "copilot-relay"注册完立刻做一次这个检查。它决定了 relay 是能一直跑,还是三天后无声无息地死掉:
(Get-ScheduledTask -TaskName "copilot-relay").Settings.ExecutionTimeLimit期望值是 PT0S。 如果显示 PT72H,说明设置没生效 —— PowerShell 的
[TimeSpan]::Zero 并不总能序列化成 PT0S,而且失败时没有任何提示。这种情况下直接改
XML:
$task = Get-ScheduledTask -TaskName "copilot-relay"
$task.Settings.ExecutionTimeLimit = "PT0S"
Set-ScheduledTask -InputObject $task
(Get-ScheduledTask -TaskName "copilot-relay").Settings.ExecutionTimeLimit # PT0S对一个长期运行的服务来说,任务计划程序的默认值基本都是错的。
-ExecutionTimeLimit ([TimeSpan]::Zero) 是坑最深的一个。默认值是 PT72H,也就是
3 天 —— 到时间后任务计划程序会终止这个任务。一个完全健康的 relay 会在 72 小时后
死掉,而且因为看起来像正常结束,任何地方都不会报错。零表示没有上限。务必按上面的方法
确认它生效了。
-DontStopIfGoingOnBatteries 和 -AllowStartIfOnBatteries 在笔记本上都必须加,
因为两个底层默认值都跟你作对:StopIfGoingOnBatteries 默认为 true(一拔电源
Windows 就停掉任务),DisallowStartIfOnBatteries 也默认为 true(用电池时根本不启动)。
注意 -DisallowStartIfOnBatteries 不是 cmdlet 参数 —— 那是底层 XML 属性的名字。把它
传给 New-ScheduledTaskSettingsSet 没有任何作用,应该用 -AllowStartIfOnBatteries。
直接执行 node.exe 而不是套一层 powershell.exe。npm 在 Windows 上全局安装会生成
.cmd、.ps1 和 shell 三种垫片,(Get-Command copilot-relay).Source 指向的就是其中之
一。垫片会让 cmd.exe/powershell.exe 成为父进程、node.exe 成为子进程,于是任务计划
程序监督的是外壳而不是 relay 本身。中间那层 shell 退出后,进程树终止并不可靠,
Stop-ScheduledTask 可能杀不掉 relay。直接调 node dist\main.js start,relay 就是任务
自己的进程,启停一一对应。
-RestartCount 10、间隔 1 分钟 限制重试次数。这些设置属于 RestartOnFailure,
只在任务失败时生效 —— 进程以 0 退出算正常结束,所以正常的 copilot-relay stop
永远不会被它们重新拉起。relay 启动时会校验上游 Copilot 访问,失败则 exit(1);登录瞬间
网络往往还没就绪,第一次失败是正常的。十分钟内重试十次,既能覆盖慢速网络,又不会对着
真正的配置错误无限重试。
-MultipleInstances IgnoreNew 本来就是默认值,这里显式写出来是为了表明意图:绝不允许
第二个实例和第一个并存。
三层检查。每一层证明的东西都比上一层更多,而前两层在一个根本无法处理任何请求的 relay 上也会通过。
Get-ScheduledTask -TaskName "copilot-relay" | Select-Object State
Get-ScheduledTaskInfo -TaskName "copilot-relay" |
Select-Object LastRunTime, LastTaskResult, NumberOfMissedRuns
Invoke-RestMethod http://127.0.0.1:4142/healthz期望 State: Running、LastTaskResult: 267009(表示正在运行,不是错误)、以及
ok : True。这个接口是静态处理器:它只能证明有个端口在监听,别的什么都证明不了,完全
不会访问 GitHub Copilot。
(Invoke-RestMethod http://127.0.0.1:4142/v1/models).data.id期望看到你配置的模型,例如 gpt-5.6-sol[1m] 和 claude-opus-5。结果直接来自配置,
同样不访问上游。一个 Copilot 令牌一小时前就过期的 relay,前两层照样通过。
$body = @{
model = "gpt-5.6-sol"
max_tokens = 16
messages = @(@{ role = "user"; content = "Reply with the single word: ok" })
} | ConvertTo-Json -Depth 5
Invoke-RestMethod -Method Post http://127.0.0.1:4142/v1/messages `
-ContentType "application/json" `
-Headers @{ "anthropic-version" = "2023-06-01" } `
-Body $body返回内容加上非零 usage,说明整条链路都是通的:配置、令牌刷新、Copilot 调用,以及转换
回 Claude 格式。只有这一层能证明 relay 真的可以为 Claude Code 服务。 它会消耗少量
token。
如果前两层通过而第三层失败,问题在鉴权或上游,不在任务本身 —— 执行
copilot-relay auth 并查看当天日志。
$today = Get-Date -Format "yyyy-MM-dd"
Get-Content "$env:USERPROFILE\.copilot-relay\logs\copilot-relay.$today.log" -Tail 80 -Wait日志按天轮转,文件名带本地日期。要搜索所有保留的日期:
Select-String -Path "$env:USERPROFILE\.copilot-relay\logs\copilot-relay.*.log" `
-Pattern "Startup preflight failed"现在停止,下次登录时再启动:
Stop-ScheduledTask -TaskName "copilot-relay"现在停止,并且登录时也不再启动:
Stop-ScheduledTask -TaskName "copilot-relay"
Disable-ScheduledTask -TaskName "copilot-relay"重新启用:
Enable-ScheduledTask -TaskName "copilot-relay"
Start-ScheduledTask -TaskName "copilot-relay"copilot-relay stop 会直接找到并终止 relay 进程。它以 0 退出,任务计划程序视为正常
结束而非失败,所以重启策略不会触发,它会一直保持停止,直到下次登录。
不论用哪种守护方式,确认 relay 确实没了:
copilot-relay stop
Get-NetTCPConnection -LocalPort 4142 -State Listen -ErrorAction SilentlyContinue第二条命令应该没有任何输出。
Stop-ScheduledTask -TaskName "copilot-relay"
Unregister-ScheduledTask -TaskName "copilot-relay" -Confirm:$falserelay 大约每 3 天死一次。 -ExecutionTimeLimit 没生效。特征是 LastTaskResult =
267014(SCHED_S_TASK_TERMINATED)且运行时长接近 72 小时 —— 这个码表示任务被终止,
而不是崩溃。检查并修复:
(Get-ScheduledTask -TaskName "copilot-relay").Settings.ExecutionTimeLimitPT72H 是默认值,也正是问题所在;你要的是 PT0S。参见
务必确认执行时间上限真的生效了。
拔电源就停,或者用电池时根本起不来。 两个电池相关的默认值都跟你作对 ——
StopIfGoingOnBatteries 和 DisallowStartIfOnBatteries 默认都是 true。需要同时加
-DontStopIfGoingOnBatteries 和 -AllowStartIfOnBatteries。
Stop-ScheduledTask 之后 relay 还在跑。 任务套了垫片或 powershell.exe。用上面的
node.exe 方式重新注册。
LastTaskResult 是 1。 启动前校验失败:鉴权过期或没有网络。看当天日志,然后执行
copilot-relay auth。
LastTaskResult 是 267009。 不是错误 —— SCHED_S_TASK_RUNNING,任务正在运行。
LastTaskResult 是 267011。 SCHED_S_TASK_NOT_SCHEDULED —— 按计划运行所需的某个
属性缺失,重新注册任务即可。
任务注册了但从来不启动。 -AtLogOn 在登录时触发;如果你是在已经登录的会话里注册的,
先手动执行一次 Start-ScheduledTask。
relay 在跑但 Claude Code 不走它。 服务注册没问题;检查
%USERPROFILE%\.claude\settings.json 里的 ANTHROPIC_BASE_URL。当 claudeSetup: true
时,relay 启动时会自己管理这个值。
使用「当前用户」的计划任务。除非你明确管理独立的 home 目录和 token 缓存,否则不要用
SYSTEM 运行 —— SYSTEM 有自己的用户配置目录,看不到你以自己身份执行的
copilot-relay auth。