Repository navigation
Description
Request: #1100
Problem
In the reporter's words: "exec runs its command on the worker. So adb reverse tcp:5173 tcp:5173 or adb forward from a remote agent connects the device to port 5173 on the worker, not on the agent's machine. The device can't reach a dev server or a candidate build served on the caller's machine. Every device test of a local build has to move to the worker, which also competes with the timing measurements we run there."
Who it is for
A coding agent or CI job on a machine other than the worker, holding a lease on an Android emulator or an enrolled Android phone through a gateway or a lone worker's HTTP API, that serves its app under test locally (a Vite dev server, a mock API, a local web build) or wants to reach a port on the device (a debugger, an app's own server).
Outcome
- On the caller's machine,
simlock tunnel reverse <device-port>[:<caller-port>]makes the leased device'slocalhost:<device-port>reach the caller'slocalhost:<caller-port>, andforward <device-port>[:<caller-port>]makes the caller'slocalhost:<caller-port>reach the device's<device-port>. One command opens several; the caller port defaults to the device port. - It works through a gateway and against a lone worker, over the same HTTP address and agent token the caller already uses. No new inbound port opens anywhere.
- The tunnels live while the command runs. Stopping it, or losing its connection, closes them and removes what they set up on the device; the lease is untouched. The lease ending stops the command.
- Only the lease's holder (and an operator) can open a tunnel on it. The caller side only ever touches loopback.
- It is off unless an operator turns on
capabilities.tunnel.enabledon the gateway and the workers. - Android emulators and enrolled Android phones are covered; any other lease is refused.
- After this feature, no
adb reverserule on an Android device, and noadb forwardrule the adb server holds for that device, survives into the next lease or a worker restart.
How it works for the user
flowchart TD
A[Agent on machine B holds an Android lease] --> B[simlock tunnel reverse 5173]
B --> C{tunnel capability on, lease Android, port free, under 8 tunnels?}
C -->|no| E[error line, exit non-zero, nothing left open]
C -->|yes| D[Tunnels open; command stays in the foreground]
D --> F[Device opens localhost:5173]
F --> G{caller listening on 5173?}
G -->|yes| H[bytes flow both ways]
G -->|no| I[device connection refused; one warning line, at most every 10 s]
D --> J{Ctrl-C, lost connection, or lease ends}
J --> K[tunnels closed, device rules removed]
Before, from machine B:
$ curl -X POST -H "Authorization: Bearer $T" .../v1/leases/w1.3f9c/exec -d '{"tool":"adb","args":["reverse","tcp:5173","tcp:5173"]}'
# the device's localhost:5173 now reaches port 5173 on the worker, where nothing runs
After:
$ export SIMLOCK_URL=https://gateway.example SIMLOCK_TOKEN=sk_agent_…
$ simlock tunnel reverse 5173 forward 9229
reverse device:5173 -> localhost:5173 (lease w1.3f9c)
forward localhost:9229 -> device:9229 (lease w1.3f9c)
reverse 5173: nothing listening on localhost:5173 # on stderr, if the dev server is down
^C
$ simlock tunnel reverse 8081 --lease w1.7a2e # an iOS simulator lease, exit 1
{"error":{"code":"TUNNEL_UNSUPPORTED","message":"Lease w1.7a2e: the lease is on an iOS simulator; tunnels need an Android emulator or phone"}}
$ simlock tunnel reverse 8081 # 8081 already reversed on the device, exit 1
{"error":{"code":"TUNNEL_PORT_IN_USE","message":"Lease w1.3f9c: reverse 8081: the device port is already reversed; remove that rule first"}}
Examples
| Tunnel argument | Today | After |
|---|---|---|
reverse 5173 |
no command | device 5173 → caller 5173 |
reverse 8081:3000 |
no command | device 8081 → caller 3000 |
forward 9229 |
no command | caller 9229 → device 9229 |
forward 9229:19229 |
no command | caller 19229 → device 9229 |
reverse 80 |
no command | refused by the worker: TUNNEL_BAD_PORT |
reverse 70000, reverse abc, reverse 5173: |
no command | usage error |
| nine tunnels on one lease | — | refused: at most 8 per lease |
| Lease on | Today (adb reverse through exec) |
After (simlock tunnel) |
|---|---|---|
| Android emulator | reaches the worker | reaches the caller |
| enrolled Android phone | reaches the worker | reaches the caller |
| iOS simulator, iPhone | no adb | TUNNEL_UNSUPPORTED |
What could go wrong
- A dev server that answers only on the caller's LAN address, not loopback, is not reached. Likely now and then; the docs say the server must listen on localhost.
- A large build served through the tunnel loads slower than on the worker: every byte crosses the gateway. Expected; the docs say so.
- An operator turns the capability on and a lease holder reaches a device port they did not expect. The holder already runs
adbon the device through exec, so no new power. - A rule an agent made by hand through exec now disappears at lease end. Intended.
Words used
- tunnel: one
reverseorforwardbetween a device port and a caller port, open whilesimlock tunnelruns, e.g.reverse 5173. - reverse: the device connects out; its
localhost:<device-port>reaches the caller'slocalhost:<caller-port>. - forward: the caller connects in; its
localhost:<caller-port>reaches the device's<device-port>. - caller: the machine
simlock tunnelruns on, usually not the worker. - capability: an optional part of Simlock an operator turns on in config, here
capabilities.tunnel.enabled.
Non-goals
- iOS simulators and physical iPhones (a simulator's
reverseport would be a worker port shared by every lease; Host ports leased with the device #162's area). - Reaching any address on the caller's network other than loopback.
- Tunnels that outlive the command, or reattach after a lost connection.
- UDP.
- A JS client or MCP tool for tunnels.
- Simlock's own CLI remote mode beyond this one command (The fleet serves only agent-device users: CLI agents, CI runners and operators each hit a gap #945).
Completion conditions
simlock tunnel reverse <p>against a lone worker makes a page served on the caller'slocalhost:<p>load on a leased Android emulator (slow lane, emulator).- Through a gateway,
simlock tunnel reverse <p> forward <q>does the same, and a server on the emulator's port<q>answers on the caller'slocalhost:<q>(slow lane, emulator). - The CLI and a lone worker with fake devices agree on the socket protocol (fast end-to-end test).
- A tunnel on an iOS lease, a used device port, a port below 1024 or a ninth tunnel fails at once with its error, and nothing stays open.
- Stopping the command removes its rules from the device; the lease ending stops the command; no reverse rule, and no forward rule the server holds for the device, survives Android reclaim.
- With
capabilities.tunnel.enabledoff, the route answers as an unknown route on a worker andTUNNEL_UNAVAILABLEthrough a gateway whose worker has it off. - The CLI reference, configuration reference and remote-agents page describe tunnels.
Open questions
none
Decisions
- ADR 0054 (PR docs(adr): 0054 a lease holder tunnels device ports to its own machine #1111, Proposed) — A lease holder tunnels device ports to its own machine: a
tunnelmodule, off by default, carries reverse and forward for Android leases over one lease WebSocket and one uplink channel per run.
Shared values
| Value or state | Rule | Written by | Read by | Source |
|---|---|---|---|---|
| route and gate | GET /v1/leases/{id}/tunnel, a WebSocket owned by module tunnel; on only under capabilities.tunnel.enabled (default false on every role); answers the lease's holder and operators, others 404 as an unknown lease |
#1112 (worker), #1113 (gateway) | #1114, #1115 | src/input/definition.ts:12-18, src/core/config.ts:923 |
| frame codec | src/contract/tunnel-frames.ts, a contract helper with no wire shape of its own (listed for the protocol-bump check): one binary WebSocket message is [tag u8][conn u32 big-endian][payload], at most 65541 bytes; conn 0 is the control stream; connections the worker opens are odd, the caller's even, each side's ids rising |
#1112 | #1113, #1114, #1115 | ADR 0054 §3; src/contract/AGENTS.md (helpers) |
| control messages (conn 0) | 0x01 HELLO caller→worker, JSON of at most 4 KiB {tunnels:[{kind:"reverse"|"forward", devicePort}]}, the socket's first message, once; 0x81 READY worker→caller, the same list; 0x82 REFUSED worker→caller, JSON {code, message}, then close 4003 |
#1112 | #1114, #1115 | ADR 0054 §2 |
| connection messages | 0x02 OPEN JSON {kind, devicePort}; 0x03 DATA 1 to 65536 bytes; 0x04 CREDIT u32 bytes granted; 0x05 END half-close; 0x06 RESET JSON {reason}; each direction of a connection starts with a 262144-byte window and the sender never has more than that unacknowledged; a reader grants CREDIT as it consumes |
#1112 | #1114, #1115 | ADR 0054 §3 |
| bad frames | malformed JSON, an unknown tag, a frame before HELLO, a second HELLO, DATA past the granted window, a running window above 2^31, or any frame naming a connection id the other side never opened (wrong parity, or above the highest it opened): REFUSED {code:"BAD_REQUEST"} and close 4003. A frame for a connection already closed is ignored |
#1112 | #1113, #1114, #1115 | new |
| limits | 8 tunnels per lease across its sockets (a HELLO that would pass 8, a 9-entry one included, is TUNNEL_LIMIT); 256 open connections per socket, both directions counted together (the 257th OPEN is answered RESET {reason:"too many connections"}); device ports 1024 to 65535, checked by the worker only; fixed, not config |
#1112 | #1114, #1115 | ADR 0054 §2-3 |
| error codes (contract) | added to the contract's error table, one protocol bump in #1112: TUNNEL_UNSUPPORTED 422 exit 1, TUNNEL_PORT_IN_USE 409 exit 1, TUNNEL_BAD_PORT 422 exit 1, TUNNEL_LIMIT 409 exit 1, TUNNEL_FAILED 502 exit 1, TUNNEL_UNAVAILABLE 503 exit 1, TUNNEL_DISCONNECTED 503 exit 1, TUNNEL_CALLER_PORT_IN_USE 409 exit 1, TUNNEL_LEASE_ENDED 410 exit 14 |
#1112 | #1113, #1114, #1115 | src/contract/errors.ts:289-340 (neighbours) |
| refusal messages | name no lease id (through a gateway the worker knows only its own); the CLI prefixes Lease <the caller's id>: . TUNNEL_UNSUPPORTED the lease is on <an iOS simulator|an iPhone>; tunnels need an Android emulator or phone; TUNNEL_PORT_IN_USE reverse <p>: the device port is already reversed; remove that rule first; TUNNEL_BAD_PORT <kind> <p>: device ports are 1024 to 65535; TUNNEL_LIMIT the lease already has <n> tunnels; at most 8; TUNNEL_FAILED <kind> <p>: <adb's message>, or no free tunnel port on the worker when the reserved range is used up; TUNNEL_UNSUPPORTED before #1115 for a forward tunnel: forward tunnels are not available yet; TUNNEL_UNAVAILABLE (gateway, 503 before the upgrade) Tunnels are off on worker <workerId>; turn on capabilities.tunnel.enabled there; BAD_REQUEST <what was wrong> |
#1112, #1113 (TUNNEL_UNAVAILABLE) |
#1114, #1115 | new; refusal pattern src/input/refusal.ts:10-20 |
| close codes | 4001 lease ended (reason: explicit, killed, expired, device-lost, or lease ended when a gateway saw the lease go without the worker's close); 4003 after REFUSED; 1011 the lease's worker stopped answering or is stopping (as the input socket reports a stopping worker); 1001 the daemon that holds the caller's socket is stopping |
#1112, #1113 | #1114, #1115 | src/input/AGENTS.md:40-46, src/input/gateway-link.ts:93-94 |
| relay channel | through a gateway the socket rides a two-way relay as the input socket does: the worker's lease id is the relay's lease argument; frames wrapped [len u32][tag u8][payload]; the worker sends a heartbeat 0xF0 every 5 s; 0xF1 {code, reason} carries the close; the gateway relays frames up to 65541 bytes and closes a caller that sends a bigger one with REFUSED BAD_REQUEST and 4003 |
#1113 (both sides) | — | src/input/framing.ts:7, src/input/worker-link.ts:58-59, src/gateway/relay.ts:46 |
| device-port context | the daemon gives the module ctx.deviceTcp, keyed like ctx.inputs.openInput: supports(platform, device); reverse(platform, device, devicePort, signal) → {hostPort, onConnection(listener), close()}, with a loopback listener on a free port of the loopback ports 47000 to 47999, reserved for tunnels (a busy one is skipped), and adb reverse --no-rebind; connect(platform, device, devicePort, signal) → a byte stream with half-close (adb host protocol tcp:<p>); the Android driver implements them for emulators (its configured adb server port) and phones (5037, after the default-server gate); the fake driver implements them for tests |
#1112 (supports, reverse), #1115 (connect) |
#1112, #1115 | src/modules/types.ts:190-197, src/drivers/android/AGENTS.md:126 |
| byte streams | the ports TcpStream gains end() (half-close); the new loopback TcpListener (listen on 127.0.0.1:0 or a named port, accept, close; an in-use port is its own error) hands out the same stream type |
#1112 | #1115 | src/ports/tcp-stream.ts:10-14 |
| reverse rule cleanup | the module removes its rules on socket close, awaited and bounded at 10 s, before lease end lets reclaim run; a removal that fails keeps its loopback port reserved (connections closed at once) until reclaim. Android reclaim, after the device is proved its own, runs adb -s <serial> reverse --remove-all and removes each adb forward --list line <serial> <local> <remote> for that serial with adb forward --remove <local>; it never runs forward --remove-all. At daemon start, before any lease is served, the Android driver reads adb reverse --list on every emulator it reattaches and proves its own and every enrolled phone present; on an idle device it removes each rule whose host side is tcp: in 47000 to 47999; on a leased device it removes nothing (safety rule 2) and marks those ports taken so no new tunnel listener is given one, and the device's reclaim removes them: the range is reserved for tunnels (the docs tell holders not to point their own rules there; one that does is removed too), and no tunnel survives a restart; rules pointing anywhere else, a holder's own through exec, stay (leases kept across a restart, ADR 0019, keep them) |
#1112 | — | src/drivers/android/index.ts:1133-1140, src/drivers/android/index.ts:890-898 |
| caller settings | --url or SIMLOCK_URL (an https:// URL, or http:// with host 127.0.0.1, [::1] or localhost); the global --token or SIMLOCK_TOKEN (the credential the command sends as Authorization: Bearer; its help text says so); --lease <id>, else, for an agent token (its role read from GET /v1/identity), its only lease from GET /v1/leases; an operator token must name --lease; the WebSocket client is ws, through a ports adapter in the barrel that loads ws with import() on first use, so no other CLI command and not the MCP server ever load it (Node's built-in client hides a refused upgrade's status and body, and server pings) |
#1114 | #1115 | src/cli/index.ts:45, src/cli/index.ts:60 |
| caller ports | reverse's caller port 1 to 65535 (connected to); forward's 1024 to 65535 (listened on); no caller port twice in one command, across directions; usage errors |
#1114, #1115 | — | new |
| CLI output | stdout, one line per tunnel once READY: reverse device:<d> -> localhost:<c> (lease <id>), forward localhost:<c> -> device:<d> (lease <id>); stderr warning reverse <d>: nothing listening on localhost:<c>, at most once per 10 s per tunnel |
#1114 (reverse), #1115 (forward) | — | new |
| CLI exits and bounds | 0 on Ctrl-C or SIGTERM, after sending the close (waited at most 2 s); 1 on a refusal (its code), a lost connection (TUNNEL_DISCONNECTED, Lease <id>: the connection to <url> was lost; no message or ping for 45 s counts as lost; no READY or REFUSED within 90 s of HELLO counts as lost) or a busy caller port (TUNNEL_CALLER_PORT_IN_USE, forward <d>: localhost:<c> is already in use); 2 on a usage error; 14 when the lease ends (TUNNEL_LEASE_ENDED, Lease <id> ended: <reason>) |
#1114 | #1115 | src/contract/errors.ts:216-217, src/cli/AGENTS.md:85 |
Behaviours
| Behaviour (invoker, action, what it sees) | Seam | Cases | Task |
|---|---|---|---|
A holder opens the tunnel socket on a lone worker, sends HELLO with reverse tunnels and gets READY; a device connection arrives as OPEN and bytes flow both ways under credit |
worker module over a lease socket, fake device-tcp | one tunnel; two tunnels; data both ways; END from each side half-closes; a reader that grants no credit pauses only its connection; the 257th connection is reset |
#1112 |
A holder's HELLO that cannot be met is refused whole with REFUSED and close 4003, and nothing it named stays set up |
worker module | iOS lease TUNNEL_UNSUPPORTED; reversed port TUNNEL_PORT_IN_USE; port 80 and 70000 TUNNEL_BAD_PORT; a ninth tunnel across two sockets and a 9-entry HELLO TUNNEL_LIMIT; adb error TUNNEL_FAILED after an earlier tunnel opened (it is removed); a forward tunnel before #1115 TUNNEL_UNSUPPORTED |
#1112 |
A holder that sends a bad frame is refused BAD_REQUEST and closed 4003, while a frame for an already closed connection is ignored |
worker module | frame before HELLO; second HELLO; unknown tag; DATA past the window; id never opened; CREDIT for a connection just closed → ignored, other connections go on |
#1112 |
| When the socket closes, the lease ends or the daemon stops, the worker removes the tunnel's rules and listeners and closes its connections | worker module | client closes; lease ends → 4001 with the reason, rules removed before the hook returns; drain → 1001; a removal that fails keeps the port reserved | #1112 |
| A caller reaches the tunnel route on a lone worker only when it may | daemon HTTP and upgrade wiring | capability off → 404; another holder's lease → 404; holder → upgraded; operator → upgraded | #1112 |
| Android reclaim and daemon start leave no stale rule, and never touch another lease's | Android driver | reclaim, emulator; reclaim, phone; a rule made through exec; another device's forward on the same server stays; reclaim of an emulator not proved its own removes nothing; start removes rules into 47000-47999 on idle owned devices; on a leased device it removes nothing and the ports its rules point to are skipped by the next reverse; a leased device's rule to another port stays; a removal on an already-aborted lease-end hook is skipped (reclaim does it) |
#1112 |
The Android driver's reverse sets adb reverse --no-rebind to a loopback listener and removes it on close |
Android driver device-tcp | configured emulator server port; phone port 5037 behind the gate; the listener's port is in 47000-47999, a busy one skipped, all busy → TUNNEL_FAILED; cannot rebind existing socket → TUNNEL_PORT_IN_USE; another adb failure → TUNNEL_FAILED |
#1112 |
| A holder opens the tunnel socket through a gateway and it is relayed to the lease's worker | daemon end-to-end with a real gateway relay and a fake worker device | relayed HELLO/READY and a full 65541-byte DATA; REFUSED → 4003; a caller frame over 65541 bytes → 4003; worker capability off → 503 TUNNEL_UNAVAILABLE; worker stops → 1011; lease ends → 4001 with the worker's reason; gateway drains → 1001 |
#1113 |
| The gateway's tunnel bridge bounds every wait on the worker | gateway side with a fake relay and a fake clock | no head in 10 s → 503 WORKER_UNREACHABLE; the relay ends for silence → 1011; lease removed and no worker close within 2 s → 4001 lease ended |
#1113 |
| The worker's relay door keeps an idle tunnel's relay alive | worker relay door with a fake clock | an idle socket sends a heartbeat every 5 s; a close sends 0xF1 {code, reason} |
#1113 |
A user runs simlock tunnel reverse … on another machine and sees one line per tunnel, then the device reaches its loopback port |
CLI command, fake ports | 5173; 8081:3000; two reverses; flags vs env; --lease vs an agent token's only lease vs none vs several; operator token without --lease → usage error; nothing listening → one warning per 10 s, tunnel stays; a 1 MiB response arrives whole (credit granted) |
#1114 |
A user's simlock tunnel that cannot run exits with its code and one JSON error line |
CLI command, fake ports | usage errors (70000, abc, 5173:, no tunnel, same caller port twice); http:// to a non-loopback host; 401 on the upgrade; refusal → exit 1 with its code (reverse 80 → TUNNEL_BAD_PORT); lease ends → exit 14; no message for 45 s → TUNNEL_DISCONNECTED; no READY in 90 s → TUNNEL_DISCONNECTED; an idle tunnel with server pings stays open past 60 s; a refused upgrade shows its code (401, 404 UNKNOWN_LEASE, 503 TUNNEL_UNAVAILABLE); lease ends → TUNNEL_LEASE_ENDED, exit 14; Ctrl-C and SIGTERM → close sent, exit 0 |
#1114 |
| The CLI against a lone worker with fake devices opens a reverse tunnel, carries bytes, and its rules are gone after Ctrl-C | daemon end-to-end with real HTTP, the CLI command and the fake driver's device-tcp | reverse ok; refusal reaches the CLI as its code; Ctrl-C removes the rule | #1114 |
On a real emulator against a lone worker, a page served on the caller loads on the device through reverse |
slow lane e2e/slow-android-tunnel.test.ts |
reverse | #1114 |
A user runs simlock tunnel forward … and the caller's loopback port reaches the device port |
daemon end-to-end with real HTTP, the CLI command and the fake driver's device-tcp | 9229; 9229:19229; the line printed per tunnel; caller port already in use → exit 1 before HELLO; caller port below 1024 → usage error; reverse 5173 forward 5173 → usage error (same caller port across directions); device port closed → the caller's connection closed; caller END half-closes the device stream; connections accepted before READY wait, and close on REFUSED; listeners closed after Ctrl-C, REFUSED, 4001 and a lost connection; forward-only HELLO on an iOS lease → TUNNEL_UNSUPPORTED |
#1115 |
| The worker validates caller-opened connections | worker module | OPEN naming a port that is not one of the socket's forward tunnels, an odd or reused id, or kind reverse → BAD_REQUEST, 4003; caller and device connections share the 256 |
#1115 |
| The worker opens a device port through adb's host protocol for each forwarded connection | Android driver device-tcp | configured emulator server port; phone behind the gate (gate closed → RESET with its message); tcp:<p> answers FAIL → RESET; no answer in 5 s → RESET {reason:"timeout"} |
#1115 |
On a real emulator through a gateway, reverse and forward both work, both ways |
slow lane e2e/slow-android-tunnel.test.ts |
reverse through a gateway; forward through a gateway with a device server that answers | #1115 |
Tasks
Sub-issues: #1112, #1113, #1114, #1115 (not linked as GitHub sub-issues yet: the author has no write access; a maintainer links them).
flowchart LR
R[#1107: device.rotate] --> T1
T1[#1112: contract codes and codec, tunnel module on a worker, reverse, cleanup] --> T2[#1113: gateway relay, both sides]
T1 --> T3[#1114: simlock tunnel, reverse]
B[#1108: simlock rotate] --> T3
T3 --> T4[#1115: forward]
T1 --> T4
T2 --> T4
- The tunnel module: reverse a leased Android device's port over a lease WebSocket on a worker #1112 — contract error codes and frame codec (one protocol bump), the
tunnelmodule on a lone worker,reversefor Android emulators and phones, rule cleanup at close, reclaim and start — a holder with its own WebSocket client reverses a device port on a lone worker. Risk: a device rule left behind, or another lease's rule removed. - The gateway relays the tunnel socket to the lease's worker #1113 — the relay's worker and gateway sides — the same through a gateway. Risk: a socket left open after the worker or lease is gone, or a quiet tunnel cut after 15 s.
- simlock tunnel: reverse a leased device's port to the caller's machine #1114 —
simlock tunnelon the caller,reverse, with the fast end-to-end and the slow-lane emulator test — an agent on another machine reverses with one command. Risk: the command keeps running after its tunnels are gone. - simlock tunnel forward: reach a leased device's port from the caller's machine #1115 —
forwardon both sides — an agent reaches a device port from its machine. Risk: a caller port left listening after the command ends.
Completion conditions: the first is #1114's Done when (slow lane, lone worker); the second #1115's (slow lane, gateway); the third #1114 and #1115; the fourth #1112, #1114 and #1115; the fifth #1112 and #1114; the sixth #1112 and #1113; the seventh each task's docs (configuration and remote-agents for a lone worker #1112; remote-agents through a gateway #1113; CLI reference #1114, extended by #1115).
Open work in the same area:
- simlock rotate and MCP rotate_device #1108 (Rotate a leased iOS simulator from the CLI, MCP, HTTP and agent-device #1105's CLI task): edits the CLI tree, the root usage and the CLI reference, and moves the only-lease resolver into a shared CLI file; simlock tunnel: reverse a leased device's port to the caller's machine #1114 depends on it and reuses that resolver with an HTTP lease lister.
- device.rotate: rotate a leased iOS simulator over the contract, HTTP and the client #1107 (Rotate a leased iOS simulator from the CLI, MCP, HTTP and agent-device #1105's core task): adds contract error codes, a protocol bump and
ctx.inputs.leaseInputbesideinputsinWorkerContextand the daemon's module context; The tunnel module: reverse a leased Android device's port over a lease WebSocket on a worker #1112 changes the same lines, so The tunnel module: reverse a leased Android device's port over a lease WebSocket on a worker #1112 depends on device.rotate: rotate a leased iOS simulator over the contract, HTTP and the client #1107. - Host ports leased with the device #162 (feature:spec, host ports): worker-side ports for a lease; its non-goals leave tunnels out. No shared file; no Depends on.
- The fleet serves only agent-device users: CLI agents, CI runners and operators each hit a gap #945 (request): CLI remote mode;
--url,SIMLOCK_URLand the global--tokensent remotely are the first remote CLI settings and The fleet serves only agent-device users: CLI agents, CI runners and operators each hit a gap #945 should reuse them. No Depends on. - The Android startup sweep attaches another Simlock instance's emulators to this instance's adb server #264 (bug:new, Android startup sweep and the adb server): the start sweep here runs only on devices proved Simlock's own. No shared code path.
Written by an agent.
Activity
leonardoxr commentedon Oct 31, 2026
Spec approved: the feature, ADR 0054 (PR #1111, now Accepted — not yet implemented) and tasks #1112, #1113, #1114, #1115, each checked ready.
The author has no write access to this repository, so a maintainer still needs to:
feature:planned, and The tunnel module: reverse a leased Android device's port over a lease WebSocket on a worker #1112 to simlock tunnel forward: reach a leased device's port from the caller's machine #1115task:ready;request:new(it has no state label).Delivery order: #1111 (ADR) at the bottom of the stack; #1112 after #1107 (both bump the protocol and change
WorkerContext); then #1113 and #1114 (after #1108) side by side; #1115 last. #1114 and #1115 adde2e/slow-android-tunnel.test.ts, which needs a slow lane with the Android SDK.Written by an agent.
test(tunnel): input and tunnel together on one lease through a gatewa…
test(tunnel): input and a reverse tunnel together on a real emulator …
test(tunnel): gateway slow-lane adb calls run as the lease's agent wi…
V3RON commentedon Nov 1, 2026
Delivery run
Merged: #1121 (#1112), #1125 (#1113)
Parked: #1114 — review cap reached; R4 fixed after the final fix-check (50c1f69, two test assertions), unreviewed
Waiting: #1115 — on #1114; #1111 (ADR 0054) — flips to Accepted and merges after #1115
Decided: #1112 — phone cleanup → emulators only, a phone loses only its tunnel's own rules; #1112 — loopback listener → no check, exposure documented; #1112 — stale phone rule → its port stays reserved; #1112 — aborted lease end on a phone → still removes the tunnel's rules
Written by an agent.
test(tunnel): input and tunnel together on one lease through a gatewa…
test(tunnel): input and a reverse tunnel together on a real emulator …
test(tunnel): gateway slow-lane adb calls run as the lease's agent wi…
test(tunnel): through a gateway simlock adb needs no closed stdin (AD…
V3RON commentedon Nov 1, 2026
Delivery run
Merged: #1121 (#1112), #1125 (#1113), #1126 (#1114)
Parked: #1115 — needs-hardware: reviewed and fixed; the slow lane passed before the last test-only fix, then the emulator would not boot on four runs
Waiting: #1111 (ADR 0054) — flips to Accepted and merges after #1115; #1182 — task:draft, needs a spec session
Decided: #1112 — phone cleanup, stale phone rule ports, an aborted end on a phone, and loopback exposure → all confirmed by the maintainer
Written by an agent.
feat(tunnel): simlock tunnel forward reaches a leased device's port f…
docs(adr): 0054 a lease holder tunnels device ports to its own machine (
V3RON commentedon Nov 1, 2026
Delivery run
Merged: #1121 (#1112), #1125 (#1113), #1126 (#1114), #1127 (#1115), #1111 (ADR 0054, now Accepted)
Waiting: #1182 — task:draft (Unix-socket listener), needs a spec session; #1110 stays open until it closes
Decided: #1112 — phone cleanup, stale phone rule ports, an aborted end on a phone, and loopback exposure → all confirmed by the maintainer
Written by an agent.
github-actions commentedon Nov 3, 2026
Closing: all 5 sub-issues are closed. The PR that closed the last one walks the completion conditions (delivery rule 9); reopen this if one does not hold on
main. Flip its ADRs to Accepted if delivery has not.V3RON commentedon Nov 3, 2026
Shipped: #1112, #1113, #1114 and #1115 are merged, and ADR 0054 is merged (#1111). The follow-up #1182 (Unix-socket listener) was closed as not planned on 2026-11-03.
Written by an agent.