Skip to content

fix(helper): pump run loop for CLLocationManager auth before scan (the real root cause)#45

Merged
chenchaoyi merged 1 commit into
mainfrom
fix/scan-runloop-pump
May 13, 2026
Merged

fix(helper): pump run loop for CLLocationManager auth before scan (the real root cause)#45
chenchaoyi merged 1 commit into
mainfrom
fix/scan-runloop-pump

Conversation

@chenchaoyi
Copy link
Copy Markdown
Owner

Summary

The real root cause of the macOS 26 install hang you've been seeing through v1.0.3 → v1.0.5.

The earlier fix in v1.0.3 (disclaim + CLLocationManager.startUpdatingLocation + Thread.sleep(0.3)) was on the right track for points 1 + 3 but got point 2 wrong:

Thread.sleep does not pump the run loop. CLLocationManager's delegate-callback handshake with locationd never actually completes inside a short-lived CLI subprocess. CoreWLAN's macOS 26 redaction gate checks whether the calling process is a registered location consumer — not just authorised — so scans came back redacted (ssid / bssid null) even when the bundle's TCC grant was in place.

The helper GUI window saying "Location: granted" was reading authorizationStatus synchronously (cached state). The CoreWLAN redaction gate is the stricter check, and it failed silently — leading to the polling hang.

Fix

New LocationAuthProbe : CLLocationManagerDelegate that flips a flag when locationManagerDidChangeAuthorization resolves status to non-.notDetermined. The scan subcommand pumps RunLoop.current.run(mode: .default, before: …) in 50ms slices, exiting as soon as the callback fires (typically <100ms when the grant is already in TCC.db) or after a 2s timeout. Only then does scanForNetworks run.

Mirrors the existing runBluetoothStatusProbe pattern — which is exactly why bluetooth-status has been working correctly through all these versions while scan has been broken.

Verification (local, before opening this PR)

helper/build.sh                          # rebuilds with the fix
open helper/diting-tianer.app            # new cdhash 6e74d56e..., user grants Location
pkill -f diting-tianer.app && sleep 1    # kill GUI to defeat warm-cache
helper/diting-tianer.app/.../diting-tianer scan | jq '.networks | length'

3 cold-start subprocess runs:

run 1: rows=129 with_bssid=129
run 2: rows=153 with_bssid=153
run 3: rows=153 with_bssid=153

100% unredacted, no warm GUI session needed. This is the first time the fix has been verified to work in the exact same conditions install.sh produces.

  • uv run pytest — 499 passed
  • Tag v1.0.6 + watch release workflow — awaiting your explicit go

🤖 Generated with Claude Code

Real root cause of the macOS 26 install hang. The v1.0.3 fix added
disclaim-responsibility + CLLocationManager.startUpdatingLocation()
+ Thread.sleep(0.3) to runScanAndDumpJSON. The disclaim hop and
the manager init are correct; the Thread.sleep was wrong.

Thread.sleep does NOT pump the run loop. CLLocationManager's
delegate-callback handshake with locationd never actually
completes inside a short-lived CLI subprocess. CoreWLAN's macOS
26 redaction gate checks whether the process is a *registered*
location consumer — not just authorised — so scans came back
redacted (ssid/bssid null) even when the bundle's TCC grant was
in place. The bundle GUI sees authorizationStatus as granted
because that's a synchronous read of cached state; the redaction
gate is the stricter check.

This kept the user stuck at "需要以下权限:- 定位服务" through
v1.0.3 / v1.0.4 / v1.0.5 even after clicking Allow on every
prompt.

Fix: new LocationAuthProbe : CLLocationManagerDelegate that flips
a flag when `locationManagerDidChangeAuthorization` resolves
status to non-.notDetermined. The scan subcommand pumps
`RunLoop.current.run(mode:.default, before:…)` in 50ms slices,
exiting as soon as the callback fires (typically <100ms when the
grant is already in TCC.db) or after a 2s timeout. Only then
does scanForNetworks run. Same pattern as the existing
runBluetoothStatusProbe.

Verified locally end-to-end:
- helper/build.sh rebuilds → new cdhash 6e74d56e...
- user grants Location on the new cdhash via `open helper/...`
- 3 cold-start subprocess scans (helper GUI killed between runs
  to defeat warm-cache effects) → 100% unredacted rows each run

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chenchaoyi chenchaoyi merged commit 18b3104 into main May 13, 2026
4 checks passed
@chenchaoyi chenchaoyi deleted the fix/scan-runloop-pump branch May 13, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant