Skip to content

fix(mobile): require an unlocked device for the Android Approve action - #8245

Merged
iscekic merged 5 commits into
mainfrom
drain/android-approval-presence
Oct 26, 2026
Merged

iscekic merged 5 commits into
mainfrom
drain/android-approval-presence

Conversation

@iscekic

@iscekic iscekic commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — on the API 36 and API 30 emulators, each running the dev client built from this branch against the local dev stack, Approve completes the device authentication step first and then runs the approval, and the card shows the answer.

What changed

Approve on both Android agent cards now runs only for an unlocked device.

  • New ActiveAgentsApprovalActivity: an invisible, single-use activity in its own
    task (exported=false, excluded from recents, translucent, no content). It
    draws nothing and stays behind the keyguard, so the dismissal it asks for is
    what presents the device credential. It runs the approval when the device is
    already unlocked; otherwise it asks the keyguard to dismiss itself (API 26+)
    and runs the approval on onDismissSucceeded. An authentication step that is
    not completed returns to the card with the approval still available. API 24-25
    have no requestDismissKeyguard and no action authentication flag, so the
    approval runs from an unlocked tap.
  • New ApprovalGate: the single construction of the Approve transport for both
    cards. PendingIntent.getActivity to the gate activity, the action built with
    setAuthenticationRequired(true) from API 31, and the unique
    OneTimeWorkRequest (KEEP) that runs KiloActiveAgentsApprove.
  • ActiveAgentsLiveUpdateModule.approvePendingIntent and
    SessionNotifications.approveIntent now build the Approve action through
    ApprovalGate; Open and New agent keep their own intents, unchanged.
  • ActiveAgentsActionReceiver keeps its ACTION_APPROVE branch under the same
    unlocked-device rule and otherwise only handles dismissal and the quiet alarm.
  • Unused KEY_ORGANIZATION_ID work-input constant removed.
  • Spec: .specs/mobile-session-live-activity.md "Android: actions" carries the
    unlocked-device rule, with a changelog entry.

Verification

Two emulators on this work VM, each with the dev client built from this branch
and signed in to an owned fake account on the local dev stack (nextjs :3000,
Metro :8081, adb reverse for every mobile port). The cards come from the
dev-only glanceable fixtures, which post through the production Follow path;
their approval key names no live request, so the revalidating read the approval
issues answers 404 and the card settles after the answer.

Transport, both cards (dumpsys notification --noredact):

[0] "Approve" -> PendingIntent{... com.kilocode.kiloapp startActivity ...}
[1] "Open"    -> PendingIntent{... com.kilocode.kiloapp startActivity ...}

