Skip to content

fix(relay): preserve conversation routing in iOS push responses - #16350

Open
dannyclifford wants to merge 2 commits into
pingdotgg:mainfrom
dannyclifford:up/ios-notification-routing
Open

dannyclifford wants to merge 2 commits into
pingdotgg:mainfrom
dannyclifford:up/ios-notification-routing

Conversation

@dannyclifford

@dannyclifford dannyclifford commented Oct 6, 2026 •

Copy link
Copy Markdown

Problem

Ordinary remote iOS alerts can arrive without opening their thread when tapped. The relay places routing metadata only at the payload root. In the pinned expo-notifications@58.0.11, NotificationRecords.serializedNotificationData reads userInfo["body"] into notification.content.data; the mobile extractor reads that data.

Reproduction at 3a9c1a6df1: build an ordinary alert with environment/thread IDs and a thread deep link, then apply the pinned remote-data selection. The root-only payload supplies no data dictionary, so the existing extractor returns no route. The metadata remains available in request.trigger.payload, but the extractor does not read it.

Change

Move the routing dictionary under body, which the current mobile consumer reads. Alert text remains in aps.alert.body. Avoid duplicating the root keys: a sanitized long-ID fixture otherwise grows to 4,603 bytes and exceeds APNs’ 4,096-byte limit.

A client fallback to trigger.payload is possible; fixing the producer lets existing compatible clients use their current extractor without an app update. Correct route data also restores the existing foreground rule that silences alerts for the thread already on screen. Other-thread/overview behavior and notification preferences are unchanged.

Scope and approval

This repairs existing notification-to-thread behavior documented in docs/user/mobile-notifications.md; it adds no new workflow or setting. It is proposed under the small, focused obvious-bug exception, with no prior maintainer approval claimed.

Only the APNs payload builder and its regression tests change. This is separate from the alert-selection work in #11073 and #15015 and the native response races in #15030. It does not replace those fixes. No native, dependency, schema or infrastructure configuration changes.

Verification

  • Added payload-contract assertion: fails on the base (1 failed, 11 passed); passes with the fix. This models the inspected SDK contract, not native execution.
  • vp test run infra/relay/src/agentActivity/ApnsClient.test.ts apps/mobile/src/features/agent-awareness/notificationNavigation.test.ts apps/mobile/src/features/agent-awareness/foregroundNotificationBehavior.test.ts: 27 passed.
  • Long-ID payload regression: fails at 4,603 bytes with duplicated routing fields; passes after moving them under body. This checks a concrete size regression, not a universal size bound for arbitrary input.
  • vp run --filter t3code-relay typecheck, targeted lint and formatting: passed.
  • Supplementary physical before/after: an own-brand development-signed iPhone 14 Plus using sandbox APNs received an ordinary alert while locked/backgrounded with Wi-Fi off. Tapping first opened home; after this relay-only change, the user confirmed it opened the originating thread. fix(relay): keep concurrent threads from hiding iOS push alerts #11073 was already deployed before the failing tap. This was not a pristine-upstream device run and preceded removal of the redundant root fields; that refinement has local regression coverage only.

Official store/production APNs, cold-start and foreground device behavior, and historical official binaries were not tested. The relay needs deployment for newly constructed payloads to benefit; already-delivered alerts retain their original data.

Prepared with GPT-6 Astra in the Codex harness. Independent local review: GPT-6.1 Sol in the Codex harness.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Oct 6, 2026
Comment on lines 195 to 197
environmentId: input.notification.environmentId,
threadId: input.notification.threadId,
deepLink: input.notification.deepLink,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High agentActivity/ApnsClient.ts:195

Valid notifications with sufficiently long environmentId and threadId are rejected by APNs because this payload duplicates both values and exceeds the 4096-byte limit. The existing root fields already carry these values, so remove them when adding the Expo-compatible body copy.

-      environmentId: input.notification.environmentId,
-      threadId: input.notification.threadId,
-      deepLink: input.notification.deepLink,
🤖 Copy this AI Prompt to have your agent fix this:
In file @infra/relay/src/agentActivity/ApnsClient.ts around lines 195-197:

Valid notifications with sufficiently long `environmentId` and `threadId` are rejected by APNs because this payload duplicates both values and exceeds the 4096-byte limit. The existing root fields already carry these values, so remove them when adding the Expo-compatible `body` copy.

@macroscopeapp

macroscopeapp Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Would Approve

Macroscope's review found this PR approvable — This is a focused relay bug fix that restores iOS notification routing by moving metadata into the Expo-compatible payload location, with regression coverage and no schema, infrastructure, security, billing, or default-setting changes. The final code also avoids duplicating routing fields and verifies the APNs payload-size limit; the supplied High finding remains an independent blocking signal.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 6, 2026

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 236dd2fc-391a-4996-9135-b7c2aa2cc861
📥 Commits

Reviewing files that changed from the base of the PR and between 68e50db and 27229bb.

📒 Files selected for processing (2)
  • infra/relay/src/agentActivity/ApnsClient.test.ts
  • infra/relay/src/agentActivity/ApnsClient.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The APNs payload now places environmentId, threadId, and deepLink under body. Tests check the routing metadata and the serialized size of a sanitized payload.

Changes

APNs notification payload

Layer / File(s) Summary
Add and verify routing metadata
infra/relay/src/agentActivity/ApnsClient.ts, infra/relay/src/agentActivity/ApnsClient.test.ts
The payload nests routing metadata under body. Tests verify the metadata and check that a sanitized payload with long multibyte content is no larger than 4096 bytes.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to 27229

The change intends to expose conversation routing data in iOS notifications. No concrete routing failure is established by the available evidence, so no actionable merge risk remains.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 27229

The change preserves existing delivery controls and restricts navigation to conversation routes. No privilege escalation or additional data disclosure was established, but the native compatibility premise remains unverified and long links can retain an altered conversation identity.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The effective new behavior is conversation navigation and foreground alert suppression on receiving iOS clients, conditional on native data mapping. The builder sends the same routing values to the same supplied device token; relocation does not itself broaden recipients, add credentials, or introduce an external-URL destination.

Trust Boundaries and Controls

  • observed — The delivery path sanitizes the notification before construction. For queued jobs, it retains checks for the current user-associated device and token, activity freshness, and notification preferences before sending. On mobile, explicit links must normalize to a thread route; the inspected screen requires the selected thread's environment-scoped identity to match the route before rendering thread content.

Resilience and Maintainability Implications

  • observed — Payload construction introduces no shared-state transition. The existing mobile response handler deduplicates synchronously using a component-owned set, marking an identifier before navigation. Initial-response routing failures return before clearing the native response, while the already-marked identifier can prevent another attempt during that component lifetime. The PR does not modify this lifecycle or claim to repair its recovery behavior.

Hardening Proposals

  • proposed — Preserve routing identity by deriving the thread route from intact IDs or rejecting a link whose decoded identity disagrees with those IDs. This would contain the documented truncation mismatch; it is a hardening proposal, not a verified authorization finding.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the relay fix and its purpose: preserving conversation routing in iOS push responses.
Description check ✅ Passed The description covers the required Problem, Change, Scope and approval, and Verification sections. It explains the bug, the payload change, the scope exception, test results, and verification limits.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 11, 2026 — with ChatGPT Codex Connector

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants