home / skills / onekeyhq / app-monorepo / 1k-patching-native-modules

1k-patching-native-modules skill

/.claude/skills/1k-patching-native-modules

This skill analyzes crash logs from iOS and Android native modules to generate patches that fix crashes and stabilize builds.

npx playbooks add skill onekeyhq/app-monorepo --skill 1k-patching-native-modules

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.2 KB
---
name: 1k-patching-native-modules
description: Patches native modules (expo-image, react-native, etc.) to fix native crashes or bugs. 
disable-model-invocation: true
---

# Patching Native Modules

Follow this workflow to analyze crash logs, fix native module bugs, and generate patches.

## Workflow Overview

```
1. Analyze Crash Log → 2. Locate Bug → 3. Fix Code → 4. Clean Build Artifacts → 5. Generate Patch → 6. Commit & PR
```

## Step 1: Analyze Crash Log

### iOS Crash (EXC_BAD_ACCESS / KERN_INVALID_ADDRESS)

Key information to extract:
- **Exception type**: `EXC_BAD_ACCESS`, `SIGABRT`, etc.
- **Stack trace**: Identify the crashing function
- **Memory address**: Helps identify nil pointer issues
- **Library**: Which native module is crashing

Example crash pattern:
```
EXC_BAD_ACCESS: KERN_INVALID_ADDRESS at 0x3c61c1a3b0d0
 objc_msgSend in unknown file
 -[SDWebImageManager cacheKeyForURL:context:]  ← Crashing function
 -[SDWebImageManager loadImageWithURL:options:context:progress:completed:]
```

### Android Crash (NullPointerException / OOM)

Look for:
- **Exception class**: `NullPointerException`, `OutOfMemoryError`
- **Stack trace**: Java/Kotlin method chain
- **Thread info**: Main thread vs background

## Step 2: Locate the Bug

### Find native module source

```bash
# iOS (Swift/Objective-C)
ls node_modules/<package>/ios/

# Android (Kotlin/Java)
ls node_modules/<package>/android/src/main/java/
```

### Common crash causes

| Crash Type | Common Cause | Fix Pattern |
|------------|--------------|-------------|
| `EXC_BAD_ACCESS` | Nil pointer dereference | Add `guard let` check |
| `KERN_INVALID_ADDRESS` | Accessing deallocated memory | Use weak references |
| `NullPointerException` | Null object access | Add null check |
| `OutOfMemoryError` | Large image/data processing | Add size limits |

## Step 3: Fix the Code

### iOS (Swift) - Nil Check Pattern

```swift
// Before (crashes when uri is nil)
imageManager.loadImage(with: source.uri, ...)

// After (safe)
guard let sourceUri = source.uri, !sourceUri.absoluteString.isEmpty else {
  onError(["error": "Image source URI is nil or empty"])
  return
}
imageManager.loadImage(with: sourceUri, ...)
```

### Android (Kotlin) - Null Check Pattern

```kotlin
// Before
val uri = source.uri
loadImage(uri)

// After
val uri = source.uri ?: return
if (uri.toString().isEmpty()) return
loadImage(uri)
```

## Step 4: Clean Build Artifacts (CRITICAL)

**Before generating patch, MUST clean Android build cache:**

```bash
# Remove Android build artifacts to avoid polluting the patch
rm -rf node_modules/<package>/android/build

# For expo-image specifically:
rm -rf node_modules/expo-image/android/build
```

Why this matters:
- Android build generates `.class`, `.jar`, binary files
- These pollute the patch file (can grow to 5000+ lines)
- patch-package will include these unwanted files

## Step 5: Generate Patch

```bash
# Generate patch file
npx patch-package <package-name>

# Example:
npx patch-package expo-image
```

Patch file location: `patches/<package-name>+<version>.patch`

### Verify patch content

```bash
# Check patch doesn't include unwanted files
grep -c "android/build" patches/<package-name>*.patch
# Should return 0

# View actual changes
head -100 patches/<package-name>*.patch
```

## Step 6: Commit & Create PR