API 36 emulator, device protected by a PIN

  1. Approve on the expanded card completes the device authentication step first;
    the approval starts after it:

    ActivityTaskManager: START u0 {... ActiveAgentsApprovalActivity (has extras)}
    ActivityTaskManager: Activity requesting to dismiss Keyguard: ActivityRecord{... ActiveAgentsApprovalActivity}
    WM-WorkerWrapper: Worker result SUCCESS for Work [... tags={ com.kilocode.activeagentsliveupdate.ActiveAgentsApproveWorker }]
    
  2. The backend logs the revalidating read for the approval (cliSessionsV2.get,
    404 for the fixture's key), and the card then lists Open alone.

  3. An authentication step that is not completed returns to the shade; the
    expanded card still lists Approve and Open.

  4. With no authentication step outstanding, Approve starts the activity 34 ms
    after the tap, the worker completes SUCCESS, and the same backend read
    follows.

API 30 emulator, device protected by a PIN

The requestDismissKeyguard path, entered by starting the same activity with the
same extras the action carries:

ActivityTaskManager: START u0 {cmp=com.kilocode.kiloapp/com.kilocode.activeagentsliveupdate.ActiveAgentsApprovalActivity (has extras)}
ActivityTaskManager: Activity requesting to dismiss Keyguard: ActivityRecord{... ActiveAgentsApprovalActivity}

The PIN pad is on screen (PIN area and the keypad in the uiautomator dump); after
the PIN, KeyguardViewMediator: handleKeyguardDone, then
WM-WorkerWrapper: Worker result SUCCESS for Work [... ActiveAgentsApproveWorker],
then the backend cliSessionsV2.get read.

Unproven: no API 24-25 device was available. There the activity finishes without
asking, which is the rule for those levels (no requestDismissKeyguard, no action
authentication flag).

Production prerequisites

None.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Transport: Which transport should the Approve action use? The finding wants the device unlocked before dispatch; the work item brief says setAuthenticationRequired(true) on API 31+ and an activity with requestDismissKeyguard below it.
    Answer: Activity on every API + authentication flag on 31+
    Confidence: 0.75
  2. Question: Lock screen: The finding also suggests a lock-screen-safe public version of the card that omits Approve. Should this change add it?
    Answer: No: keep the action, now unlock-gated
    Confidence: 0.75
  3. Question: API 24-25: requestDismissKeyguard exists only from API 26. The app's minSdk is 24. What should a locked API 24-25 device do when Approve is tapped?
    Answer: Fail closed: ignore the tap while locked
    Confidence: 0.70
  4. Question: Cancelled unlock: What should happen when the user cancels the unlock prompt?
    Answer: Nothing: finish silently, card keeps Approve
    Confidence: 0.70

Both Android cards now send Approve through an invisible gate activity that
starts the headless approval once the device is unlocked, and the action carries
the platform's authentication requirement from Android 12. A declined unlock
leaves the request in place for an unlocked tap, and API levels without
requestDismissKeyguard keep the request until then.
The contract suite reads the Kotlin sources: every Approve action now goes
through the gate activity, the action carries the platform authentication
requirement where it exists, and the dismissal path keeps its work order.
@iscekic iscekic added drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening labels Oct 20, 2026
@iscekic

iscekic commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Captures from the Android agent cards on the emulator (API 36, owned fake-account test data, local stack). Approve and Open stay on the card while the device is locked; tapping Approve there brings up device authentication before the approval runs. Both captures are of the dev-only glanceable fixtures that post the real notification through the production Follow and push path.

Agent notification with Approve and Open on the lock screen

Followed session card with Approve and Open on the lock screen

Comment thread .specs/mobile-session-live-activity.md Resolved
@kilo-code-bot

kilo-code-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit removes the gate activity's show-when-locked flags so the keyguard stays present for the dismissal request; the removal is consistent with ApprovalGate.action's API 31+ authentication flag, the manifest's translucent/no-content gate, and the contract tests.

Assumptions: [INFERENCE] The behavior of requestDismissKeyguard from an activity placed behind the keyguard on API 26-30 was not verified here; the PR body documents API 36 testing only and states API 24-25 are covered by contract tests.

Files Reviewed (1 file)
  • apps/mobile/modules/active-agents-live-update/android/src/main/java/com/kilocode/activeagentsliveupdate/ActiveAgentsApprovalActivity.kt
Previous Findings
  • The four dismissal-rule citation comments were confirmed fixed in 98ee1396, and the incremental commit does not touch those files.
Previous Review Summaries (3 snapshots, latest commit 33f5d09)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 33f5d09)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change only retargets two code comments: the manifest now describes the action receiver as handling dismissal and quiet-alarm broadcasts, and approve-ask.ts describes Approve as starting a gate activity that boots WorkManager; both match ActiveAgentsActionReceiver.kt and ActiveAgentsApprovalActivity/ApprovalGate at HEAD.

Files Reviewed (2 files)
  • apps/mobile/modules/active-agents-live-update/android/src/main/AndroidManifest.xml
  • apps/mobile/src/lib/glanceable/approve-ask.ts
Previous Findings
  • Dismissal rule citation (rule 3 → 4) in four code comments: resolved in 98ee1396; re-verified at HEAD.

Previous review (commit 98ee139)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change only updates four code comments to cite "Android: actions" rule 4 for notification dismissal, matching the renumbered spec where rule 3 is now the unlocked-device rule and rule 4 is dismissal; all four citations were verified against .specs/mobile-session-live-activity.md:339-346.

Files Reviewed (4 files)
  • apps/mobile/modules/active-agents-live-update/android/src/main/java/com/kilocode/activeagentsliveupdate/ActiveAgentsActionReceiver.kt
  • apps/mobile/src/glanceable-android/approve-task.ts
  • apps/mobile/src/glanceable-android/session-follow.ts
  • apps/mobile/src/lib/glanceable/session-follow.ts

Previous review (commit 73c9698)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The Android Approve presence gate is implemented consistently on both cards (activity PendingIntent, API 31+ authentication flag, unlocked-only enqueue, keyguard callback guards); the single finding is a spec renumbering that leaves four code comments citing the wrong rule.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
.specs/mobile-session-live-activity.md 344 Dismissal moved from rule 3 to rule 4, but four code comments still cite rule 3 for dismissal (ActiveAgentsActionReceiver.kt:50, approve-task.ts:140, session-follow.ts:256, lib/glanceable/session-follow.ts:211).
Files Reviewed (9 files)
  • .specs/mobile-session-live-activity.md - 1 issue
  • apps/mobile/modules/active-agents-live-update/android/src/main/AndroidManifest.xml - 0 issues
  • apps/mobile/modules/active-agents-live-update/android/src/main/java/com/kilocode/activeagentsliveupdate/ActiveAgentsActionReceiver.kt - 0 issues
  • apps/mobile/modules/active-agents-live-update/android/src/main/java/com/kilocode/activeagentsliveupdate/ActiveAgentsApprovalActivity.kt - 0 issues
  • apps/mobile/modules/active-agents-live-update/android/src/main/java/com/kilocode/activeagentsliveupdate/ActiveAgentsApproveWorker.kt - 0 issues
  • apps/mobile/modules/active-agents-live-update/android/src/main/java/com/kilocode/activeagentsliveupdate/ActiveAgentsLiveUpdateModule.kt - 0 issues
  • apps/mobile/modules/active-agents-live-update/android/src/main/java/com/kilocode/activeagentsliveupdate/ApprovalGate.kt - 0 issues
  • apps/mobile/modules/active-agents-live-update/android/src/main/java/com/kilocode/activeagentsliveupdate/SessionNotifications.kt - 0 issues
  • apps/mobile/src/glanceable-android/session-native-contract.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 36.1K · Output: 5.8K · Cached: 282.1K

Review guidance: REVIEW.md from base branch main

The Android actions list gains the unlocked-device rule before the dismissal
rule, so the four comments that cite the dismissal now cite rule 4.
@iscekic
iscekic enabled auto-merge October 20, 2026 03:49
@iscekic iscekic added the merge:human Substantive functional risk requires human review. label Oct 20, 2026
The manifest note for the action receiver names the dismissal and
quiet-alarm broadcasts, and the shared answer module names the gate
activity that starts the headless approval on Android.
@iscekic
iscekic disabled auto-merge October 20, 2026 19:05
@iscekic iscekic added merge:blocked merge:blocked:insufficient-e2e Verification does not cover the change and removed merge:blocked labels Oct 20, 2026
@iscekic iscekic added the intent:ok Janitor change confirmed as a real bug fix, not intended behavior label Oct 23, 2026
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. and removed merge:human Substantive functional risk requires human review. merge:blocked:insufficient-e2e Verification does not cover the change merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: E2E captures from the API 36 emulator running the dev client built from this branch against the local dev stack (owned fake account, dev-only glanceable fixture cards). The shade was expanded and the card actions driven through the uiautomator bounds read in the same command as each capture.

  • Card actions: both agent cards expanded, each offering Approve and Open.
  • After Approve: the same session card shows the answer in flight, with Open as its only action.

The device logs and the backend read for the authentication path are quoted in the pull request verification section.

Agent card actions

Session card after Approve

@eshurakov eshurakov 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.

Shallow review: no critical issues found.

@iscekic
iscekic added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit ac7f04d Oct 26, 2026
106 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain intent:ok Janitor change confirmed as a real bug fix, not intended behavior janitor:trust Janitor finding: trust boundary hardening merge:human Substantive functional risk requires human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants