Skip to main content

Troubleshooting Guide

This guide compiles common issues encountered during the integration and operation of the tk_ota_push SDK and the native runtime, complete with root causes in the codebase and step-by-step solutions.

1. Patch is Not Downloaded or Does Not Apply​

Symptom A: TkOtaPush.instance.isAvailable Returns false​

  • Cause: The application was launched in debug mode (flutter run) or compiled using standard Flutter instead of the Shorebird toolchain (tk-ota-push release).
  • Solution: Always build the baseline Store binary using tk-ota-push release and install the resulting APK/AAB or IPA in release mode.

Symptom B: outcome.executionMode is blockedByGatekeeper (networkCallPerformed: false)​

  • Cause: One of the 3 cumulative conditions in RemoteConfigGatekeeper evaluated to false. Inspect outcome.gatekeeperEvaluation?.verdict and outcome.message:
    • GatekeeperVerdict.skippedVersionMismatch: version passed to TkOtaPush.init(appReleaseVersion: ...) does not match target_release_version in the Remote Config payload.
    • GatekeeperVerdict.skippedNoPatchAvailable: patch_available is false in the Remote Config payload.
    • GatekeeperVerdict.skippedAlreadyUpToDate: the currently active patch (currentPatchNumber) is greater than or equal to latest_patch_number.
  • Solution: Align appReleaseVersion with the exact version in pubspec.yaml (e.g., '1.0.0+3') and bump latest_patch_number in your Remote Config provider.

Symptom C: Patch Downloaded but UI Does Not Change Immediately​

  • Cause: By architecture, the C++/Rust engine loads the new snapshot (application binary or patch artifact) during Dart VM initialization on cold start (Cold Start). Backgrounding and foregrounding the app (Warm Resume) does not reload the shared native library.
  • Solution: Completely terminate (cold-kill) and reopen the app (or programmatically restart following a critical update where outcome.restartRequired == true).

2. Error Update rejected: hash mismatch or CorruptPatch​

  • Cause: The application binary (Android) or App (iOS) binary installed on the device does not have the same SHA-256 fingerprint as the reference Release recorded on the OTA server. This occurs if you ran flutter build apk manually after running tk-ota-push release, or if an unstripped intermediate file was used.
  • Solution:
    1. Use tk_ota_push >= 0.5.1 / 1.0.0, where Publisher._extractLibAppFromPackage extracts application binary directly from the final .apk or .aab archive.
    2. Distribute to users strictly the .apk, .aab, or .ipa produced by tk-ota-push release, without running subsequent manual build commands.

3. Error InvalidSignature (Patch signature could not be verified)​

  • Cause: The Rust engine rejected the patch because the RSA signature (hash_signature) returned by POST /api/v1/patches/check does not validate against patch_public_key embedded inside flutter_assets/shorebird.yaml (failure_category: "invalid_signature").
  • Solution:
    1. Verify shorebird.yaml is declared under flutter.assets in pubspec.yaml.
    2. Ensure the RSA signing key in cryptographic signing engine was not regenerated with a different keypair since the Store Release was published.
    3. When running tk-ota-push release, the CLI automatically synchronizes patch_public_key from GET /api/admin/v1/public-key.

4. Bootloop Crash on Patch Boot & Active Rollback Alert​

  • Cause: Code introduced in the patch causes a fatal unhandled exception at boot time, or reads an incompatible local database/preferences schema following a rollback.
  • Automatic Behavior:
    1. On the subsequent launch, handle_prior_boot_failure_if_necessary() observes that currently_booting_patch failed to finish booting.
    2. The Rust engine flags the patch as PatchKnownBad, rolls back to the previous healthy binary, and dispatches __patch_install_failure__ (crash_recovery: patch N failed to boot).
    3. On the Dart side, OtaRollbackTracker fires onRollbackDetected(OtaRollbackEvent).
  • Solution:
    1. Implement onRollbackDetected in TkOtaPush.init to wipe obsolete local caches (SharedPreferences, SQLite, Hive) upon regression.
    2. Fix the Dart bug and publish a newer patch with an incremented number (N + 1).
    3. In the Web Console (/support), acknowledge the rollback alert on impacted devices.

5. Incorrect app_id or Environment Collision (dev / staging / prod)​

  • Cause: Published patches are not discovered because the app_id baked into the Release differs from the app_id targeted during tk-ota-push patch.
  • Solution:
    1. Check the resolution cascade in FlutterProject.resolveEffectiveAppId: if you built the Release using --flavor prod, pass --flavor prod during tk-ota-push patch.
    2. Inspect the effective value injected into the header of shorebird.yaml following the build.

6. Unreachable Server & Android HTTP Cleartext Blocking​

  • Symptom: No network calls reach the TkushPatch Server backend (ShorebirdUpdaterController) when testing on an Android device over local Wi-Fi with base_url: "http://192.168.x.x:8080", and telemetry logs failure_category: "network".
  • Causes & Solutions:
    1. Missing INTERNET Permission: declare <uses-permission android:name="android.permission.INTERNET"/> inside android/app/src/main/AndroidManifest.xml.
    2. Android 9+ Cleartext Blocking: for local development over unencrypted http://, set android:usesCleartextTraffic="true" on the <application> tag in AndroidManifest.xml.
    3. Using localhost in base_url: on a physical smartphone, localhost resolves to the phone itself rather than your dev workstation. Use your machine's LAN IP in shorebird.yaml (e.g., http://192.168.0.199:8080). The TkushPatch Server backend dynamically rewrites download URLs via ShorebirdUpdaterController.resolveClientReachableUrl.

7. NATIVE CODE DIVERGENCE DETECTED Blocking During patch​

  • Symptom: tk-ota-push patch aborts with the message: NATIVE CODE DIVERGENCE DETECTED (classes.dex, manifest or C++ libs modified) (or Info.plist or Frameworks modified on iOS).
  • Cause: NativeIntegrityScanner detected that native machine code (nativeCodeHash) changed compared to the locked Release on the server (added a Flutter plugin with Kotlin/Swift/C++ code, or modified AndroidManifest.xml / Info.plist).
  • Solution:
    • The OTA engine can only patch the Dart layer. Increment your version in pubspec.yaml and publish a new Store Release (tk-ota-push release).
    • If you are certain the native change is non-breaking for Dart execution, bypass using --force (not recommended in production).

8. HTTP 409 CONFLICT - RELEASE OVERWRITE PROTECTION​

  • Symptom: tk-ota-push release fails with HTTP 409 CONFLICT stating that the Release is already locked.
  • Cause: Publisher.guardNoOverwrite prohibits overwriting an existing Release with a new build, because recompiling under the same version modifies baseline memory layouts and would trigger segmentation faults (Segfault) on devices running the previously compiled binary.
  • Solution:
    • To patch the existing version, execute tk-ota-push patch.
    • To publish a new Store version, bump the build number in pubspec.yaml (e.g., 1.0.0+2 to 1.0.0+3) before running tk-ota-push release.