```bash
# Stage patch file
git add patches/<package-name>*.patch

# Commit with descriptive message
git commit -m "fix(ios): prevent EXC_BAD_ACCESS crash in <package> when <condition>

Add guard checks in <package> native layer to prevent crash when <scenario>.

Fixes Sentry issue #XXXXX"

# Create PR
gh pr create --title "fix(ios): <description>" --base x
```

## Common Packages & Their Native Locations

| Package | iOS Source | Android Source |
|---------|------------|----------------|
| `expo-image` | `node_modules/expo-image/ios/` | `node_modules/expo-image/android/src/` |
| `react-native` | `node_modules/react-native/React/` | `node_modules/react-native/ReactAndroid/` |
| `@react-native-async-storage/async-storage` | `node_modules/@react-native-async-storage/async-storage/ios/` | `...android/src/` |
| `react-native-reanimated` | `node_modules/react-native-reanimated/ios/` | `...android/src/` |

## Existing Patches Reference

Check existing patches for patterns:
```bash
ls patches/
cat patches/expo-image+3.0.10.patch
```

## Troubleshooting

### Patch file too large

```bash
# Clean all build artifacts
rm -rf node_modules/<package>/android/build
rm -rf node_modules/<package>/ios/build
rm -rf node_modules/<package>/.gradle

# Regenerate
npx patch-package <package>
```

### Patch not applying

```bash
# Check package version matches
cat node_modules/<package>/package.json | grep version

# Rename patch if version changed
mv patches/<package>+old.patch patches/<package>+new.patch
```

### Swift/Kotlin syntax help

**Swift guard let:**
```swift
guard let value = optionalValue else {
  return  // Must exit scope
}
// value is now non-optional
```

**Kotlin null check:**
```kotlin
val value = nullableValue ?: return
// value is now non-null
```

## Related Files

- Patches directory: `patches/`
- expo-image iOS: `node_modules/expo-image/ios/ImageView.swift`
- expo-image Android: `node_modules/expo-image/android/src/main/java/expo/modules/image/`

Overview

This skill patches native mobile modules (iOS/Android) such as expo-image and react-native to fix native crashes and runtime bugs. It guides through analyzing crash logs, locating the faulty native code, applying safe nil/null checks, cleaning build artifacts, and generating small reproducible patch files. The workflow is secure, repeatable, and focused on avoiding polluted patches.

How this skill works

It inspects crash logs (EXC_BAD_ACCESS, SIGABRT, NullPointerException, OOM) to identify the crashing function and the implicated native package. You locate the module source in node_modules, apply minimal native fixes (Swift/Kotlin nil checks, weak references, size limits), clean Android/iOS build artifacts, and generate a patch with patch-package. It includes checks to verify the patch does not include build outputs and steps to commit and create a PR.

When to use it

  • A native crash references code inside node_modules for a known package (expo-image, reanimated, etc.).
  • Crashes show EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, SIGABRT on iOS or NullPointerException/OutOfMemoryError on Android.
  • You need a small focused change you can ship as a patch without forking the library.
  • You must avoid binary/build artifacts in generated patches to keep diffs small and reviewable.

Best practices

  • Always extract exception type, stack trace, thread info and exact crashing function from the crash log before changing code.
  • Add minimal, defensive checks (guard let in Swift, ?: return in Kotlin) rather than broad refactors.
  • Remove node_modules/<package>/android/build and other build dirs before running npx patch-package to avoid binary pollution.
  • Verify patch content with grep/head to ensure no android/build or .class/.jar entries are included.
  • Commit patch with a descriptive message referencing the crash and Sentry or issue number when available.

Example use cases

  • Fix EXC_BAD_ACCESS in expo-image by guarding against nil image URIs in ImageView.swift.
  • Resolve NullPointerException in a React Native module by adding Kotlin null checks in android/src files.
  • Limit image decoding size to avoid OutOfMemoryError on Android in a native image loader.
  • Create a small patch for @react-native-async-storage to prevent a crash on null input and push a PR with the patch file.

FAQ

What if the patch contains binary/build files?

Remove android/ios build folders in node_modules/<package> and regenerate the patch; verify with grep that no build paths are present.

How do I handle version mismatches so the patch applies?

Check node_modules/<package>/package.json for the installed version. Rename the patch file to match the package+version or update the dependency so versions align.