Skip to content

native-devtools sockets: multi-server contention (attach-to-every-sim + latest-bind-wins + unlink-on-stop) #775

Description

@filip131311

Summary

When more than one argent tool-server runs on the same host (a second dev
instance, a stale/orphaned instance, or another agent session's server), the
native-devtools injection sockets become a three-way hazard:

  1. Attach-to-every-sim: each tool-server's simulator watcher initializes a
    NativeDevtools service — and binds /tmp/argent-nd-<UDID>.sock — for
    every booted simulator, even devices that server will never drive. A
    session doing Android-only work through a dev tool-server knocked out an
    unrelated iOS session's devtools on a different simulator.
  2. Latest-bind-wins: the blueprint unlinks any existing socket file before
    binding (fs.unlinkSync(socketPath) in native-devtools.ts, "remove stale
    socket file from a crashed previous run"). A live server's socket is
    indistinguishable from a crashed one, so every server start silently steals
    all future app connections; the losing server keeps its established
    connections but every relaunched app connects to the thief.
  3. Unlink-on-stop kills survivors: when the last-binding server stops, its
    cleanup removes the socket file — the surviving servers' listeners are gone
    from the filesystem, so no app can connect to anyone until something
    rebinds.

Observed failure signatures (all reproduced live, 2026-08-11)

  • flow launch step: could not connect to native devtools for <bundleId>… —
    the app connected to another server's socket; the flow's server waits out its
    connect window in vain. Reproduced on demand: the failure windows correlate
    1:1 with another server instance's start timestamps (22:07, 22:10:57,
    22:15:51, 22:16:55 across two independent sessions).
  • ViewInspector RPC timed out: <method> mid-flow — when the steal happens
    between a flow's app relaunch and its first RPC.
  • With three sessions (two scoped experiments + one unrelated Android A/B loop
    restarting its dev server per run), no session could keep a stable devtools
    connection on any simulator.

Also worth noting operationally: there is no in-band way to identify or
negotiate with the process holding the socket — lsof + process-tree forensics
was the only way to find the owner.

Sketch of remedies (not prescriptive)

  • Scope: only claim devtools sockets for devices a server is actually asked to
    drive (lazy claim on first tool call for that UDID), or honor an explicit
    device allowlist. (An ARGENT_DEVTOOLS_ONLY_UDID env filter prototype made
    two concurrent sessions coexist cleanly during this investigation.)
  • Ownership: before unlinking an existing socket, probe it — a live listener
    answers; only unlink dead sockets. On shutdown, only unlink a socket the
    server still owns (compare inode/identity).
  • Diagnosability: embed owner pid + port in a sidecar file next to the socket
    so the "stale or duplicate argent server" error can name the actual owner.

Environment

argent 0.20.0 (installed) + worktree dev builds of the tool-server, macOS,
iOS 26.4/26.5 simulators, Expensify NewDot test app. Full timeline and process
trees available on request.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    b:minorBug that either affects a small cohort of users and affects a minor feature.bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions