* feat(gateway): propagate approvals to ancestor sessions with replay Squash-rebased #103921 segment onto the native-clients tip on current main. Session-scoped approval events publish sanitized pending/terminal transitions to opted-in session audiences, with authoritative pending replay on stream subscribe and durable-expiry reconciliation through the owning manager. SessionApprovalEvent/Replay wire types export from the approvals owner module; Swift models regenerated. (cherry picked from commit 2d1dcf9747044710111d0c730fc46ba6013a165c) (cherry picked from commit ccf88efd56b513d07599ffcee997bf0cddaf9adc) (cherry picked from commit 8eb33f59f00ec9ee4625259b76bd7ae60d2cd480) (cherry picked from commit c297cbc93c8401e9c79c20f242245a194f5dd236) (cherry picked from commit 93d68f28601ff37d863da4db009504993a22533a) (cherry picked from commit d285ccd8b2f212bb57e814107e7709c896e96b7d) (cherry picked from commit 5c536982231402b1b58d1683794ea9948b32d077) (cherry picked from commit 1646e5b6dc4dd20de54e10f110a5909be18d7d50) (cherry picked from commit 35cf1b705b247fb282f4d56ffe74bd2fb238221f) (cherry picked from commit4f8ef3699d) (cherry picked from commit 74fbdcc99d5fff062d67760a51dba4ee9e84479b) (cherry picked from commit 222b285502681d2be14b1819798e9ee5555f431e) (cherry picked from commit b9e744951f676b600e7c5322bfd04b0e99797c07) (cherry picked from commit9e904efde5) (cherry picked from commit678f2384fa) (cherry picked from commit72842e5cf6) (cherry picked from commit be74c25e80e84a1f065053766f23efb84822e811) (cherry picked from commit93ba8c4b09) (cherry picked from commit8f23761372) * feat(gateway): fail-closed plugin and tool approval gates Squash-rebased #103932 segment onto the ancestor-propagation tip on current main. Plugin node.invoke approvals claim a one-shot allow-once decision before handing execution authority to the policy, so observation or retry cannot replay a consumed approval; sibling tool gates bind approval ids to their originating reviewer identity. (cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577) (cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8) (cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24) (cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8) (cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd) (cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480) (cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4) (cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d) (cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032) (cherry picked from commit9b7e5a9608) (cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e) (cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b) (cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796) (cherry picked from commit76829805a7) (cherry picked from commita8b493f934) (cherry picked from commitaceb990597) (cherry picked from commit a29b0e75482470c53a9fb4fe3f204e14cf71868f) (cherry picked from commit2e3be08653) (cherry picked from commitc9ae3d7202) * fix: main-gate repairs for the durable-approvals stack - android: capture createdAtMs on the pending exec-approval write at registration; canonical readback after a refresh that already replaced the visible rows was dropping the approval instead of surfacing the still-pending reconciliation message (#104913 merged without this) - android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest - agents: hermetic model-discovery test via the plugins/provider-runtime boundary (lazy plugin-runtime resolution hangs vitest workers since #104770) - cli: usage-cost settle-budget test asserts the budget bound on every call instead of pinning the poll count (fast hosts fit a second poll in 50ms) - protocol coverage: allowlist session.approval for ios/android (native review rides exec.approval push/nudge delivery) - docs map, native i18n inventory, plugin-sdk api baseline regenerated
OpenClaw Android App
OpenClaw Android is the officially released Google Play app. It connects to an OpenClaw Gateway as a companion node for chat, voice, approvals, screen, and device-aware automation.
Current App Surface
- New 4-step onboarding flow
- Connect tab with
Setup Code+Manualmodes - Encrypted persistence for gateway setup/auth state
- Chat UI restyled
- Settings UI restyled and de-duplicated (gateway controls moved to Connect)
- QR code scanning in onboarding
- Performance improvements
- Streaming support in chat UI
- Dedicated per-device Android chat session created/adopted on connect without resetting history
- Request camera/location and other permissions in onboarding/settings flow
- Push notifications for gateway/chat status updates
- Security hardening (biometric lock, token handling, safer defaults)
- Authenticated background presence beacons
- Voice tab full functionality
- Screen tab full functionality
- Skill Workshop settings can filter proposals, inspect proposal content, and apply/reject/quarantine drafts through Gateway RPCs
- Per-app language selection for translated resources follows Android system settings and persistence
- Cron job settings support details, run history, run now, edits, enable/disable, and deletion with admin-scoped Gateway access
Open in Android Studio
- Open the folder
apps/android.
Build / Run
cd apps/android
./gradlew :app:assemblePlayDebug
./gradlew :app:installPlayDebug
./gradlew :app:testPlayDebugUnitTest
cd ../..
pnpm android:release:archive
Third-party debug flavor:
cd apps/android
./gradlew :app:assembleThirdPartyDebug
./gradlew :app:installThirdPartyDebug
./gradlew :app:testThirdPartyDebugUnitTest
Repository-backed debug Gradle invocations, including pnpm android:run and
pnpm android:screenshots, stamp the full checkout commit and capture one UTC
build timestamp shared by every debug variant in that invocation. Release
tasks still require explicit openclawBuildCommit and
openclawBuildTimestamp properties so signed artifacts remain reproducible.
Android release archives use the pinned version in apps/android/version.json. Update it with:
pnpm android:version
pnpm android:version:check
pnpm android:version:pin -- --from-gateway
pnpm android:version:pin -- --version 2026.6.5 --version-code 2026060501
Release-owner signing sync:
pnpm android:release:signing:plan
MATCH_PASSWORD=<signing repo password> pnpm android:release:signing:sync:pull
MATCH_PASSWORD=<signing repo password> pnpm android:release:signing:check
The signing sync pulls encrypted Android upload-key assets from the shared apps-signing repo and materializes decrypted files under apps/android/build/release-signing/.
Standalone release APK verification also requires that key's public certificate SHA-256 fingerprint to match Config/ReleaseSigning.json.
Generate raw Google Play screenshots:
pnpm android:screenshots
The screenshot script defaults to a retained OpenClaw_Screenshots_API36 AVD
created from Android's no-cutout Pixel 2 profile. It creates the AVD when
missing, boots it headlessly, waits for Android to finish booting, disables
animations, captures the screenshots, then shuts down the emulator it started.
The API 36 Google APIs system image must be installed in the local Android SDK.
Use ANDROID_SCREENSHOT_AVD or --avd to select another AVD, or --device to
explicitly use a connected emulator.
pnpm android:release:archive builds signed release artifacts into apps/android/build/release-artifacts/ and writes .sha256 checksum files:
- Play build:
openclaw-<version>-play-release.aab - Third-party build:
openclaw-<version>-third-party-release.apk
pnpm android:bundle:release is an alias for the same Fastlane archive lane.
Regular final and correction OpenClaw releases publish the signed third-party APK as OpenClaw-Android.apk with a checksum manifest and GitHub Actions provenance. .github/workflows/android-release.yml is the only automated GitHub Release upload path; OpenClaw Release Publish dispatches it while the canonical release is still a draft and blocks publication until the uploaded asset contract verifies.
The protected android-release environment supplies MATCH_PASSWORD; the repository's read-only GitHub App token checks out encrypted material from openclaw/apps-signing. The workflow builds the exact release tag, refuses to replace different existing bytes, and re-downloads the APK for checksum, certificate, and provenance verification.
pnpm android:release:archive is for local archive validation only. It is not a
fallback upload path after pnpm android:release:upload fails.
Agent-driven Google Play uploads must use pnpm android:release:upload as the
only release path. If that command fails, stop and fix the failing screenshot,
metadata, signing, validation, archive, or upload step before trying again. Do
not upload archived artifacts through direct Fastlane lanes, Gradle artifacts,
Google Play API commands, or Play Console mutation commands.
See apps/android/VERSIONING.md and apps/android/fastlane/SETUP.md for the release workflow.
Prefer pnpm android:release:archive, which stamps and validates the full Git commit and one UTC build timestamp before signing. Flavor-specific direct Gradle release tasks must pass the same metadata explicitly:
cd apps/android
commit="$(git -C ../.. rev-parse HEAD)"
built_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
./gradlew -PopenclawBuildCommit="$commit" -PopenclawBuildTimestamp="$built_at" :app:bundlePlayRelease
./gradlew -PopenclawBuildCommit="$commit" -PopenclawBuildTimestamp="$built_at" :app:bundleThirdPartyRelease
Kotlin Lint + Format
pnpm android:lint
pnpm android:format
Android framework/resource lint (separate pass):
pnpm android:lint:android
Direct Gradle tasks:
cd apps/android
./gradlew :app:ktlintCheck :benchmark:ktlintCheck
./gradlew :app:ktlintFormat :benchmark:ktlintFormat
./gradlew :app:lintPlayDebug :app:lintThirdPartyDebug
gradlew auto-detects the Android SDK at ~/Library/Android/sdk (macOS default) if ANDROID_SDK_ROOT / ANDROID_HOME are unset.
Macrobenchmark (Startup + Frame Timing)
cd apps/android
./gradlew :benchmark:connectedDebugAndroidTest
Reports are written under:
apps/android/benchmark/build/reports/androidTests/connected/
Perf CLI (low-noise)
Deterministic startup measurement + hotspot extraction with compact CLI output:
cd apps/android
./scripts/perf-startup-benchmark.sh
./scripts/perf-startup-hotspots.sh
Benchmark script behavior:
- Runs only
StartupMacrobenchmark#coldStartup(10 iterations). - Prints median/min/max/COV in one line.
- Writes timestamped snapshot JSON to
apps/android/benchmark/results/. - Auto-compares with previous local snapshot (or pass explicit baseline:
--baseline <old-benchmarkData.json>).
Hotspot script behavior:
- Ensures debug app installed, captures startup
simpleperfdata for.MainActivity. - Prints top DSOs, top symbols, and key app-path clues (Compose/MainActivity/WebView).
- Writes raw
perf.datapath for deeper follow-up if needed.
Run on a Real Android Phone (USB)
- On phone, enable Developer options + USB debugging.
- Connect by USB and accept the debugging trust prompt on phone.
- Verify ADB can see the device:
adb devices -l
- Install + launch debug build:
pnpm android:install
pnpm android:run
If adb devices -l shows unauthorized, re-plug and accept the trust prompt again.
USB-only gateway testing (no LAN dependency)
Use adb reverse so Android localhost:18789 tunnels to your laptop localhost:18789.
Terminal A (gateway):
pnpm openclaw gateway --port 18789 --verbose
Terminal B (USB tunnel):
adb reverse tcp:18789 tcp:18789
Then in app Connect → Manual:
- Host:
127.0.0.1 - Port:
18789 - TLS: off
Hot Reload / Fast Iteration
This app is native Kotlin + Jetpack Compose.
- For Compose UI edits: use Android Studio Live Edit on a debug build (works on physical devices; project
minSdk=31already meets API requirement). - For many non-structural code/resource changes: use Android Studio Apply Changes.
- For structural/native/manifest/Gradle changes: do full reinstall (
pnpm android:run). - Canvas web content already supports live reload when loaded from Gateway
__openclaw__/canvas/(seedocs/platforms/android.md).
Connect / Pair
- Start the gateway (on your main machine):
pnpm openclaw gateway --port 18789 --verbose
- In the Android app:
- Open the Connect tab.
- Use Setup Code or Manual mode to connect.
- Approve pairing (on the gateway machine):
openclaw devices list
openclaw devices approve <requestId>
More details: docs/platforms/android.md.
Permissions
- Discovery:
- Android 13+ (
API 33+):NEARBY_WIFI_DEVICES - Android 12 and below:
ACCESS_FINE_LOCATION(required for NSD scanning)
- Android 13+ (
- Location:
- Both flavors:
ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATIONfor foreground checks. - Third-party flavor only:
ACCESS_BACKGROUND_LOCATIONplusFOREGROUND_SERVICE_LOCATIONfor user-enabledAlwayschecks.
- Both flavors:
- Foreground service notification (Android 13+):
POST_NOTIFICATIONS - Camera:
CAMERAforcamera.snapandcamera.clipRECORD_AUDIOforcamera.clipwhenincludeAudio=true
Google Play Restricted Permissions
As of March 19, 2026, these manifest permissions are the main Google Play policy risk for this app:
READ_SMSSEND_SMSREAD_CALL_LOG
Why these matter:
- Google Play treats SMS and Call Log access as highly restricted. In most cases, Play only allows them for the default SMS app, default Phone app, default Assistant, or a narrow policy exception.
- Review usually involves a
Permissions Declaration Form, policy justification, and demo video evidence in Play Console. - The Play build removes these behind the
playflavor. - Photo library access is also removed from the Play build. Use third-party builds for
photos.latest.
Current OpenClaw Android implication:
- APK / sideload build can keep SMS, Call Log, and recent-photo features.
- Google Play build excludes SMS send/search, Call Log search, and recent-photo access unless the product is intentionally positioned and approved under the relevant policy exception.
- The repo now ships this split as Android product flavors:
play: removesREAD_SMS,SEND_SMS,READ_CALL_LOG,READ_MEDIA_IMAGES,READ_MEDIA_VISUAL_USER_SELECTED,READ_EXTERNAL_STORAGE, and background location; hides SMS, Call Log, Photos, andAlwayslocation surfaces.- Installed-app listing is user controlled.
device.appsis advertised only after the user enables Settings > Phone Capabilities > Installed Apps. The command defaults to launcher-visible apps and does not requireQUERY_ALL_PACKAGES. thirdParty: keeps the full permission set and the existing SMS / Call Log / Photos functionality, and offers explicitAlwayslocation opt-in through Android settings.
Policy links:
- Google Play SMS and Call Log policy
- Google Play sensitive permissions policy hub
- Android default handlers guide
Other Play-restricted surfaces to watch if added later:
ACCESS_BACKGROUND_LOCATIONMANAGE_EXTERNAL_STORAGEQUERY_ALL_PACKAGESREQUEST_INSTALL_PACKAGESAccessibilityService
Reference links:
Integration Capability Test (Preconditioned)
This suite assumes setup is already done manually. It does not install/run/pair automatically.
Pre-req checklist:
- Gateway is running and reachable from the Android app.
- Android app is connected to that gateway and
openclaw nodes statusshows it as paired + connected. - App stays unlocked and in foreground for the whole run.
- Open the app Screen tab and keep it active during the run (canvas/A2UI commands require the canvas WebView attached there).
- Grant runtime permissions for capabilities you expect to pass (camera/mic/location/notification listener/location, etc.).
- No interactive system dialogs should be pending before test start.
- Canvas host is enabled and reachable from the device for remote Canvas checks (do not run gateway with
OPENCLAW_SKIP_CANVAS_HOST=1; startup logs should includecanvas host mounted at .../__openclaw__/). - Local operator test client pairing is approved. If first run fails with
pairing required, preview the latest pending request, approve the printed request ID, then rerun: - For A2UI checks, keep the app on Screen tab; the node uses its bundled app-owned A2UI page for message application.
openclaw devices list
openclaw devices approve --latest # preview only; copy the requestId from output
openclaw devices approve <requestId>
Run:
pnpm android:test:integration
Optional overrides:
OPENCLAW_ANDROID_GATEWAY_URL=ws://...(default: from your local OpenClaw config)OPENCLAW_ANDROID_GATEWAY_TOKEN=...OPENCLAW_ANDROID_GATEWAY_PASSWORD=...OPENCLAW_ANDROID_NODE_ID=...orOPENCLAW_ANDROID_NODE_NAME=...
What it does:
- Reads
node.describecommand list from the selected Android node. - Invokes advertised non-interactive commands.
- Skips
screen.recordin this suite (Android requires interactive per-invocation screen-capture consent). - Asserts command contracts (success or expected deterministic error for safe-invalid calls like
sms.sendandnotifications.actions).
Common failure quick-fixes:
pairing requiredbefore tests start:- list pending requests (
openclaw devices list), then approve with the exact ID (openclaw devices approve <requestId>) and rerun.
- list pending requests (
A2UI host not reachable/A2UI_HOST_UNAVAILABLE:- keep the app foregrounded on the Screen tab and rerun. A2UI commands use the bundled app-owned A2UI page; the Gateway Canvas host is still needed for remote Canvas checks, but not for A2UI message application.
NODE_BACKGROUND_UNAVAILABLE: canvas unavailable:- app is not effectively ready for canvas commands; keep app foregrounded and Screen tab active.
Contributions
Maintainer: @obviyus. For issues/questions/contributions, please open an issue or reach out on Discord.