Skip to content

feat(ios): add background alive beacon support#63123

Closed
ngutman wants to merge 2 commits into
mainfrom
fix/ios-background-alive-beacon
Closed

feat(ios): add background alive beacon support#63123
ngutman wants to merge 2 commits into
mainfrom
fix/ios-background-alive-beacon

Conversation

@ngutman

@ngutman ngutman commented Apr 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Problem: iOS background wakes did not give the gateway a durable, acked way to record that a node was recently alive unless a live websocket reconnect happened.
  • Why it matters: operators could not distinguish "currently connected" from "recently seen alive," and iOS background wake opportunities were being spent on reconnect attempts without a durable last-seen signal.
  • What changed: added durable lastSeenAtMs / lastSeenReason metadata, a node.presence.alive event path, and iOS background wake logic that reconnects briefly if needed and sends an acked alive beacon.
  • What did NOT change (scope boundary): connected / connectedAtMs still mean live websocket state now; this does not try to keep a persistent background socket alive.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: iOS wake paths only did best-effort reconnect work and the gateway only persisted durable node activity on live connect, so short background wake opportunities had no durable recent-alive breadcrumb.
  • Missing detection / guardrail: there was no durable last-seen field or acked node-auth beacon contract for iOS background wakes.
  • Contributing context (if known): iOS background execution is OS-controlled, so relying on live websocket state alone misses successful wake opportunities that do not remain connected.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/gateway/server-node-events.test.ts
    • src/gateway/node-catalog.test.ts
    • src/infra/node-pairing.test.ts
    • apps/ios/Tests/NodeBackgroundAliveBeaconTests.swift
    • apps/ios/Tests/NodeBackgroundAliveE2ETests.swift
  • Scenario the test should lock in: durable last-seen persistence/surfacing, connect-derived last-seen updates, and iOS background beacon throttling/reconnect behavior.
  • Why this is the smallest reliable guardrail: the gateway contract and iOS wake logic are the narrowest seams that express the bug.
  • Existing test that already covers this (if any): N/A
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • Nodes can now surface durable lastSeenAtMs / lastSeenReason even when not currently connected.
  • iOS background refresh, silent push, and significant location wake paths now try to deliver an acked alive beacon instead of only kicking reconnect work.

Diagram (if applicable)

Before:
[iOS background wake] -> [best-effort reconnect attempt] -> [maybe live connect] -> [no durable recent-alive signal unless connected]

After:
[iOS background wake] -> [brief reconnect if needed] -> [node.presence.alive ack] -> [gateway persists lastSeen*]

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (Yes)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: adds a node-authenticated node.event usage for node.presence.alive; it does not require operator auth and keeps connected semantics unchanged.

Repro + Verification

Environment

  • OS: macOS 15 + iOS Simulator 18.6
  • Runtime/container: Node 24 / pnpm
  • Model/provider: N/A
  • Integration/channel (if any): local gateway + iOS app
  • Relevant config (redacted): local loopback gateway config under .local/ios-background-alive-e2e/

Steps

  1. Start a local dev gateway.
  2. Connect the iOS app / test node to that gateway.
  3. Trigger connect/background-alive paths and inspect node.list plus targeted tests.

Expected

  • The gateway preserves live connected semantics.
  • The gateway also persists durable lastSeenAtMs / lastSeenReason for recent-alive updates.

Actual

  • Verified as expected via targeted TS tests, local gateway inspection, and iOS logic coverage.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios:
    • targeted gateway tests pass for node event + node catalog persistence
    • xcodebuild test -scheme OpenClawLogicTests ... passes
    • local gateway node.list inspection matches expected last-seen fields
  • Edge cases checked:
    • offline node-pair metadata surfacing
    • device-pair fallback surfacing
    • background-beacon throttle helper behavior
  • What you did not verify:
    • reliable real silent-push wake delivery in Simulator
    • clean app-hosted OpenClawTests execution in Simulator (build-for-testing succeeded, but the test runner hung before connecting)

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: iOS background execution is best-effort and OS-controlled.
    • Mitigation: the design uses existing wake paths plus acked beacon delivery rather than promising persistent connectivity.
  • Risk: Simulator background validation is incomplete for silent push.
    • Mitigation: targeted logic and gateway coverage were added, and local gateway verification was performed.

@aisle-research-bot

aisle-research-bot Bot commented Apr 8, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Unbounded presenceKey tracking enables memory/disk I/O DoS via node.presence.alive flooding
2 🟡 Medium Unbounded node-controlled trigger persisted to pairing metadata (potential disk/memory DoS)
1. 🟡 Unbounded presenceKey tracking enables memory/disk I/O DoS via `node.presence.alive` flooding
Property Value
Severity Medium
CWE CWE-400
Location src/gateway/server-node-events.ts:690-718

Description

The new node.presence.alive event handling introduces an in-memory throttle map (recentNodePresencePersistAt) keyed by presenceKey = deviceId ?? nodeId. This key ultimately derives from the connecting client's connect.client.id / connect.device.id and can be made high-cardinality by repeatedly reconnecting with new IDs.

Impact:

  • Unbounded memory growth: recentNodePresencePersistAt is never pruned and will grow with every new presenceKey observed.
  • Disk/CPU I/O amplification even for unknown/unpaired IDs: the handler calls updatePairedNodeMetadata() (and optionally updatePairedDeviceMetadata()), which load pairing state from disk before discovering the ID is not paired. Because the throttle is per-presenceKey, an attacker can bypass the 60s interval by using many distinct IDs and force repeated JSON reads/lock contention.

Vulnerable code:

const presenceKey = opts?.deviceId ?? nodeId;
const lastPersistedAt = recentNodePresencePersistAt.get(presenceKey) ?? 0;
...
await Promise.all([
  updatePairedNodeMetadata(nodeId, { ... }),
  ...(opts?.deviceId ? [updatePairedDeviceMetadata(opts.deviceId, { ... })] : []),
]);
recentNodePresencePersistAt.set(presenceKey, receivedAtMs);

Recommendation

Mitigate high-cardinality key abuse and avoid expensive work for unknown IDs:

  1. Bound and prune recentNodePresencePersistAt (e.g., LRU with a max size + TTL cleanup).
  2. Only record throttle entries after confirming the node/device exists and was updated, and skip loading/persisting for unpaired IDs.
  3. Add rate limiting for node.event / node.presence.alive at the transport layer (per-connection/per-IP) to prevent reconnect+flood patterns.

Example (sketch):

// 1) Maintain a bounded LRU/TTL cache
const recentNodePresencePersistAt = new LruCache<string, number>({ max: 10_000, ttl: 10 * 60_000 });// 2) Verify paired node/device first (or provide a lightweight exists check)
const pairedNode = await getPairedNode(nodeId);
if (!pairedNode) return;// 3) Throttle and persist
const key = opts?.deviceId ?? nodeId;
const last = recentNodePresencePersistAt.get(key) ?? 0;
if (Date.now() - last < NODE_PRESENCE_PERSIST_MIN_INTERVAL_MS) return;
await updatePairedNodeMetadata(nodeId, patch);
recentNodePresencePersistAt.set(key, Date.now());
2. 🟡 Unbounded node-controlled `trigger` persisted to pairing metadata (potential disk/memory DoS)
Property Value
Severity Medium
CWE CWE-400
Location src/gateway/server-node-events.ts:690-713

Description

The gateway handles node.presence.alive events by extracting obj.trigger from attacker-controlled payloadJSON and persisting it as lastSeenReason for both the node and (optionally) the paired device.

  • Input: evt.payloadJSON comes from the node via node.event.
  • Transformation: normalizeOptionalString(obj.trigger) only trims whitespace; it does not enforce an allowlist or maximum length.
  • Sink: the value is persisted to the pairing state via updatePairedNodeMetadata(...lastSeenReason: trigger) / updatePairedDeviceMetadata(...lastSeenReason: trigger), which writes to disk.

A malicious/compromised node can send a very large trigger string (e.g., megabytes). Even though persistence is throttled to once per minute, the gateway will still write the oversized value to disk, potentially causing:

  • disk usage bloat (pairing JSON state file grows dramatically)
  • increased memory/CPU usage on subsequent loads/serializations
  • degraded performance or denial of service

Vulnerable code:

const trigger = normalizeOptionalString(obj.trigger) ?? "background";
...
updatePairedNodeMetadata(nodeId, { lastSeenReason: trigger })
...
updatePairedDeviceMetadata(opts.deviceId, { lastSeenReason: trigger })

Recommendation

Constrain trigger before persisting:

  1. Enforce a strict allowlist (preferred) or a conservative max length.
  2. Optionally, normalize to a fixed set of known reasons.

Example fix (allowlist + fallback):

const rawTrigger = normalizeOptionalString(obj.trigger) ?? "background";
const allowed = new Set(["background","silent_push","bg_app_refresh","significant_location","connect"]);
const trigger = allowed.has(rawTrigger) ? rawTrigger : "background";

If you must accept arbitrary strings, cap length:

const trigger = (normalizeOptionalString(obj.trigger) ?? "background").slice(0, 64);

