Menu ▾ ▴

#80 New-message notifications: badge updates but no notification is delivered (real messages), despite granted authorization

closed
nobody
2026-07-18
2026-07-10
Anonymous
No

Originally created by: kristofferR

Symptom

A real Messenger message arrived (2026-07-10 ~06:05, official v1.2.3, notarized, running since ~02:13): the unread badge updated, but no notification was delivered at all — nothing presented, and nothing in Notification Center. Carrier.log showed notification authorization granted at launch and no errors.

What the investigation ruled out

  • In-app DND schedule — was unset; the feature is being removed anyway (#79) since the OS owns notification suppression.
  • mute_notifications / hide_notification_preview — both off.
  • macOS Focus — a scheduled Sleep Focus was active at the time, but Focus delivers quietly to Notification Center; NC was empty, so the notification never reached the system at all.
  • Authorization / LaunchServices state (#76/#77) — auth is granted and the LS refresh works; stale registrations from previously mounted DMGs persist in the LS database even after unmount, but the startup LSRegisterURL wins the lookup.
  • The whole native pipeline — verified working end-to-end: a synthetic notification fired through the shimmed Notification constructor (new dev-only __carrier_mcp_notify_test__ command in mcp-bridge.js) was delivered and presented:
    usernoted: Delivering <NotificationRecord app:"io.github.kristofferr.carrier" ...> to [ .alert .lockScreen .notificationCenter ] usernoted: Presenting <NotificationRecord ...> as banner (["badge", "sound", "alert"])

Finding along the way: grants are keyed to the code signature

Installing an ad-hoc-signed debug build over /Applications/Carrier.app immediately broke notifications: both requestAuthorization and addNotificationRequest fail with UNErrorDomain error 1 (notificationsNotAllowed). Rebuilding the same code signed with the Developer ID identity restored the grant. Official builds are unaffected, but local debug builds used for notification work must be signed with APPLE_SIGNING_IDENTITY.

Remaining suspect

The badge rides document.title — a separate path — so it only proves Facebook's JS saw the message. The one unverified link is Facebook's page code never constructing a Notification for the real message (most plausibly gated on page visibility, or an FB-side notification setting for this browser/session).

Instrumentation added (on the [#79] branch)

Content-free breadcrumbs now cover every hop, so the next real-message failure pinpoints itself in Carrier.log:

  • page: rate-limited notify.fired diag when Facebook constructs a Notification (includes document.visibilityState)
  • backend: carrier:notify received (id N) receipt + payload parse failures
  • macOS delivery: addNotificationRequest completion errors are logged (previously swallowed)

Next step

Reproduce with a real incoming message (window visible vs. hidden) on the instrumented build and read the breadcrumbs:

  • no notify.fired → Facebook never fired; investigate FB-side gating
  • notify.fired but no carrier:notify received → emit/IPC hop broken
  • received but delivery error → native side, error now logged

Ref [#76], [#77], [#79]

Related

Tickets: #110
Tickets: #111
Tickets: #135
Tickets: #76
Tickets: #77
Tickets: #79
Tickets: #84
Tickets: #86

Discussion

  • Anonymous

    Anonymous - 2026-07-18

    Ticket changed by: kristofferR

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-07-18

    Originally posted by: kristofferR

    Resolved in [#137] (merged to main). The root cause identified in this investigation — Facebook's page code not constructing a Notification for a real incoming message (the badge updates via document.title, but no Web Notification ever fires) — is now covered by the row-driven fallback delivery path: the injected bridge watches the conversation list and fires a native notification when an unread conversation's preview changes, independent of whether Facebook constructs a Notification. So "badge updates but no banner" for a genuinely new message now delivers.

    The two concrete failure modes filed off this issue were also fixed (#109 pairing, [#110] the auto-refresh swallow race), and the content-free Carrier.log breadcrumbs from the [#79] instrumentation shipped, so any residual gap still pinpoints itself.

     

    Related

    Tickets: #110
    Tickets: #137
    Tickets: #79


Log in to post a comment.