Repository navigation
fix(notifications): hold the push-to-start fence across token rotation - #6689
Merged
Merged
Conversation
Surface: the mobile app (apps/mobile), the iOS Live Activity. An iOS Live Activity becomes expanded, and vibrates, with no user action. This must never happen. A Live Activity is subtle: it sits collapsed and it stays silent. Build on the in-flight client fix, #6483 (branch `kwf/owner-live-activity-strays-c7`). That branch already touches `apps/mobile/src/glanceable-ios/adopt-activity.ts`, `ios-sink.ts`, `register.ts` and `lib/glanceable/persist.ts`. Reproduce this defect on top of it. Required behaviour: - The card stays collapsed until the user taps it. - No haptic and no vibration from a Live Activity update, whatever the trigger. - No alert and no sound. - An update changes the numbers only. It never changes the presentation state on its own. Audit every path that can expand the card or ask for a haptic: a push, a local refresh, a reconnect, an app foreground, and a duplicate start. Name the trigger you found in the pull request body. If one path cannot be proven, say so and name it. Proof: the card over several update cycles, staying collapsed and silent, with the decisive log lines or a screen recording from a real iOS simulator.
iscekic
marked this pull request as draft
September 24, 2026 13:20
iscekic
added this pull request to stack #6690
September 24, 2026 13:20
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Incremental review since Files Reviewed (18 files)
Previous Review Summaries (2 snapshots, latest commit a73411a)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit a73411a)Status: No Issues Found | Recommendation: Merge Incremental review since Files Reviewed (3 files)
Previous review (commit 0eb54ff)Status: No Issues Found | Recommendation: Merge Files Reviewed (20 files)
Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
…-expand-vibrate-05d1 # Conflicts: # apps/mobile/src/lib/glanceable/persist.ts
…ty-strays-c7 # Conflicts: # apps/mobile/src/lib/glanceable/persist.ts
The fake SecureStore in this suite is a plain object, not a vi.fn, so the mock helpers were not on it: typecheck rejected them and the async implementation tripped the no-await rule. Inject a rejecting store, then a gated one, through _setSecureStoreForTests, the way the neighbouring first-restore case does.
…7' into kwf/ios-live-activity-expand-vibrate-05d1 # Conflicts: # apps/mobile/src/lib/glanceable/persist.ts
…e mock The new test installed a one-shot rejection and a one-shot pending read on `secureStoreMock.getItemAsync`, but that field was a plain async function, so both `mockRejectedValueOnce` and `mockImplementationOnce` were invalid calls. oxlint failed the PR on the second one (promise-function-async, prefer-await-to-then). Wrap the field in `vi.fn` and await the gate inside the one-shot implementation.
…7' into kwf/ios-live-activity-expand-vibrate-05d1 # Conflicts: # apps/mobile/src/glanceable-ios/ios-sink.test.ts
oxlint rejects an async store read whose body is only a throw (require-await), so the case no longer needs a rejecting store: a first restore against the empty mirror already settles with a null snapshot, which is the state the later in-flight read must not be confused with.
…7' into kwf/ios-live-activity-expand-vibrate-05d1
iscekic
marked this pull request as ready for review
September 25, 2026 13:49
iscekic
added a commit
that referenced
this pull request
Sep 25, 2026
The gastown auth and Durable Object fixes, the session-ingest test repair, and the security-auto-analysis integration config came from an unrelated backend gate repair. They do not belong to a cloud-agent-sdk capability change. Reverts those trees to the branch merge base (8e59fe6). The same gastown fix is present in #6689 and #6580.
The gastown auth, Durable Object lifecycle, gastown integration tests, and session-ingest validation changes came from a backend gate repair, not from this change. Restored to the branch merge base (f1f708e).
…-expand-vibrate-05d1
A sweep that met an in-flight persisted-state read deferred and never ran again, so when that read settled with an empty mirror the card the sweep kept was unowned and stayed on the Lock Screen until the next foreground or publisher update. `persist` now exposes `whenGlanceableRestoresSettle`, which resumes a caller when the last in-flight read lands, and the sweep awaits it before it runs again. A read that fails still settles, so the sweep re-reads the unreadable flag instead of waiting forever.
…7' into kwf/ios-live-activity-expand-vibrate-05d1
The mobile lint enables the promise rules, so `then` callbacks fail the lint job: the sweep resumes through an async IIFE, the tests await the waiter through the same shape, and `whenGlanceableRestoresSettle` is async.
…7' into kwf/ios-live-activity-expand-vibrate-05d1
iscekic
added a commit
that referenced
this pull request
Sep 26, 2026
) * fix(cloud-agent-sdk): treat unknown CLI capabilities as supported Surface: the mobile app (apps/mobile) and the cloud-agent SDK (packages/cloud-agent-sdk). A capability gate that depends on CLI support must default to YES. Today it defaults to NO, so a feature disappears until the CLI advertises it. Evidence: - `packages/cloud-agent-sdk/src/session-manager.ts:880` creates `supportsAttachmentsAtom` as `atom(false)`. - `recomputeSupportsAttachments` (`:1468`-`:1485`) sets `true` for `cloud-agent`, and `currentCapabilities?.attachments === true` for `remote`. Every other remote state (absent, false, mid-reconnect) sets `false`. - `apps/mobile/src/components/agents/session-detail-content.tsx:2298` passes that atom to `attachmentsEnabled`, so the paperclip is absent until the CLI reports the capability. Requirements: - Optimistic default: while the CLI capability is unknown, the gate reports supported. - Downgrade only on an explicit negative. A heartbeat or `sessions.list` row that says `attachments === false` sets the gate to false. - A `read-only` session stays unsupported. - Apply the same rule to every gate in this file that reads a CLI capability. Attachments is one example, not the whole set. - The downgrade must still take effect as soon as the CLI reports it. Do not lose the reconciliation. Proof: unit tests for unknown -> true, explicit false -> false, `cloud-agent` -> true, `read-only` -> false. Then one live proof on the platform you choose: open a remote session whose CLI has not yet reported capabilities, and show the attachmen * fix: kwf-fix-review-af0e patch delivery * fix: fix the failed backend verification gate (second attempt, different model) (kwf kwf-fix-review-af0e/gr2) * style: apply the repo formatter * fix(gastown): keep per-town auth on container control-plane routes The /container/ entry in the /api/towns/:townId/* skip list let every Town Container control-plane route (agents/start, agents/:id/stop, agents/:id/message, agents/:id/status, agents/:id/stream-ticket, health, pty) bypass kiloAuthMiddleware, adminAuditMiddleware and townAuthMiddleware. The handlers proxy straight to the container control server and check no authorization of their own, so any principal that clears Cloudflare Access could drive another tenant's container by supplying its townId. CF Access authenticates the caller but does not enforce town ownership. Drop the skip and return the middleware response instead of awaiting it, so an unauthenticated caller gets the middleware 401 instead of a dropped response. Update the container route comment and the two integration tests that asserted an unauthenticated request reached the body validator. * chore(scope): keep the PR to the CLI capability change The gastown auth and Durable Object fixes, the session-ingest test repair, and the security-auto-analysis integration config came from an unrelated backend gate repair. They do not belong to a cloud-agent-sdk capability change. Reverts those trees to the branch merge base (8e59fe6). The same gastown fix is present in #6689 and #6580. * fix(mobile): recheck spawn admission against the refreshed instance The file and clone checks ran against the press-time row before the refetch resolved the live row. A rebooted host can come back on a new connectionId and report an explicit refusal the press-time row did not, so the spawn could use a row that now refuses the file payload or the clone source. Both checks now run again against the live row before the spawn commits; the attempt was already admitted, so a refusal fails it and re-arms the abandon guard. * fix(cloud-agent-sdk): refuse remote parts without the consumer path The send guard checked the session type and the CLI capability, but not the consumer's declaration that it can deliver remote attachment parts. The UI gate disables the attachment control without that declaration, so a caller that supplied attachmentParts could still have them forwarded. The guard now requires config.supportsRemoteAttachmentParts, the same condition the gate uses. * test(mobile): make the refreshed-instance refetch stubs async The two new stubs returned Promise.resolve from a plain arrow, which the repo's promise rules reject (promise-function-async and prefer-await-to-then). An async arrow satisfies both without the disable comment the older stubs needed. * test(mobile): use the file's refetch stub pattern for the new cases An async arrow trips require-await in the mobile lint config, and a bare Promise.resolve arrow trips promise-function-async and prefer-await-to-then. The file's established single-line stub with the disable comment satisfies all three; the refreshed list is hoisted to a const so the stub stays on one line.
…-expand-vibrate-05d1
eshurakov
approved these changes
Sep 28, 2026
This was referenced Sep 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changelog for users
Changelog for maintainers
dropFencedStartsnow treats the push-to-start fence as scope-wide: while any fence underiosStartPrefixis live, everyios_push_to_starttoken is dropped from the send list.alerton every push-to-start, and that alert is what lights the screen and expands the card.ios_activityrow) still releases every fence in the scope and returns the full token list, so ordinary starts resume once the app owns the card.holds the push-to-start fence across a rotated push-to-start tokenin the notifications glanceable delivery tests.E2E proof
Owner request
E2E proof
Open findings (not fixed here)