This prevents oversized payloads from bloating persisted state and reduces downstream risk if the value is later displayed in UIs/logs.


Analyzed PR: #63123 at commit 8935187

Last updated on: 2026-04-08T11:31:15Z

@openclaw-barnacle openclaw-barnacle Bot added app: ios App: ios gateway Gateway runtime size: L maintainer Maintainer-authored PR labels Apr 8, 2026
@greptile-apps

greptile-apps Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds durable lastSeenAtMs/lastSeenReason metadata to the node catalog and pairing records, and wires up iOS background wake paths (background refresh, silent push, significant location) to send an acked node.presence.alive beacon to the gateway. The new resolveEffectiveLastSeen catalog logic correctly picks the most-recent timestamp across the live session, node-pairing, and device-pairing sources.

One minor inconsistency in the new updatePairedDeviceMetadata function: the read uses normalizeDeviceId(deviceId) but the write uses the raw deviceId parameter. While device IDs from current callers are derived from cryptographic keys and won't have whitespace, the parallel updatePairedNodeMetadata in node-pairing.ts uses a single normalized variable for both read and write and should be followed here too.

Confidence Score: 5/5

Safe to merge; the single finding is a minor write-key normalization inconsistency with no impact on current callers.

All remaining findings are P2. The updatePairedDeviceMetadata write-key inconsistency is a real code issue but device IDs from authenticated sessions are crypto-derived and never contain whitespace, so it cannot manifest with existing callers. The rest of the change is well-structured with good test coverage of the gateway contract, catalog merge logic, and iOS throttle behavior.

src/infra/device-pairing.ts — write key in updatePairedDeviceMetadata should use normalizeDeviceId(deviceId) for consistency.

Vulnerabilities

No security concerns identified. The new node.presence.alive event path reuses the existing node-auth node.event contract and does not require operator privileges. The trigger value written to persistent storage is passed through normalizeOptionalString before use. No secrets, tokens, or new network attack surfaces are introduced.

Comments Outside Diff (1)

  1. src/infra/device-pairing.ts, line 738 (link)

    P2 Write key not normalized

    The read on line 732 uses normalizeDeviceId(deviceId) (which trims whitespace), but the write on line 738 uses the raw deviceId. If deviceId ever contains leading/trailing whitespace the read would find the existing entry under the trimmed key but the write would create a new orphaned entry under the un-normalized key, leaving the original record stale.

    Compare the parallel updatePairedNodeMetadata in node-pairing.ts, which correctly keeps a single normalized variable and uses it for both the read and the write.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/infra/device-pairing.ts
    Line: 738
    
    Comment:
    **Write key not normalized**
    
    The read on line 732 uses `normalizeDeviceId(deviceId)` (which trims whitespace), but the write on line 738 uses the raw `deviceId`. If `deviceId` ever contains leading/trailing whitespace the read would find the existing entry under the trimmed key but the write would create a new orphaned entry under the un-normalized key, leaving the original record stale.
    
    Compare the parallel `updatePairedNodeMetadata` in `node-pairing.ts`, which correctly keeps a single `normalized` variable and uses it for both the read and the write.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/infra/device-pairing.ts
Line: 738

Comment:
**Write key not normalized**

The read on line 732 uses `normalizeDeviceId(deviceId)` (which trims whitespace), but the write on line 738 uses the raw `deviceId`. If `deviceId` ever contains leading/trailing whitespace the read would find the existing entry under the trimmed key but the write would create a new orphaned entry under the un-normalized key, leaving the original record stale.

Compare the parallel `updatePairedNodeMetadata` in `node-pairing.ts`, which correctly keeps a single `normalized` variable and uses it for both the read and the write.

```suggestion
    state.pairedByDeviceId[normalizeDeviceId(deviceId)] = {
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: resolve background alive beacon rev..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c496da5a85

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/ios/Sources/Model/NodeAppModel.swift
@ngutman

ngutman commented Apr 8, 2026

Copy link
Copy Markdown
Member Author

Addressed the current review comments in 89351872e04ff36892e58c5f20daebea5c759558.

What changed:

  • hardened pairing-state map handling in src/infra/device-pairing.ts and src/infra/node-pairing.ts
    • null-prototype records on load
    • reserved pairing-map keys rejected
    • normalized device metadata writes now use the normalized key consistently
  • stopped using self-declared instanceId as a writable last-seen alias
  • changed node.presence.alive to update paired-device metadata only from the authenticated deviceId
  • added gateway-side throttling for repeated node.presence.alive persistence writes
  • changed iOS wake handling so recent_success counts as a handled wake for background scheduling while remaining applied=false

Added/updated coverage for:

  • normalized paired-device metadata writes
  • reserved device-id rejection
  • no paired-device metadata write without authenticated device id
  • throttled repeated alive-beacon persistence
  • handled-success semantics for recent_success

Verification rerun:

  • pnpm test src/gateway/server-node-events.test.ts src/gateway/node-catalog.test.ts src/infra/node-pairing.test.ts src/infra/device-pairing.test.ts
  • pnpm check
  • pnpm build
  • xcodebuild test -scheme OpenClawLogicTests -destination 'platform=iOS Simulator,id=95882E7B-EDC3-4B64-84AD-DE6E603A76C7'

@ngutman

ngutman commented Apr 8, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89351872e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

self.pushWakeLogger.info("Wake reconnect trigger applied wakeId=\(wakeId, privacy: .public)")
return makeResult(true, "reconnect_triggered")
let nowMs = Int(Date().timeIntervalSince1970 * 1000)
let lastSuccessAtMs = UserDefaults.standard.object(forKey: Self.backgroundAliveBeaconLastSuccessAtMsKey) as? Int

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.

P2 Badge Scope alive-beacon throttle key by gateway stable ID

This throttle check reads a single global UserDefaults timestamp, so a successful beacon for one gateway profile can suppress beacon delivery for another profile for 10 minutes when users switch gateways while still connected. In that case the wake is marked recent_success and node.event is skipped, so the newly active gateway never receives a fresh lastSeen update. Store/read this timestamp per active gateway identity (for example keyed by cfg.stableID or effective stable ID) rather than globally.

Useful? React with 👍 / 👎.

Comment on lines +699 to +700
if (receivedAtMs - lastPersistedAt < NODE_PRESENCE_PERSIST_MIN_INTERVAL_MS) {
return;

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.

P3 Badge Handle backward clock jumps in presence throttle

This rate-limit check assumes wall-clock monotonicity; if system time moves backward (for example NTP correction), receivedAtMs - lastPersistedAt becomes negative and the branch will keep dropping node.presence.alive events until time catches up. That can suppress legitimate liveness persistence for an extended period. Add a monotonic guard (for example only throttle when receivedAtMs >= lastPersistedAt) or reset stale entries when the clock regresses.

Useful? React with 👍 / 👎.

@steipete steipete left a comment

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.

Thanks. I reviewed current head 89351872e04ff36892e58c5f20daebea5c759558 against current main.

What this PR does: adds durable lastSeenAtMs / lastSeenReason for node/device pairing records, handles node.presence.alive through node.event, surfaces that in the node catalog, and changes the iOS silent-push/background-refresh/significant-location wake paths to reconnect briefly and publish an acked alive beacon. The underlying gap is real: current main only calls reconnectGatewaySessionsForSilentPushIfNeeded from the iOS wake handlers (apps/ios/Sources/Model/NodeAppModel.swift:3105, :3155, :3173) and gateway node.event has no durable presence/last-seen path (src/gateway/server-methods/nodes.ts:1128).

I’m requesting changes because the two persistence hardening issues are still not fully closed:

  1. src/gateway/server-node-events.ts:690-718: trigger is still arbitrary and unbounded. The handler uses normalizeOptionalString(obj.trigger) ?? "background" (:695), and normalizeOptionalString only trims/coerces strings (src/shared/string-coerce.ts:5-15). That value is then persisted as lastSeenReason into node/device pairing JSON (src/infra/node-pairing.ts:316-349, src/infra/device-pairing.ts:737-766). A compromised or misconfigured node can still write a very large reason string into durable pairing state once per throttle interval, which means Aisle’s trigger finding is not really fixed. Best fix: strict allowlist the expected reasons (background, silent_push, bg_app_refresh, significant_location, and any intentionally supported connect-style value) or cap to a small fixed length before persistence, with a regression test for oversized/unknown triggers.

  2. src/gateway/server-node-events.ts:43-47 and :690-718: recentNodePresencePersistAt is still an unbounded module-level Map. The key is opts?.deviceId ?? nodeId (:697); node.event only passes the authenticated device id when present (src/gateway/server-methods/nodes.ts:1095-1142), otherwise the fallback nodeId remains the connect device/client id path (current src/gateway/node-registry.ts:45-48). Both updatePairedNodeMetadata and updatePairedDeviceMetadata return without throwing for unknown ids (src/infra/node-pairing.ts:321-326, src/infra/device-pairing.ts:744-749), so the handler can still set throttle entries for unpaired/high-cardinality ids after the disk-backed lookup resolves. Best fix: bound/TTL/LRU this cache and avoid recording throttle entries unless an approved node/device actually existed or was updated. Please add coverage that unknown/high-cardinality presence ids do not grow the cache or drive unbounded pairing-file work.

The red checks-node-test job is a broad extension-shard heap OOM, so I’m not treating that alone as evidence this PR broke tests. After the fixes and rebase, though, this needs fresh targeted gateway/infra tests plus the relevant changed gate.

@steipete

Copy link
Copy Markdown
Contributor

Thanks again for opening this. I rebuilt the feature as #73330 so we keep the useful iOS background alive signal while tightening the protocol boundary:

  • node.presence.alive is an additive node.event event, not a freeform trigger sink.
  • last-seen persistence only happens for authenticated, approved device/node identities.
  • reason values are closed/normalized, persistence is throttled, and the in-memory throttle map is bounded.
  • docs and regression tests are included.

I kept attribution in the changelog and commit co-author trailer.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 14, 2026
@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Keep open only because this is a member-authored, maintainer-labeled PR; technically, the branch is superseded by the merged replacement at #73330 and remains a worse landing path because it still carries security/availability issues.

Root-cause cluster
Relationship: superseded
Canonical: #73330
Summary: This PR and #73330 address the same iOS background alive-beacon feature; the latter merged as the canonical implementation, while #73373 is Android parity on the same protocol.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Canonical path: Close this PR as superseded by #73330.

So I’m closing this here and keeping the remaining discussion on #73330.

Review details

Best possible solution:

Close this PR as superseded by #73330.

Do we have a high-confidence way to reproduce the issue?

Yes for the review findings: the PR diff shows the freeform trigger persistence and unbounded throttle map directly. I did not run a live iOS/gateway scenario in this read-only cleanup review.

Is this the best way to solve the issue?

No, this branch is no longer the best way to solve the issue. The merged replacement is narrower and safer because it uses authenticated device identity, closed reason normalization, bounded throttling, docs, and regression tests.

Security review:

Security review needs attention: The stale branch still has concrete resource-exhaustion concerns in the node-authenticated presence persistence path; the merged replacement addresses the same class of issues.

  • [medium] Freeform Presence Reason Persistence — src/gateway/server-node-events.ts:695
    The PR branch can persist arbitrary node-controlled trigger strings into pairing metadata as lastSeenReason, which can bloat durable state and downstream loads.
    Confidence: 0.89
  • [medium] Unbounded Presence Throttle Tracking — src/gateway/server-node-events.ts:43
    The PR branch keeps recentNodePresencePersistAt as an unbounded module-level map, allowing high-cardinality authenticated node traffic to grow memory and drive repeated metadata lookups.
    Confidence: 0.86

AGENTS.md: found and applied where relevant.

What I checked:

  • linked superseding PR: feat: add authenticated iOS background presence beacon #73330 (feat: add authenticated iOS background presence beacon) is merged at 2026-04-28T07:10:36Z.
  • cluster evidence: the durable review links that PR in the work cluster or recommended risk path.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • steipete: Authored the merged replacement PR that carried forward this PR's useful behavior with tighter protocol and persistence boundaries, and left the maintainer review explaining the remaining hardening issues on this branch. (role: replacement implementer and reviewer; confidence: high; commits: 8234825675d8, 23ef816dc468, bdba90a20b2b; files: src/gateway/server-node-events.ts, src/shared/node-presence.ts, apps/ios/Sources/Push/BackgroundAliveBeacon.swift)
  • ngutman: Authored this PR's branch commits and is credited as co-author on the merged replacement commit, making them relevant to original intent and source provenance. (role: original source contributor and replacement co-author; confidence: high; commits: c496da5a8518, 89351872e04f, 8234825675d8; files: apps/ios/Sources/Model/NodeAppModel.swift, src/gateway/server-node-events.ts, src/gateway/node-catalog.ts)

Codex review notes: model internal, reasoning high; reviewed against 010b61746379.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 18, 2026
@clawsweeper clawsweeper Bot added P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 26, 2026
@steipete

Copy link
Copy Markdown
Contributor

Thanks again for the original background-presence work. This is superseded by #73330, merged as bdba90a and shipped in v2026.4.27.

The replacement preserves the useful iOS wake beacon while requiring authenticated device identity, normalizing reasons to a closed set, returning meaningful handling results, and bounding persistence throttling. Contributor credit was preserved there, so this older branch is no longer the landing path.

@steipete steipete closed this Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: ios App: ios gateway Gateway runtime maintainer Maintainer-authored PR merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: L status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants