Three things cannot be exercised on a simulator at all, and all three produced real
bugs on 2026-09-01:
APNs push — simulators receive none. The foreground-banner fix and the relay's new silent background push are both unverified in the only environment where they run.
VoIP push — a simulator has no VoIP token, so calls fall back to an ordinary alert
push. The "New message" banners appearing during calls were specifically that
fallback path. A real device would not have taken it.
The microphone — voice notes cannot be recorded on a simulator, so that feature has
never been observed working end to end.
Done looks like: one call, one message, one voice note and one backgrounded
notification, between two physical devices, before any build goes to a tester.
Three things cannot be exercised on a simulator at all, and all three produced real
bugs on 2026-09-01:
- **APNs push** — simulators receive none. The foreground-banner fix and the relay's new
`silent` background push are both unverified in the only environment where they run.
- **VoIP push** — a simulator has no VoIP token, so calls fall back to an ordinary alert
push. The "New message" banners appearing during calls were *specifically* that
fallback path. A real device would not have taken it.
- **The microphone** — voice notes cannot be recorded on a simulator, so that feature has
never been observed working end to end.
**Done looks like:** one call, one message, one voice note and one backgrounded
notification, between two physical devices, before any build goes to a tester.
FN_APNS_ENV is not set in /srv/prod/messenger/.env today, so the relay is on sandbox — checked 2026-09-01. That default is deliberate and right for a build you
install yourself, and wrong for TestFlight.
Why it will not be obvious. A production push to a sandbox token — or the reverse —
fails with BadDeviceToken, and the error does not say which way round it is. From the
outside it reads as "calls don't ring on a locked phone", which is indistinguishable from
a bug in the call path. The relay's own source carries a warning about exactly this.
Steps at release:
Set FN_APNS_ENV=production in /srv/prod/messenger/.env
Recreate the relay so it re-reads the env
Confirm the startup log line: APNs wake-ups enabled for net.flamenet.messenger (production)
Ring a locked TestFlight phone from Android before telling anyone it works
⚠️ Order matters: a development build on a physical device needs sandbox. If both a
dev device and TestFlight are in play at once, one of them will not ring, and the relay
cannot serve both — the environment is per-relay, not per-token.
ℹ️FN_APNS_TOPIC is also unset and defaults to net.flamenet.messenger, which matches
the shipping bundle id. Correct today, but a default rather than a decision.
What is already in place (verified 2026-09-01)
Info.plist declares both voip and remote-notification background modes
VoIPCallReceiver + CallKit are merged to main (8265298)
The relay holds secrets/apns-key.p8, with FN_APNS_KEY_ID and FN_APNS_TEAM_ID set
Android's side needs none of this: MessengerService uses CallStyle, CATEGORY_CALL and setFullScreenIntent(full, true), ships in 0.8, and is testable now
A simulator cannot test any of it. It never receives a VoIP token, so the relay falls
back to an ordinary alert push — which is the exact path that produced the spurious
"New message" banners during calls on 2026-09-01. The simulator does not merely fail to
exercise this; it exercises the wrong branch.
## Release-time step, added 2026-09-01: `FN_APNS_ENV=production`
🔴 **The moment Apple approves and the build goes out via TestFlight, the relay
must be told**, or nobody's iPhone rings and nothing says why.
`Entrypoint.swift` resolves the APNs environment as:
```swift
let sandbox = (envv["FN_APNS_ENV"] ?? "sandbox").lowercased() != "production"
```
`FN_APNS_ENV` is **not set** in `/srv/prod/messenger/.env` today, so the relay is on
**sandbox** — checked 2026-09-01. That default is deliberate and right for a build you
install yourself, and wrong for TestFlight.
**Why it will not be obvious.** A production push to a sandbox token — or the reverse —
fails with `BadDeviceToken`, and the error does not say which way round it is. From the
outside it reads as "calls don't ring on a locked phone", which is indistinguishable from
a bug in the call path. The relay's own source carries a warning about exactly this.
**Steps at release:**
1. Set `FN_APNS_ENV=production` in `/srv/prod/messenger/.env`
2. Recreate the relay so it re-reads the env
3. Confirm the startup log line: `APNs wake-ups enabled for net.flamenet.messenger (production)`
4. Ring a locked TestFlight phone from Android before telling anyone it works
⚠️ Order matters: a **development** build on a physical device needs sandbox. If both a
dev device and TestFlight are in play at once, one of them will not ring, and the relay
cannot serve both — the environment is per-relay, not per-token.
ℹ️ `FN_APNS_TOPIC` is also unset and defaults to `net.flamenet.messenger`, which matches
the shipping bundle id. Correct today, but a default rather than a decision.
---
## What is already in place (verified 2026-09-01)
- `Info.plist` declares both `voip` and `remote-notification` background modes
- `VoIPCallReceiver` + CallKit are merged to `main` (`8265298`)
- The relay holds `secrets/apns-key.p8`, with `FN_APNS_KEY_ID` and `FN_APNS_TEAM_ID` set
- Android's side needs none of this: `MessengerService` uses `CallStyle`,
`CATEGORY_CALL` and `setFullScreenIntent(full, true)`, ships in 0.8, and is testable now
**A simulator cannot test any of it.** It never receives a VoIP token, so the relay falls
back to an ordinary alert push — which is the exact path that produced the spurious
"New message" banners during calls on 2026-09-01. The simulator does not merely fail to
exercise this; it exercises the wrong branch.
changed title from Nothing is verified on a physical device: push, VoIP, and the microphone to Verify on a physical device — and set FN_APNS_ENV=production at TestFlight release
<p>changed title from <code class="idiff"><span class="idiff left deletion">Nothing is v</span>erif<span class="idiff deletion">ied</span> on a physical device<span class="idiff right deletion">: push, VoIP, and the microphon</span>e</code> to <code class="idiff"><span class="idiff left addition">V</span>erif<span class="idiff addition">y</span> on a physical device<span class="idiff right addition"> — and set FN_APNS_ENV=production at TestFlight releas</span>e</code></p>
APNs environment: FN_APNS_ENV=production on the box, confirmed in the running relay's environment. That half is closed.
Physical-device testing has happened and found real defects, all since fixed: Face ID never prompting (a .task with no id: that took its early return while the scene was still inactive), the Files and wallet keychain read deadlocking against its own prompt on the main actor, the reaction row wrapping into an upside-down L, and a chat with no way to dismiss the keyboard at all.
Still genuinely unproven: a VoIP push ringing a locked iPhone. A simulator receives no push, so this cannot be closed from a test run — it needs someone holding a locked handset while a call comes in.
Partly done. Checked rather than assumed:
- **APNs environment**: `FN_APNS_ENV=production` on the box, confirmed in the running relay's environment. That half is closed.
- **Physical-device testing has happened** and found real defects, all since fixed: Face ID never prompting (a `.task` with no `id:` that took its early return while the scene was still inactive), the Files and wallet keychain read deadlocking against its own prompt on the main actor, the reaction row wrapping into an upside-down L, and a chat with no way to dismiss the keyboard at all.
**Still genuinely unproven**: a VoIP push ringing a *locked* iPhone. A simulator receives no push, so this cannot be closed from a test run — it needs someone holding a locked handset while a call comes in.
Correcting the scope of this: it has been run on two physical phones. The blanket 'never verified on a device' reading is wrong and was repeated back to the owner today as a reason not to invite testers.
What is still genuinely open is narrower, and worth keeping the issue for:
Ringing on a LOCKED iPhone, which the simulator cannot test at all. Not exercised on 0.15.0.
FN_APNS_ENV=production at TestFlight, per fnmsg-apns-env-sandbox-at-release.
Owner will test the locked-phone ring once Apple approves the next build.
Correcting the scope of this: it has been run on two physical phones. The blanket 'never verified on a device' reading is wrong and was repeated back to the owner today as a reason not to invite testers.
What is still genuinely open is narrower, and worth keeping the issue for:
- **Ringing on a LOCKED iPhone**, which the simulator cannot test at all. Not exercised on 0.15.0.
- **`FN_APNS_ENV=production` at TestFlight**, per `fnmsg-apns-env-sandbox-at-release`.
Owner will test the locked-phone ring once Apple approves the next build.
Closing this: the locked-phone ring works (owner, 2026-09-15)
The one thing left on this issue was a VoIP push ringing a locked iPhone,
which a simulator cannot test at all. The owner has now run it on a real
handset and reports it works.
Both halves of this issue are therefore done:
FN_APNS_ENV=production on the box, confirmed in the running relay's
environment on 2026-09-09.
The locked-phone ring, verified by the owner on a physical device today.
One standing hazard survives the issue and belongs with the release checklist
rather than here, so it is written into TESTFLIGHT.md section 5: the APNs
environment is per-relay, not per-token. A development build installed from
Xcode needs sandbox; TestFlight and the App Store need production. One
relay cannot serve both, and the wrong one fails as BadDeviceToken, which
from the outside looks like calls not ringing rather than like a configuration
mistake.
## Closing this: the locked-phone ring works (owner, 2026-09-15)
The one thing left on this issue was a VoIP push ringing a **locked** iPhone,
which a simulator cannot test at all. The owner has now run it on a real
handset and reports it works.
Both halves of this issue are therefore done:
- `FN_APNS_ENV=production` on the box, confirmed in the running relay's
environment on 2026-09-09.
- The locked-phone ring, verified by the owner on a physical device today.
One standing hazard survives the issue and belongs with the release checklist
rather than here, so it is written into `TESTFLIGHT.md` section 5: the APNs
environment is **per-relay, not per-token**. A development build installed from
Xcode needs `sandbox`; TestFlight and the App Store need `production`. One
relay cannot serve both, and the wrong one fails as `BadDeviceToken`, which
from the outside looks like calls not ringing rather than like a configuration
mistake.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Three things cannot be exercised on a simulator at all, and all three produced real
bugs on 2026-09-01:
silentbackground push are both unverified in the only environment where they run.push. The "New message" banners appearing during calls were specifically that
fallback path. A real device would not have taken it.
never been observed working end to end.
Done looks like: one call, one message, one voice note and one backgrounded
notification, between two physical devices, before any build goes to a tester.
mentioned in commit
bd7b7c96e4Release-time step, added 2026-09-01:
FN_APNS_ENV=production🔴 The moment Apple approves and the build goes out via TestFlight, the relay
must be told, or nobody's iPhone rings and nothing says why.
Entrypoint.swiftresolves the APNs environment as:FN_APNS_ENVis not set in/srv/prod/messenger/.envtoday, so the relay is onsandbox — checked 2026-09-01. That default is deliberate and right for a build you
install yourself, and wrong for TestFlight.
Why it will not be obvious. A production push to a sandbox token — or the reverse —
fails with
BadDeviceToken, and the error does not say which way round it is. From theoutside it reads as "calls don't ring on a locked phone", which is indistinguishable from
a bug in the call path. The relay's own source carries a warning about exactly this.
Steps at release:
FN_APNS_ENV=productionin/srv/prod/messenger/.envAPNs wake-ups enabled for net.flamenet.messenger (production)⚠️ Order matters: a development build on a physical device needs sandbox. If both a
dev device and TestFlight are in play at once, one of them will not ring, and the relay
cannot serve both — the environment is per-relay, not per-token.
ℹ️
FN_APNS_TOPICis also unset and defaults tonet.flamenet.messenger, which matchesthe shipping bundle id. Correct today, but a default rather than a decision.
What is already in place (verified 2026-09-01)
Info.plistdeclares bothvoipandremote-notificationbackground modesVoIPCallReceiver+ CallKit are merged tomain(8265298)secrets/apns-key.p8, withFN_APNS_KEY_IDandFN_APNS_TEAM_IDsetMessengerServiceusesCallStyle,CATEGORY_CALLandsetFullScreenIntent(full, true), ships in 0.8, and is testable nowA simulator cannot test any of it. It never receives a VoIP token, so the relay falls
back to an ordinary alert push — which is the exact path that produced the spurious
"New message" banners during calls on 2026-09-01. The simulator does not merely fail to
exercise this; it exercises the wrong branch.
changed title from
Nothing is verified on a physical device: push, VoIP, and the microphonetoVerify on a physical device — and set FN_APNS_ENV=production at TestFlight releasementioned in issue #13
Partly done. Checked rather than assumed:
FN_APNS_ENV=productionon the box, confirmed in the running relay's environment. That half is closed..taskwith noid:that took its early return while the scene was still inactive), the Files and wallet keychain read deadlocking against its own prompt on the main actor, the reaction row wrapping into an upside-down L, and a chat with no way to dismiss the keyboard at all.Still genuinely unproven: a VoIP push ringing a locked iPhone. A simulator receives no push, so this cannot be closed from a test run — it needs someone holding a locked handset while a call comes in.
Correcting the scope of this: it has been run on two physical phones. The blanket 'never verified on a device' reading is wrong and was repeated back to the owner today as a reason not to invite testers.
What is still genuinely open is narrower, and worth keeping the issue for:
FN_APNS_ENV=productionat TestFlight, perfnmsg-apns-env-sandbox-at-release.Owner will test the locked-phone ring once Apple approves the next build.
Closing this: the locked-phone ring works (owner, 2026-09-15)
The one thing left on this issue was a VoIP push ringing a locked iPhone,
which a simulator cannot test at all. The owner has now run it on a real
handset and reports it works.
Both halves of this issue are therefore done:
FN_APNS_ENV=productionon the box, confirmed in the running relay'senvironment on 2026-09-09.
One standing hazard survives the issue and belongs with the release checklist
rather than here, so it is written into
TESTFLIGHT.mdsection 5: the APNsenvironment is per-relay, not per-token. A development build installed from
Xcode needs
sandbox; TestFlight and the App Store needproduction. Onerelay cannot serve both, and the wrong one fails as
BadDeviceToken, whichfrom the outside looks like calls not ringing rather than like a configuration
mistake.
mentioned in commit
0dfb2692fe