You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Simlock lease stays held after agent-device close when the CLI runs without --json #613
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
Start the simlock daemon with the agent-device front on, iOS on.
agent-device connect proxy ... with an agent token, in a fresh AGENT_DEVICE_STATE_DIR.
agent-device open <app> --platform ios (no --json).
agent-device close (no --json), then agent-device disconnect.
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.
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:
The agent-device daemon closes its agent-device session and ends the agent-device lease.
The simlock front misses endedLeases.
The renewal when the command's body ends (src/agent-device/front/renewals.ts) renews the simlock lease for a full TTL.
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.
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.
Activity
V3RON commentedon Oct 16, 2026
Reproduction
Failing test on branch
bug/613-repro: "a close streamed in agent-device's own line shape releases the simlock lease" insrc/agent-device/edge.test.ts.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-devicesrc/cli.ts:843-861). The agent-device daemon then answers/rpcas NDJSON lines:{"type":"progress",...}, then{"type":"response","response":{jsonrpc,id,result:{ok,data:{endedLeases}}}}(agent-devicepackages/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) readsresult.data.endedLeasesanderror.data.details.endedLeasesonly at the top of each line, never underresponse. SoreleaseEndedLeases(called atsrc/agent-device/front/front.ts:171) finds no ids and never releases the simlock lease.The sequence that leaves the simlock lease held:
endedLeases.src/agent-device/front/renewals.ts) renews the simlock lease for a full TTL.disconnectsends no release.The simlock lease is held until its TTL runs out. With
--jsonthe 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 hastype: "response", read the JSON-RPC answer from itsresponsefield. Make the fake's streamed lines and the test atedge.test.ts:1273use agent-device's real shape.Alternatives rejected
meta.requestProgressso replies are never streamed: the CLI would lose its progress output.close: upstream removed that on purpose (feat(cli): release the proxy lease after close callstack/agent-device#3477), and simlock cannot fix it alone.endedLeases: reads ids from places agent-device never puts them.Risk
Side findings
releaseEndedLeasesends in.catch(() => undefined)(src/agent-device/front/ended.ts:57): a failed body read orlease.releaseis dropped with no log and no retry. Not confirmed as a leak; not filed.fix(agent-device): read endedLeases from a streamed type:response line (
test(agent-device): streamed fixtures use agent-device's real line sh…
V3RON commentedon Oct 16, 2026
Delivery run
Merged: #615 (#613)
Written by an agent.