Skip to content

Simlock lease stays held after agent-device close when the CLI runs without --json #613

Closed

Description

@V3RON

What happened

After agent-device close and agent-device disconnect, the simlock lease behind the agent-device lease stays held. It is freed only when its TTL runs out (5 minutes in this setup), or by a manual release.

In a real-Mac test, the first run released its simlock lease within 15 ms of the close. Three later runs did not: each close finished ok on the agent-device side and was followed only by a lease.renew from the simlock front, with no lease.release.

What you expected

close ends the agent-device lease and simlock releases the simlock lease behind it straight away, whether or not the agent-device CLI runs with --json.

How to reproduce

  1. Start the simlock daemon with the agent-device front on, iOS on.
  2. agent-device connect proxy ... with an agent token, in a fresh AGENT_DEVICE_STATE_DIR.
  3. agent-device open <app> --platform ios (no --json).
  4. agent-device close (no --json), then agent-device disconnect.
  5. simlock lease list: the simlock lease is still held, until its TTL runs out.

The same steps with --json on close release the simlock lease.

Environment

simlock 0.3.0 (main at 7533c44), macOS, iOS only (Android off), agent-device 0.21.28 from npm.

Logs and evidence

  • Run 1's close (09:32:09) was followed by lease.release for its simlock lease.
  • The three later closes (09:38:33, 09:42:15, 09:42:19) were each followed only by a successful lease.renew; no agent-device request reached simlock after them.

Source: the simlock daemon log, events.jsonl and the agent-device session logs from that run.

Activity

  1. V3RON commented on Oct 16, 2026

    @V3RON
    ContributorAuthor

    Reproduction

    Failing test on branch bug/613-repro: "a close streamed in agent-device's own line shape releases the simlock lease" in src/agent-device/edge.test.ts.

    pnpm exec vitest run --project unit src/agent-device/edge.test.ts -t "streamed"

    It fails with the simlock lease still listed (expected [ '00000000000000000000000000000001' ] to deeply equal []). The two existing streamed tests pass.

    Root cause

    Defect.

    Without --json, the agent-device 0.21.28 CLI asks for progress streaming (meta.requestProgress: "command", agent-device src/cli.ts:843-861). The agent-device daemon then answers /rpc as NDJSON lines: {"type":"progress",...}, then {"type":"response","response":{jsonrpc,id,result:{ok,data:{endedLeases}}}} (agent-device packages/daemon/src/server/http/http-server.ts:662-667, packages/daemon-contracts/src/request-progress-protocol.ts:49).

    idsInFrame (src/agent-device/front/ended.ts:19) reads result.data.endedLeases and error.data.details.endedLeases only at the top of each line, never under response. So releaseEndedLeases (called at src/agent-device/front/front.ts:171) finds no ids and never releases the simlock lease.

    The sequence that leaves the simlock lease held:

    1. The agent-device daemon closes its agent-device session and ends the agent-device lease.
    2. The simlock front misses endedLeases.
    3. The renewal when the command's body ends (src/agent-device/front/renewals.ts) renews the simlock lease for a full TTL.
    4. The agent-device CLI reads the reply correctly and forgets the agent-device lease, so disconnect sends no release.

    The simlock lease is held until its TTL runs out. With --json the reply is plain JSON and is read correctly, which is why the first run released.

    The existing streamed test (src/agent-device/edge.test.ts:1273) uses a made-up line shape ({result:{data:{endedLeases}}}). The slow lane always passes --json (e2e/helpers/agent-device.ts:142,149). Neither catches this.

    Simplest fix

    In idsInFrame, when a line has type: "response", read the JSON-RPC answer from its response field. Make the fake's streamed lines and the test at edge.test.ts:1273 use agent-device's real shape.

    Alternatives rejected

    • Strip meta.requestProgress so replies are never streamed: the CLI would lose its progress output.
    • Have the agent-device CLI send a release after close: upstream removed that on purpose (feat(cli): release the proxy lease after close callstack/agent-device#3477), and simlock cannot fix it alone.
    • Scan every nested object for endedLeases: reads ids from places agent-device never puts them.

    Risk

    • A future agent-device reply shape could move the answer again; the test pins today's shape.
    • After the fix, the end-of-body renewal still runs once before the release lands. It is harmless, since the release follows.

    Side findings

    • releaseEndedLeases ends in .catch(() => undefined) (src/agent-device/front/ended.ts:57): a failed body read or lease.release is dropped with no log and no retry. Not confirmed as a leak; not filed.
  2. self-assigned this
    on Oct 16, 2026
  3. V3RON commented on Oct 16, 2026

    @V3RON
    ContributorAuthor

    Delivery run

    Merged: #615 (#613)

    Written by an agent.

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

Metadata

Metadata

Assignees

Labels

bug:readyAn agent may fix it.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions