A session lost to a daemon takeover ("Replacing daemon … unreachable") answers the next request with AMBIGUOUS_MATCH when two simulators are booted
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 25/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- ios, nodejs, typescript
Hướng nghiên cứu
Start in src/daemon/snapshot-session.ts, where resolveSessionDevice falls back to resolveTargetDevice({}) when the named session is missing, then check resolveCommandDevice in src/daemon/session-device-resolution.ts. Compare with session-close.ts, which already returns SESSION_NOT_FOUND. Reproduce with two booted simulators and a session the daemon does not hold. Done means a named missing session with no device selector returns SESSION_NOT_FOUND, and the daemon-takeover notice in daemon-client-lifecycle.ts gains a reason. The issue leaves the fix direction open, so confirm it with a maintainer first.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
Twice today an automated iOS run lost its session in the middle of a test. The client printed Replacing daemon (pid N, v0.21.25) in ~/.agent-device: unreachable, up to three times in one test. The replacement daemon held no sessions. The next snapshot for that session then failed after 3.3 s with AMBIGUOUS_MATCH: 2 devices match this request equally, because two iOS simulators were booted. The request named a session, but the daemon had none, so it ran device selection with no constraints.
I see two problems, one small and one larger:
- A request that names a session the daemon does not hold should say so.
closealready answersSESSION_NOT_FOUND: No active session.snapshotinstead falls through toresolveTargetDevice({}), so the answer depends on how many devices are booted. With one device booted you get an app-session error (see #1596). With two you get an error that tells you to pick a device, which hides the real cause and sends the caller after the wrong fix. This reproduces with one command (below). - A takeover that drops every session gives no reason. The notice says only
unreachable. It does not say if the pid was dead, if a probe timed out, or how many sessions it threw away. The replaced daemon's log is gone afterward. I could not tell if the daemon died or if it missed its probes (see "What I could not establish").
I know #1596 treats SESSION_NOT_FOUND after a replace as expected, since a new daemon has no sessions. I agree about the error. My point is the wrong error with two devices, and that the replace itself looks avoidable or at least explainable.
Environment
- agent-device 0.21.25 (the same code is on
mainat 35c21b6, 2026-10-15; see "Where in the code") - Driven through the Node client:
@e2e-dev/mobile0.12.0 undere2e0.20.0, which callscreateAgentDeviceClient({ session: 'e2e-ios-0' }), one worker, default state dir~/.agent-device, transportauto(the daemon registered a socket port, no HTTP port) - macOS 27.0.1 (26A434), arm64, Node v26.11.1, Xcode 27.0 (27A266a)
- Two iOS 27.0 simulators booted:
TinyConcepts E2E(the one the run drives) andPerf iPhone 17 - The engine is configured with
mobile({ platform: 'ios', device: <E2E_DEVICE> }). It passes the device onboot,open,installandpermission. Reads such assnapshotpass only{ interactiveOnly }and rely on the session to hold the device. (@e2e-dev/mobiledist/surface.js:selection()at line 296,capture()at line 631.)
What happened
Times are UTC from ~/.agent-device/sessions/e2e-ios-0/events.ndjson. The run printed Replacing daemon (pid 52888 / 53707 / 55890, v0.21.25) in /Users/…/.agent-device: unreachable three times during its first test, which then failed with snapshot failed: Failed to communicate with daemon. The other two tests of that run passed.
13:08:30.9 boot TinyConcepts E2E ok test 1 starts
13:08:42.5 open com.anyconcept.app ok 11 s after the boot
13:08:46 snapshot x4 ok 5.0 s, 1.3 s, 1.3 s, 1.3 s
13:08:59.4 last good snapshot finishes
13:09:05.5 snapshot f40644a2477f0603 starts 6.1 s later
13:09:06.7 close 7479a0272ecdf229 starts test teardown
13:09:08.86 snapshot f40644… -> AMBIGUOUS_MATCH 3356 ms (resolve_target_device 3329 ms)
13:09:08.86 close 7479a0… -> SESSION_NOT_FOUND "No active session"
The daemon wrote the request log of the failing snapshot (paths shortened):
{"ts":"2026-10-15T13:09:05.504Z","level":"info","phase":"request_start","session":"e2e-ios-0","requestId":"f40644a2477f0603","command":"snapshot","data":{"publicSession":"e2e-ios-0","effectiveSession":"e2e-ios-0","command":"snapshot"}}
{"ts":"2026-10-15T13:09:08.849Z","level":"error","phase":"resolve_target_device","session":"e2e-ios-0","requestId":"f40644a2477f0603","command":"snapshot","durationMs":3329,"data":{"cacheHit":false,"error":"2 devices match this request equally; select one explicitly."}}
{"ts":"2026-10-15T13:09:08.860Z","level":"error","phase":"request_failed","session":"e2e-ios-0","requestId":"f40644a2477f0603","command":"snapshot","data":{"code":"AMBIGUOUS_MATCH","message":"2 devices match this request equally; select one explicitly."}}
An earlier run, 13:06 to 13:08, shows the loss more sharply. In its second test, open finished OK at 13:06:34.2. At 13:06:35.4, 1.2 s later, a snapshot for the same session failed with AMBIGUOUS_MATCH (3471 ms), and again twice more at 13:06:38.8 and 13:06:40.7. A session that existed 1.2 s earlier was gone. That run failed all three tests: two with AMBIGUOUS_MATCH, one with Failed to communicate with daemon.
Counts for the day, from the same events file: 7 AMBIGUOUS_MATCH snapshot failures, all in those two runs. Both simulators were booted for 10 run starts (Perf iPhone 17 booted at 12:23Z); 2 of the 10 hit this. Earlier runs likely had only one booted device, so they could not hit the ambiguity.
Repro
Only the second half (wrong error for a missing session) reproduces on demand. In a throwaway state dir, with two iOS simulators booted:
export AGENT_DEVICE_STATE_DIR=$(mktemp -d)
agent-device snapshot --session never-opened
# Error (AMBIGUOUS_MATCH): 2 devices match this request equally; select one explicitly.
# Hint: Select the intended device explicitly, for example --udid <id> or --device "Perf iPhone 17". ...
# (3.6 s)
agent-device daemon stop
The session never-opened does not exist, which is the state a replacement daemon leaves a real session in. Expected: SESSION_NOT_FOUND, as close returns.
To see the whole chain you need a daemon takeover in the middle of a session:
- Boot two iOS simulators, A and B.
agent-device open <app> --session s --platform ios --device A, thenagent-device snapshot --session s(works).- Make the next client call find the daemon unreachable, for example
kill -9 $(jq .pid ~/.agent-device/daemon.json). agent-device snapshot --session s. I expectReplacing daemon … unreachableand thenAMBIGUOUS_MATCH, not tried by hand yet (I did not want to disturb the daemon the other runs share). The e2e runs above are the real occurrence.
Expected and actual
- Expected: a request that names a session the daemon does not hold fails with
SESSION_NOT_FOUND(and says the daemon holds no such session), whatever number of devices is booted. Better: a replaced daemon keeps the session's device, so a configured device is not forgotten. - Actual:
AMBIGUOUS_MATCHafter a 3.3 s device listing. The hint says to pass--udidor--device, which does not describe the problem. The caller learns nothing about the lost session.
Where in the code (v0.21.25)
Why the ambiguity:
src/daemon/snapshot-session.tsL9–L19:resolveSessionDevicedoessession?.device ?? (await resolveTargetDevice(flags ?? {})). A missing session takes the second branch with the request's flags, which carry no selector.snapshotreaches it fromsrc/daemon/snapshot-runtime-binding.tsL153. The same fallback is inwait,alertandsettings.src/daemon/session-device-resolution.tsL34–L49:resolveCommandDevicedoes the same for commands that use it.closehas the right answer already:src/daemon/session-lifecycle/internal/session-close.tsL428.
Why the session is gone:
src/daemon/session-store.tsL51: the live sessions are aMapin the daemon process. Nothing on disk maps a session name to a device, so a new daemon starts empty. (~/.agent-device/sessions/<name>/holds only logs.)
Why the daemon was replaced:
src/daemon-client/daemon-launch-spec.tsL138–L156:resolveDaemonTakeoverreturns{ kind: 'replace', reason: 'unreachable' }whenonClientTransport()is false.src/daemon-client/daemon-client-lifecycle.tsL280–L300:canReachReusableDaemonmakes one probe, then up to three more 200 ms apart, and only while the pid is alive. A dead pid returnsfalseat once. The doc comment above it (L273–L279) already says that replacing a daemon "ends every session the daemon holds".…/daemon-client-lifecycle.tsL193–L220 (readReusableLocalDaemon) prints the notice (L354–L360) and callsretireDaemonForTakeover. The notice carries only the pid, version and the wordunreachable.
main (35c21b6) has the same fallback in packages/daemon/src/core/snapshot-session.ts L9–L19 and the same unreachable path, so this is not fixed upstream yet.
What I could not establish
- Why the daemon was unreachable. Four probes in 0.6 s all failed, or the pid was dead. A single stall in my own client process should not fail all four probes, because the later probes run after the stall ends, so I think the daemon really had no listener, but I cannot prove it. I have no daemon log for the replaced pids:
~/.agent-device/daemon.logappears to hold only the current daemon's output. I found nonodecrash report and no unified-log line for the pid around the time. - Whether another agent-device client shared the state dir. The only other session dir (
cwd_…_default) holds twodoctorevents from 10:46Z. A client that opens no session would leave no trace. - With one simulator booted the same loss presumably shows up as
SESSION_NOT_FOUND/COMMAND_FAILEDrather thanAMBIGUOUS_MATCH. I did not sort the 12 othersnapshot COMMAND_FAILEDevents of the day by cause.
Possible direction
Offered as options, not a design:
- In
resolveSessionDevice(andresolveCommandDevice): when the request names a session and the daemon holds no such session, and the request has no device selector, returnSESSION_NOT_FOUNDwith the session name and the hint toopenagain. That skips the 3 s listing too. Sessionless use (no--session, or an explicit selector) stays as it is. - Say why in the takeover notice and diagnostics: pid alive or dead, which probe failed and how (
ECONNREFUSEDor timeout), how many sessions the replaced daemon held. Keep the olddaemon.log(rotate it) instead of overwriting. - Keep the binding. Either persist
session name -> device id/platformin the state dir so a replacement daemon can re-pin it, or, as #2458 suggests, refuse to replace a daemon whose pid is alive while a session is open and fail with a hint to stop it on purpose.
Related
#1596 (replace then SESSION_NOT_FOUND, closed as expected), #2458 (a replace drops a live session, closed), #3177 (client timeout recovery ends sibling sessions, closed).
Evidence files (event window 13:08:20–13:09:12Z, the two request logs, both run outputs) can be attached on request.
- Ngôn ngữ chính
- TypeScript
- Star
- 5k
- Fork
- 334
- Merge trung bình
- 12 giờ 2 phút
- Pull request đã merge (30 ngày)
- 678
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của callstack/agent-device
-
`devices` reports a phantom Android emulator named after the emulator's crashdata INFO bannerCó thể đã có người làm @airowe đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
callstack/agent-device#3716 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
callstack/agent-device#1869 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
backlog refactor
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 8/100
callstack/agent-device#3759 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
backlog enhancement needs-triage
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 8/100
callstack/agent-device#3758 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement ready-for-agent
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 15/100
callstack/agent-device#3757 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của callstack/agent-device
Issue tương tự
-
Endpoint links in the tag operations list are below the 24px target sizeCó thể đã có người làm @AdzerKI đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-driven-qa bug Minor
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 80/100
linagora/twake-tasks#435 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug domain: database priority: p1 Stellar Wave
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
StellarState/SS-backend#632 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
A2A audit timestamps end in +00:00Z (invalid ISO 8601)Có thể đã có người làm @O1sumitkumar đã nhận hôm nay. Đang mởbug good first issue help wanted python
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
`cf complete zsh >> .zshrc` fails on vanilla zshCó thể làm lại được Pull request cho issue này đã bị đóng mà không được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
cloudflare/cf#340 ·
Maintainer thường phản hồi trong vòng 1 ngày