Skip to main content

Troubleshooting

Common issues and fixes for the SwiftPatch SDK.


Update Not Applying

Symptoms: Update shows as available but doesn't apply after restart.

Checklist
  1. Verify you're running a release build, not debug
  2. Check that the deployment key is correct
  3. Confirm the AppDelegate/MainApplication loads the SwiftPatch bundle
  4. Make sure the target binary version matches your app version

Update Download Fails

Symptoms: Download starts but fails or times out.

Checklist
  1. Check network connectivity
  2. Verify the device can reach your SwiftPatch API server
  3. Check for proxy or firewall issues
  4. Enable debug mode for detailed logs:
export default withSwiftPatch(App, {
debug: true,
});

App Crashes After Update

Symptoms: App crashes on launch after an OTA update, or enters a restart loop.

1. Missing --hermes flag (most common)

If your app uses Hermes (React Native's JavaScript engine that runs precompiled bytecode, enabled by default since React Native 0.70+), you must deploy with --hermes. A plain JS bundle crashes immediately because the Hermes VM expects compiled bytecode.

# Wrong -- crashes Hermes apps
swiftpatch deploy -p ios

# Correct
swiftpatch deploy -p ios --hermes
warning

This is the number one cause of post-update crashes. Always use --hermes if Hermes is enabled.

2. Crash detection and auto-rollback

SwiftPatch rolls back after 2 crashes within 10 seconds (both configurable). If auto-rollback isn't working:

  1. Make sure withSwiftPatch wraps your entire app
  2. The SDK tracks crashes within crashDetectionWindowMs (default: 10s)
  3. After maxCrashesBeforeRollback crashes (default: 2), the bundle reverts
  4. After 3+ consecutive auto-rollbacks, the SDK falls back to the original app store bundle
  5. For apps with heavy initialization, increase the thresholds:
export default withSwiftPatch(App, {
crashDetectionWindowMs: 15000, // 15s window
maxCrashesBeforeRollback: 3,
});

3. Emergency: Disable the release

If users are affected, disable the release via the API:

curl -s -X POST 'https://api.swiftpatch.io/v1/cli/ci/update-release' \
-H 'x-ci-token: YOUR_CI_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"projectId":"YOUR_APP_ID","hash":"BUNDLE_HASH","appVersion":"VERSION","isDisabled":true}'

Update Not Found (Version Mismatch)

Symptoms: SDK logs No matching release found. Update was deployed but users don't receive it.

The --app-version in swiftpatch deploy must exactly match the native binary version.

  • iOS: MARKETING_VERSION in Xcode / CFBundleShortVersionString in Info.plist
  • Android: versionName in build.gradle
warning

If your iOS app reports 42.0, deploy with --app-version 42.0 -- not 42.0.0. The API does an exact string match.

Check your native binary version:

# iOS
grep 'MARKETING_VERSION' ios/YourApp.xcodeproj/project.pbxproj | head -1

# Android
grep 'versionName' android/app/build.gradle

Hash Mismatch Error

Symptoms: Error: "Bundle hash mismatch"

Checklist
  1. The bundle may have been corrupted during download
  2. If using signing, ensure the SDK public key matches the private key used to sign
  3. The SDK rejects mismatched bundles automatically

Bundle Signing Verification Fails

Symptoms: Update rejected with a signature verification error.

  1. Verify the public key matches the private key used in deploy --private-key
  2. Ensure the key pair was generated correctly:
swiftpatch generate-key-pair -o ./keys
  1. Check that .swiftpatchsigned JWT file is included in the bundle ZIP

Slot State Issues

The SDK uses three slots:

  • DEFAULT_SLOT -- Original bundle from the app binary
  • STABLE_SLOT -- Verified OTA bundle
  • NEW_SLOT -- Newly installed bundle, pending verification

Check the current slot:

const { currentBundle } = useSwiftPatch();
console.log('Slot:', currentBundle?.slot);

Native Config Not Found

Symptoms: SDK initializes but doesn't pick up credentials from Info.plist or strings.xml.

Step 1: Verify with getNativeConfig()

import { getNativeConfig } from '@swiftpatch/react-native';
const config = await getNativeConfig();
console.log('App ID:', config.appId);

Step 2: Check the Details

  • Verify key spelling: SwiftPatchAppId (exact match, case-sensitive)
  • iOS: The key must be in Info.plist, not an .xcconfig file
  • Android: The key must be in res/values/strings.xml

Error Codes

CodeDescriptionSolution
NETWORK_ERRORCannot reach the update serverCheck internet and serverUrl
DOWNLOAD_ERRORDownload failedCheck disk space and network; may retry automatically
VERIFICATION_ERRORBundle integrity check failedCheck for corruption or mismatched signing key
PATCH_ERRORDifferential patch failedSDK falls back to full download on next attempt
INSTALL_ERRORInstallation failedCheck disk space; try clearPendingUpdate()
CONFIG_ERRORInvalid configurationCheck deploymentKey and native config
STABILIZE_ERRORBundle stabilization failedCheck slot metadata
ENVIRONMENT_ERROREnvironment switch failedUse valid EnvironmentMode (PROD or STAGE)
UNKNOWN_ERRORUnexpected errorEnable debug: true and check native logs

Using getErrorDescription()

import { getErrorDescription, SwiftPatchErrorCode } from '@swiftpatch/react-native';

const { description, troubleshooting } = getErrorDescription(
SwiftPatchErrorCode.NETWORK_ERROR
);
console.log(description);
console.log(troubleshooting);

Debug Logging

Step 1: Enable Verbose Logs

export default withSwiftPatch(App, {
debug: true,
});

Step 2: View Native Logs

# iOS
xcrun simctl spawn booted log show --predicate 'subsystem == "com.swiftpatch"' --last 5m

# Android
adb logcat -s SwiftPatch

Getting Help

  1. Check the FAQ
  2. Search GitHub Issues
  3. Email support: support@swiftpatch.io