Skip to content

Fresh install cannot open the developer tunnel: unpinned pmd-pytcp >=0.1.0 breaks userspace_tunnel (QueueFull flood) #4

Open

Description

@thyoondev

GeoShift version

1.5.2 (build 9), commit 3114dd7, built from source via ./Scripts/install_app.sh

macOS, iOS, and iPhone model

macOS 26.7 (25G229, Apple silicon), iOS 27.0 (24A437), iPhone18,4

Connection type

Trusted USB

Reproduction steps

A fresh install never connects. The UI stays on "Restore GPS is waiting for a trusted iPhone connection" and the keeper log fills with asyncio.queues.QueueFull at tens of lines per second — 7,270 entries and roughly 5 MB across the three rotating logs within minutes.

Pairing, trust and lockdown are all fine: the device is USB-attached with Developer Mode on, lockdown info returns the full record, and a Wi-Fi RemotePairing record exists. The failure is one layer lower, in the userspace tunnel. Re-pairing, re-entering the six-digit code, reconnecting the cable and restarting the iPhone all have no effect, which makes sense once the cause is clear.

Root cause: an unpinned transitive dependency. Not GeoShift's own code — but the README's install command is what pulls the broken version, so a new user hits this immediately.

pymobiledevice3 9.31.0 declares no upper bound:

Requires-Dist: pmd-pytcp>=0.0.4

pmd-pytcp turned its API async in 0.1.0 (2026-07-13). pymobiledevice3 9.31.0 was released 2026-06-29 against the synchronous API and still calls it synchronously:

  • pymobiledevice3/remote/userspace_tunnel.py:201 — stack.start()
  • pymobiledevice3/remote/userspace_tunnel.py:239 — s.connect((addr, port))

Verified by unpacking each release from PyPI:

pmd-pytcp stack/lifecycle.py socket/tcp__socket.py
0.0.4 – 0.0.6 def start() def connect()
0.1.0 – 0.3.7 async def start() async def connect()

So the coroutines are created and discarded. The PyTCP stack never starts, the socket never connects, nothing drains the receive queue, the bounded queue fills — and every subsequent frame raises QueueFull, forever. QueueFull is a symptom three steps downstream of the actual defect.

Minimal reproduction, independent of GeoShift:

import asyncio
from pymobiledevice3.remote.userspace_tunnel import UserspaceRsdTunnel

async def main():
    t = UserspaceRsdTunnel(serial="<UDID>", autopair=True)
    rsd = await asyncio.wait_for(t.aopen(), timeout=30)
    print("TUNNEL OK", rsd.udid)
    await t.aclose()

asyncio.run(main())

Expected behavior

The tunnel opens and the worker connects, as it does once pmd-pytcp is constrained to the version range pymobiledevice3 9.31.0 was written against:

uv tool install --force --python 3.13 'pymobiledevice3==9.31.0' --with 'pmd-pytcp<0.1.0'

After this the keeper log shows Connected to iPhone … and Clear command sent successfully, with zero QueueFull.

Suggested for GeoShift: add the constraint to the README quick-install command. The real fix belongs upstream in pymobiledevice3 — either await those call sites or cap pmd-pytcp<0.1.0 — and I am reporting it there as well.

Redacted diagnostics

With pmd-pytcp 0.3.7 (resolved automatically by the documented install):

userspace_tunnel.py:201: RuntimeWarning: coroutine 'start' was never awaited
  stack.start()
userspace_tunnel.py:239: RuntimeWarning: coroutine 'TcpSocket.connect' was never awaited
  s.connect((addr, port))
RuntimeWarning: coroutine 'TcpSocket.recv' was never awaited
RuntimeWarning: coroutine 'TcpSocket.send' was never awaited

ERROR Exception in callback Queue.put_nowait()
handle: <Handle Queue.put_nowait()>
Traceback (most recent call last):
  File ".../asyncio/events.py", line 89, in _run
    self._context.run(self._callback, *self._args)
  File ".../asyncio/queues.py", line 166, in put_nowait
    raise QueueFull
asyncio.queues.QueueFull
    ... repeating indefinitely

With pmd-pytcp 0.0.4:

TUNNEL OK udid=<redacted>
DVT CHANNEL OK

INFO Connected to iPhone <redacted>
INFO Clear command sent successfully to <redacted>

Secondary observations

  1. The error loop is unbounded. It wrote ~5 MB across all three rotating logs in minutes and scrolled away the original context, which made diagnosis considerably harder. Rate-limiting the handler, or bailing out after N consecutive identical failures, would help.

  2. The status message is misleading here. "Restore GPS is waiting for a trusted iPhone connection" points at trust and pairing, which were both fine. Distinguishing "no trusted device found" from "tunnel failed to open" would have saved a lot of time.

  3. Minor: MissingValueError on EnableWifiConnections inside enable_wireless_connections() is caught and logged. Harmless — the connection succeeds right after — but it adds noise to an already noisy log.

Privacy confirmation

  • I removed device identifiers, IP addresses, personal paths, pairing records, and credentials.
  • This is not a security vulnerability or private report.

Activity

  1. Lemelson commented on Oct 8, 2026

    @Lemelson
    Owner

    Thanks for the detailed report. I independently verified the dependency incompatibility and prepared a focused fix in b313f12, tracked in draft PR #5.

    The original install/test command resolved pmd-pytcp 0.3.7 in a clean Python 3.13 environment. New regression checks failed on its version and real async stack/TCP/UDP methods. All installation and test instructions and Python CI now constrain pmd-pytcp>=0.0.4,<0.1.0 alongside pymobiledevice3==9.31.0.

    Verification completed:

    • Fresh uv run, isolated uv tool install, and fresh pip venv all resolved pmd-pytcp 0.0.6 and passed all 21 Python tests, including the new dependency/API regression.
    • pip check, 21 Swift tests, packaging, signature verification, and local main-screen/Settings launch passed. The GUI used the Mac's existing compatible 0.0.4 backend.
    • Both Python and Swift checks passed in GitHub CI.

    For an affected uv installation, the documented repair is:

    uv tool install --force --python 3.13 --with 'pmd-pytcp>=0.0.4,<0.1.0' \
      'pymobiledevice3==9.31.0'

    Then select Restart in GeoShift.

    I also checked the secondary observations. The rapid QueueFull flood originates in upstream relay callbacks when synchronous recv receives a coroutine; the reconnect delay cannot rate-limit those callbacks. The bound removes that incompatibility. General error deduplication and the restore UI's masking of tunnel errors remain separate improvements; pending GPS-clear retries are preserved.

    Physical-iPhone acceptance is still pending: no USB phone was connected and the saved Wi-Fi connection probe timed out. These checks confirm the dependency fix, not an actual GPS change. PR #5 remains draft, this issue remains open, and no new build has been published until tunnel/Start/Restore/reconnect and location verification on an iPhone are completed.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions