Skip to content

fix(limrun): keep the owner's reverse mappings on attached Android instances - #3204

Merged
thymikee merged 5 commits into
mainfrom
claude/limrun-attached-no-rebind
Oct 5, 2026
Merged

thymikee merged 5 commits into
mainfrom
claude/limrun-attached-no-rebind

Conversation

@thymikee

@thymikee thymikee commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Summary

Follow-up to the --no-rebind review note on #3173.

On an attached Limrun Android instance, agent-device no longer replaces a reverse mapping it did not create. Before, configurePortReverse and the localhost URL auto-reverse ran plain adb reverse. They could take over the owner's tcp:8081, and teardown then removed it.

  • createExecAndroidPortReverseProvider(adb, { noRebind }) and createAndroidPortReverseManager(executor, { noRebind }) run adb reverse --no-rebind on every ensure, so no existing device mapping is replaced. The manager serializes ensures per endpoint.
  • When adb reverse --list shows the mapping, a refusal fails with COMMAND_FAILED and details.reason: 'android_port_reverse_rebind_refused'. adb's stderr is never parsed.
  • createLimrunAndroidSession asks for no-rebind only when ownership === 'attached'.
  • The localhost auto-reverse now has the owner localhost-url, so attached teardown removes it.

10 files, including client-api.md and limrun.md.

Validation

  • 356932729c: pnpm check:affected --run passes (3111 related tests).
  • Each of these mutations fails a new test: dropping --no-rebind, restoring the own-mapping exemption, making the lock a no-op, the refusal branch, reason propagation, SDK option forwarding, forcing noRebind, and dropping the localhost-url owner.
  • Live at 356932729c on a Limrun Android instance. The owner mapped tcp:8081 from its own adb server. agent-device was attached with only LIM_ANDROID_INSTANCE_*. open exp://127.0.0.1:8081 was refused with the typed reason. tcp:8097 and tcp:8099 succeeded. After disconnect, the owner listed only host-19 tcp:8081 tcp:8081. Details are in the PR comment.

createExecAndroidPortReverseProvider and createAndroidPortReverseManager
(executor form) take { noRebind }. The provider then runs
adb reverse --no-rebind for an endpoint it did not bind, still rebinds
its own mappings, and fails a refusal with COMMAND_FAILED and
details.reason 'android_port_reverse_rebind_refused'. The refusal is
classified from adb reverse --list, not from adb's stderr. The localhost
URL auto-reverse keeps that reason on the error it rethrows.
…stances

createLimrunAndroidSession asks createPortReverse for no-rebind mode when
ownership is 'attached'. configurePortReverse and the localhost URL
auto-reverse can then no longer replace an owner mapping such as tcp:8081,
so teardown cannot remove it. Created instances keep plain adb reverse.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 10 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/platform-android/src/adb-port-reverse.ts Outdated
Comment thread website/docs/docs/limrun.md Outdated
Comment thread packages/platform-android/src/adb-port-reverse.ts
@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.97 MB 4.97 MB +1.3 kB
Package (unpacked) 4.97 MB 4.97 MB +1.3 kB
Package (download) 1.49 MB 1.49 MB +469 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 18.9 ms 20.4 ms +1.5 ms
CLI --help 53.6 ms 57.6 ms +4.1 ms

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-05 05:49 UTC

No-rebind mode now passes --no-rebind on every ensure, so a stale record
of a mapping this provider bound can no longer turn into a plain
adb reverse over another client's mapping. The manager serializes ensures
per device endpoint, so a concurrent duplicate sees the first mapping
instead of a refusal, and two owners can no longer both pass its
ownership check. Docs state when the typed reason is available.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/platform-android/src/adb-port-reverse.ts Outdated
Comment thread website/docs/docs/limrun.md Outdated
Name adb reverse --list as the condition in the noRebind JSDoc, and
scope the Limrun docs guarantee to attached Android instances.
@thymikee

thymikee commented Oct 4, 2026

Copy link
Copy Markdown
Member Author

Code looks right at e4bba1f, but the new adb reverse behavior has no live evidence yet. Smoke Tests was still running when I checked, so CI is not final. There are no conflicts.

The change adds --no-rebind and a --list check after a refusal in packages/provider-limrun/src/android.ts, and only runCmd mocks cover it. Mocks cannot show whether the Limrun ADB tunnel lists the owner's listeners in reverse --list, or whether --no-rebind refuses across adb clients. If the tunnel hides them, users get a generic adb error instead of android_port_reverse_rebind_refused. If --no-rebind does not refuse across clients, the owner's mapping is still replaced, and limrun.md describes behavior the product lacks. Please run this on a Limrun Android instance attached through LIMRUN_ANDROID_* URL and token, after the owner has run adb reverse tcp:8081 tcp:8081 from their own adb client. First, run open exp://127.0.0.1:8081 and show the JSON error with details.reason: 'android_port_reverse_rebind_refused' (or the generic adb failure, if the tunnel hides the listing). Second, run a reverse to an unused port such as 8097 and show that it succeeds. Third, run disconnect, then show from the owner's side that adb reverse --list still has tcp:8081 tcp:8081 and no longer has tcp:8097.

Not blocking: the localhost auto-reverse in app-lifecycle.ts calls ensure without an ownerId, so on an attached instance the disconnect carve-out never removes that mapping and agent-device may leave its own tcp:<port> behind (this predates the PR, and I did not check whether adbd drops it when the tunnel disconnects). A follow-up could have teardown remove every endpoint in the provider's bound map, or give that call a stable ownerId. You can take or leave this.

Is there a smaller design than a noRebind option on the exec provider? I looked and found none that is clearly better, since it reuses the existing manager and withKeyedLock and nets +62 lines.

The five cubic-dev-ai threads (one P2, four P3) are fixed at this head and can be resolved: no-rebind decision, limrun.md typed reason, per-endpoint serialization, JSDoc --list condition, attached-instance wording.

I read the diff and the pre-change code only. I did not run tests, and I could not check real adbd or tunnel behavior. Before merge, I need the live run above: open exp://127.0.0.1:8081 refused with its reason, and tcp:8081 still in the owner's adb reverse --list after disconnect.

The localhost URL auto-reverse now ensures its mapping with the stable
owner 'localhost-url'. Teardown of an attached Limrun instance removes
only owned mappings, so before this change the mapping stayed on the
owner's device after disconnect. A live run showed that adbd keeps it.
@thymikee

thymikee commented Oct 4, 2026

Copy link
Copy Markdown
Member Author

I ran the live check on a real Limrun Android instance, and it found the leftover from your non-blocking note. I fixed that in 3569327.

Setup. I created the instance with LIMRUN_API_KEY. The owner opened its own ADB tunnel on a separate adb server (adb -P 5038) and ran adb reverse tcp:8081 tcp:8081. agent-device ran from this branch with LIMRUN_API_KEY unset, attached through LIM_ANDROID_INSTANCE_URL, _TOKEN and _ADB_URL. In reverse --list, the owner's client shows as host-19 and agent-device as host-20/host-24, so the two are separate adb clients.

At e4bba1f:

  1. open exp://127.0.0.1:8081 --json was refused. The owner's host-19 tcp:8081 tcp:8081 was unchanged:
    {"code":"COMMAND_FAILED","message":"Failed to ensure Android port reverse tcp:8081 before opening localhost URL","details":{"localPort":"8081","operation":"adb reverse tcp:8081 tcp:8081","reason":"android_port_reverse_rebind_refused","dispatched":"unknown"}}
  2. react-devtools start (named reverse, tcp:8097) and open http://127.0.0.1:8099/ (localhost auto-reverse) both succeeded.
  3. After disconnect, tcp:8097 was gone, but host-20 tcp:8099 tcp:8099 was still listed 20 s later. adbd does not drop it when the tunnel closes, so your note was right.

Fix (3569327). The localhost auto-reverse now ensures its mapping with the owner localhost-url. Attached teardown removes it the same way it removes react-devtools. If the owner is dropped, Limrun removes only its own port reverse mappings from an attached Android instance and the app-lifecycle reverse test fail.

At 3569327, same instance and steps: the refusal had the same reason, tcp:8097 and tcp:8099 succeeded, and after disconnect the owner listed only:

host-19 tcp:8081 tcp:8081

pnpm check:affected --run passes at this head. The instance is deleted (terminated).

🤖 Addressed by Claude Code

@thymikee

thymikee commented Oct 4, 2026

Copy link
Copy Markdown
Member Author

The Limrun change at 3569327 now keeps the owner's reverse mappings intact on attached Android instances, and I found nothing to fix.

Since the earlier review at e4bba1f, the requested live evidence is posted. It shows the refusal JSON, the tcp:8097 and tcp:8099 successes, and the owner's reverse --list after disconnect. I did not reproduce the live Limrun run myself. I also ran no tests. I checked by reading that the tests at this head would fail against the old route without the fix.

CI is green: 21 checks, none failing. The unit and smoke lanes cover the Android localhost-reverse route and the Limrun runtime tests that this change touches. There are no conflicts.

The five cubic-dev-ai threads are fixed at this head, so please resolve them: the --no-rebind decision (#3204 (comment)), the typed-reason docs in limrun.md (#3204 (comment)), the per-endpoint lock (#3204 (comment)), the --list JSDoc (#3204 (comment)), and the "attached Android instance" scope in the docs (#3204 (comment)).

Nothing else blocks this. It is ready for a human review.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 4, 2026
@thymikee
thymikee merged commit db56bb7 into main Oct 5, 2026
21 checks passed
@thymikee
thymikee deleted the claude/limrun-attached-no-rebind branch October 5, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant