Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

A session lost to a daemon takeover ("Replacing daemon … unreachable") answers the next request with AMBIGUOUS_MATCH when two simulators are booted

Đang mở
#3,674 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@thymikee đang làm issue này rồi.

Từ ngày 16/10/2026.

  • #3701 của @thymikee — đang mở

Đá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
Lĩnh vực
cli, devtools

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ả

bug ready-for-agent
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:

  1. A request that names a session the daemon does not hold should say so. close already answers SESSION_NOT_FOUND: No active session. snapshot instead falls through to resolveTargetDevice({}), 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).
  2. 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 main at 35c21b6, 2026-10-15; see "Where in the code")
  • Driven through the Node client: @e2e-dev/mobile 0.12.0 under e2e 0.20.0, which calls createAgentDeviceClient({ session: 'e2e-ios-0' }), one worker, default state dir ~/.agent-device, transport auto (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) and Perf iPhone 17
  • The engine is configured with mobile({ platform: 'ios', device: <E2E_DEVICE> }). It passes the device on boot, open, install and permission. Reads such as snapshot pass only { interactiveOnly } and rely on the session to hold the device. (@e2e-dev/mobile dist/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:

  1. Boot two iOS simulators, A and B.
  2. agent-device open <app> --session s --platform ios --device A, then agent-device snapshot --session s (works).
  3. Make the next client call find the daemon unreachable, for example kill -9 $(jq .pid ~/.agent-device/daemon.json).
  4. agent-device snapshot --session s. I expect Replacing daemon … unreachable and then AMBIGUOUS_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_MATCH after a 3.3 s device listing. The hint says to pass --udid or --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:

Why the session is gone:

  • src/daemon/session-store.ts L51: the live sessions are a Map in 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:

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.log appears to hold only the current daemon's output. I found no node crash 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 two doctor events 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_FAILED rather than AMBIGUOUS_MATCH. I did not sort the 12 other snapshot COMMAND_FAILED events of the day by cause.
Possible direction

Offered as options, not a design:

  1. In resolveSessionDevice (and resolveCommandDevice): when the request names a session and the daemon holds no such session, and the request has no device selector, return SESSION_NOT_FOUND with the session name and the hint to open again. That skips the 3 s listing too. Sessionless use (no --session, or an explicit selector) stays as it is.
  2. Say why in the takeover notice and diagnostics: pid alive or dead, which probe failed and how (ECONNREFUSED or timeout), how many sessions the replaced daemon held. Keep the old daemon.log (rotate it) instead of overwriting.
  3. Keep the binding. Either persist session name -> device id/platform in 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

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của callstack/agent-device

Tất cả issue của callstack/agent-device

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.