Skip to content

🛠️ Molly Stuck Loading: Expired Build from a Stale F-Droid Repo

🔴 Symptom

Molly (a hardened Signal fork) sits on a permanent loading spinner and never completes device linking. The failure is completely silent:

  • No error dialog, no crash, no FATAL EXCEPTION, no ANR
  • Zero bytes transferred during the "sync"
  • The app is alive and allocating memory — it simply never talks to the network

Nothing in logcat names the problem.

🎯 Root Cause

F-Droid was pointing at a stale repository, serving an old Molly build long after upstream had moved on.

Molly deliberately caps build lifespan to avoid Signal server incompatibility (mollyim#677, changelog v7.72.2-1). Signal's servers reject clients that fall too far behind, so Molly expires its own builds rather than fail in confusing ways. When the build ages out, the client refuses to complete provisioning.

Real-world instance (2026-07-20):

Installed v8.7.3-2, released 29 Apr — 82 days old
Current upstream v8.19.2-2 (Signal 8.19.2)

Fix: point F-Droid at Molly's official repo (https://molly.im/fdroid/repo), update, and re-link. Or install the current APK from GitHub releases.

Check the version first

For any app that self-expires, this is a ten-second check that short-circuits hours of debugging:

adb shell dumpsys package im.molly.app | grep versionName

Compare against the upstream releases page before touching logcat.

⚠️ The Screen Is Unobservable

Molly sets FLAG_SECURE, and on GrapheneOS the usual escapes are closed too:

  • adb exec-out screencap -p returns a solid black image
  • adb shell uiautomator dump is refused

There was almost certainly an on-screen "update required" banner the entire time, and it was invisible to tooling. Ask the user what the screen says — do not assume you can capture it.

🟢 Diagnostic Procedure

With the UI unreadable, the diagnosis has to come from system services.

1. Confirm connection and package

adb devices -l                              # Pixel 6a = model:Pixel_6a, codename bluejay
adb shell pm list packages | grep -i molly  # im.molly.app

2. Version vs upstream — do this first

adb shell dumpsys package im.molly.app | grep -iE 'versionName|firstInstall|lastUpdate'

3. Is it actually moving data?

Per-interface totals, which is what you want when an app may be using a non-default network:

adb shell cat /proc/net/dev | grep -E 'wlan|p2p'

Sample twice ~20 s apart and diff. A few KB device-wide means nothing is happening.

dumpsys netstats buckets are per-network-ident

A byte counter scoped to the Wi-Fi ident will not show traffic crossing any other interface. Use /proc/net/dev for ground truth.

/proc/net/tcp is root-only on modern Android

An empty socket dump is inconclusive — not proof there is no connection.

4. Rule out power management

adb shell am get-standby-bucket im.molly.app     # 5=EXEMPTED 10=ACTIVE 20=WORKING_SET ...
adb shell dumpsys deviceidle whitelist | grep -i molly

5. Rule out DNS/network path

for d in chat.signal.org cdn.signal.org cdsi.signal.org storage.signal.org; do
  printf '%-24s ' "$d"; adb shell ping -c1 -W2 $d 2>&1 | head -1
done

Real IPs = DNS is fine. Relevant on a network running Pi-hole or NextDNS, where a blocklist can eat legitimate endpoints.

6. Registration/connection state

adb logcat -c; sleep 15
adb logcat -d -v brief --pid=$(adb shell pidof im.molly.app)

Key line, and it is self-explaining:

D/IncomingMessageObserver: [Does Not Need Connection] Network: true,
  PushAvailable: false, WS Open or Keep-alives: false,
  Registered: false, Force websocket: true

Does Not Need Connection + Registered: false means the app is deliberately not opening its websocket because there is no account yet — that is the normal pre-link state, not a fault.

📋 Things That Look Like Bugs But Aren't

Observation Reality
PushAvailable: false, Force websocket: true Normal. Since v7.68.5 Molly is a single FOSS build — Play Services SDK removed, FCM reimplemented open-source. There is no longer an "FCM flavor" vs "FOSS flavor".
FcmReceiveService running Normal for that open-source FCM implementation.
Not in the doze whitelist Only matters after linking — see below.
NotificationVibratorHelper: at least one timing must be non-zero Cosmetic Android system notification bug, unrelated.
Black screenshot FLAG_SECURE, not a broken UI.

🔋 Post-Link: Set Battery to Unrestricted

Because Force websocket: true means Molly holds its own connection instead of relying on FCM push, doze will stall delivery.

Settings → Apps → Molly → Battery → Unrestricted, then verify:

adb shell dumpsys deviceidle whitelist | grep -i molly   # user,im.molly.app,10009
adb shell am get-standby-bucket im.molly.app             # 5 (EXEMPTED)

The bucket moving from 10 (ACTIVE) to 5 (EXEMPTED) confirms it took effect.

🔗 Linking an Android Phone as a Secondary Device

Signal supports this — "You can link a Desktop, iPad or Android device (tablet or phone) to your primary Signal Device." Molly runs as a non-primary device too (changelog #647, SSRE2 linked-device fixes).

Direction matters: the Android device displays the QR via "Link your account"; the primary scans it (Settings → Linked devices → Link a new device).

\"Don't Transfer\" is a one-shot decision

Declining message-history transfer during linking cannot be undone later. Per Signal's docs, changing your mind means reinstalling on the linked device and re-linking with the transfer option.

🧭 Debugging Lesson

Four plausible hypotheses were investigated and disproven before the real cause surfaced: Pi-hole DNS blocking, doze/battery restriction, Wi-Fi Direct p2p failure, and a flapping 2G cellular network with a captive portal.

All four were infrastructure explanations. The actual cause was a stale package repository. When a homelab's history is full of DNS and network problems, that's where suspicion goes first — worth consciously checking the boring supply-chain explanation (is this software simply out of date?) early.