Daemon startup: wait for a live daemon instead of a fixed 15 s deadline on slow or overloaded hosts
维护者通常 1 天内回复
已经有一个关联 PR 被合并了。
- #3791 来自 @thymikee —— 已合并
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 35/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- node.js, typescript
调研方向
Start with startLocalDaemon and waitForDaemonStartup in src/daemon-client/daemon-client-lifecycle.ts; trace how startup results, timeouts, and daemon logs are handled. Then read the existing lifecycle and startup-race tests and the daemon environment-variable documentation. Done when the completion conditions pass, including typed failure reasons, configurable timing, progress reporting, and updated help or docs.
由索引模型根据 Issue 内容生成。
描述
Purpose
On a slow or overloaded host, the CLI kills a healthy daemon that is still starting. The result is daemon_startup_failed, and the run fails before any work starts. This happens on any CI that can clog: shared macOS runners, small Linux containers, Windows runners, and nested virtualization. It also happens on a developer machine under load. A slow host must make startup slower, not fail it.
#3342 records this failure class (more than 12 instances on unrelated diffs since 2026-10-08; re-runs at the same head pass). This issue is the fix contract for that class.
Current behavior (origin/main)
startLocalDaemon in src/daemon-client/daemon-client-lifecycle.ts:
- It sets one wall-clock deadline:
DAEMON_STARTUP_TIMEOUT_MS = 15_000(line 83, used at line 480). waitForDaemonStartup(line 818) polls until the daemon publishes a reachabledaemon.json, or until the process exits, or until the deadline.- At the deadline, the client retires the daemon, even when the process is alive. A
timeoutresult never retries (retryis true only forearly_exit), soDAEMON_STARTUP_ATTEMPTS = 2does not help. Every report showsstartupAttempts: 1. - No setting can change the 15 s budget.
Evidence from run 39019020372 (PR #3760, 2026-10-17):
- The step
pnpm clean:daemontook 70 s on that runner, which shows that the host was overloaded. - The CLI spawned daemon pid 6849 at 09:30:03. The client killed it 15 s later (
cleanupResults: retired / forced / exited). daemon.logwas empty (0 bytes), so the daemon did not reach its first log line. The process was alive but did not get enough CPU.- The same signature occurred on
refactor/host-environment-root(run 39008970059) andfix/provider-not-registered(run 38993655494), which do not contain that PR's commits.
Required behavior
-
Wait for a live daemon. While the spawned daemon process is alive and has not exited, the client continues to wait for registration. A slow start is not a failure.
-
Fail fast on a real failure. If the daemon process exits before it registers, the client reports the failure immediately, as it does now (
early_exit,unproven). -
Keep a hard ceiling. A daemon that stays alive but never registers must not hang the client. The client stops at a ceiling. The default ceiling must be large enough for an overloaded CI host (proposal: 120 s). Measure the slowest successful startup in CI logs before you choose the number.
-
Show progress. The daemon writes startup phase markers (for example
spawned,modules-loaded,lock-acquired,listening,registered) with timestamps, to the place the client already reads (daemon.logtail or the startup metadata). The client can then tell "slow but progressing" from "stuck". If a marker shows progress, the client keeps waiting up to the ceiling. If no marker appears for a long time, the client can stop earlier than the ceiling. Put the stall window next to the ceiling, as one constant. -
Make the budget configurable. An environment variable sets the ceiling, for example
AGENT_DEVICE_DAEMON_STARTUP_TIMEOUT_MS. It applies to the CLI, MCP, and the Node client, because all three usestartLocalDaemon(viaensureDaemon(), #3765 and #3766). Document it with the other daemon environment variables. Do not add CI auto-detection. Rule 1 makes the default safe on every host. -
Use typed failure reasons.
details.kindstaysdaemon_startup_failed. A newdetails.reasontells the caller what happened:startup-exited: the process exited before it registered.startup-stalled: the process is alive, but no progress marker appeared within the stall window.startup-ceiling: the process made progress, but did not register before the ceiling.
Each reason has its own hint. For
startup-stalledandstartup-ceiling, the hint says that the host can be overloaded, and names the environment variable. Callers branch ondetails.reason, never on the message. -
Record timings. The failure details include the time of each phase marker that the client saw, and
startupTimeoutMsshows the effective ceiling.
Example failure payload:
{
"code": "COMMAND_FAILED",
"message": "Failed to start daemon",
"details": {
"kind": "daemon_startup_failed",
"reason": "startup-stalled",
"startupTimeoutMs": 120000,
"phases": { "spawned": 0, "modules-loaded": 41250 },
"hint": "The daemon is alive but made no startup progress for 60 s. The host can be overloaded. Retry, or raise AGENT_DEVICE_DAEMON_STARTUP_TIMEOUT_MS."
}
}
Completion conditions
- A test starts a daemon that is alive but slow (for example a test-only startup delay of 20 s, longer than the old 15 s budget). Startup succeeds, and the client does not kill the daemon.
- A test starts a daemon that exits during startup. The client reports
startup-exitedwithin 1 s after the exit. - A test starts a daemon that stays alive and writes no phase marker. The client reports
startup-stalledat the stall window, not at the ceiling. - A test sets the environment variable to a small value and sees
startup-ceilingwith the newstartupTimeoutMs. - The existing lifecycle and startup-race tests pass without change in meaning.
- The
daemon-clienthelp or user docs name the environment variable and the three reasons. - After the change merges, #3342 gets a
Current state:comment. Then the iOS Smoke preflight step is observed for one week. The issue closes if nodaemon_startup_failedwith reasonstartup-stalledorstartup-ceilingappears below the new ceiling.
Out of scope
- Re-running CI lanes to hide the failure.
- The slow
pnpm clean:daemonstep in.github/workflows/ios.yml(70 s on an overloaded runner). That is a separate CI cost. Report it in #3342 if it repeats.
Dependencies
- #3342: failure-class log and evidence.
- #3765 and #3766 (merged):
ensureDaemon()and attach mode. They callstartLocalDaemon, so the change applies to all clients.
- 主要语言
- TypeScript
- 星标
- 5k
- 派生
- 334
- 平均合并
- 12 小时 14 分钟
- 30 天内合并 PR
- 713
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
callstack/agent-device 的其他 Issue
-
bug
难度 2/5 1-3 小时 新手友好度 83/100
callstack/agent-device#3792 · 1 条评论 ·
维护者通常 1 天内回复
-
macOS helper audio-probe always reports -90 dBFS: AVAudioPCMBuffer frameLength set after the copy未关闭bug ready-for-agent
难度 2/5 1-3 小时 新手友好度 88/100
callstack/agent-device#3771 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 68/100
callstack/agent-device#1869 ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 55/100
callstack/agent-device#3801 ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 49/100
callstack/agent-device#3800 ·
维护者通常 1 天内回复
查看 callstack/agent-device 的全部 Issue
相似的 Issue
-
Unsampled pageload span is profiled when profileLifecycle is 'trace' (browser UI profiling)可能已有人在做 @Jxxunnn 今天认领。 未关闭Browser Waiting for: Product Owner
难度 2/5 1-3 小时 新手友好度 82/100
getsentry/sentry-javascript#25497 · 1 条评论 ·
维护者通常 1 天内回复
-
e2e-failure ready-to-code
难度 2/5 1-3 小时 新手友好度 77/100
redhat-developer/rhdh-plugin-export-overlays#4438 · 1 条评论 ·
维护者通常 1 天内回复
-
resource: destroying a resource calls AbortController.abort() without a reason, creating an AbortError with a stack trace each time可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭area: core
难度 2/5 1-3 小时 新手友好度 75/100
angular/angular#71422 · 1 条评论 ·
维护者通常 1 天内回复
-
bug status: needs triage
难度 2/5 1-3 小时 新手友好度 72/100
facebook/docusaurus#12739 · 1 条评论 ·
维护者通常 1 天内回复
-
factory-active task-identify-harness-labels-done task-identify-issue-type-done
难度 1/5 1-3 小时 新手友好度 68/100
维护者通常 1 天内回复