Skip to content

perf/fix: dedupe cold-start checks, move IO off UI threads, CI cost cuts - #654

Merged
sunnylqm merged 26 commits into
masterfrom
claude/optimization-opportunities-89caab
Oct 12, 2026
Merged

sunnylqm merged 26 commits into
masterfrom
claude/optimization-opportunities-89caab

Conversation

@sunnylqm

@sunnylqm sunnylqm commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up review of the code added since the 2026-09 audit (10.55.1 → 10.59.2), implemented layer by layer.

Cross-platform contract

  • C1 – config equivalence. Native ↔ JS config comparisons now use a canonical form (defaults for disabled/queryUrls/rnu/rn/afterDownload, trailing / stripped and deduped in endpoints/queryUrls, key order ignored) instead of exact strings. Before this change, with nativeConfigSource: 'native' the delayed native round never recognised the JS round and every cold start sent a duplicate check on all three platforms. An equivalent config no longer cancels in-flight rounds.
  • The documented flow where the host configures natively first and JS takes over in the default mode is unchanged (an earlier commit rejected it with NATIVE_CONFIG_CONFLICT; that was reverted in 0d3a6a9).

JS

  • Launch deep link is handled once (no replay on setOptions/fast refresh)
  • Concurrent markSuccess calls share one in-flight promise
  • Mirror HEAD ranking runs once per download
  • Native check cache: ts is checked first, plus a per-process "expired" flag
  • nanoid inlined and the dependency dropped

Android

  • bundleHash is computed once on its own thread, so the first check is no longer sent without a hash
  • 64 KB download IO
  • Archive and bundle digests computed while streaming
  • JNI class/field IDs cached in JNI_OnLoad, and registration failures are logged
  • Cleanup protects current, last and running versions; the delete happens outside versionFilesLock via .trash
  • Check cache moved to its own prefs file
  • One shared OkHttp client
  • .so rebuilt (NDK 28.2)

iOS

  • Separate reset and config generations
  • switchVersion hashes the bundle outside the state lock
  • One runnable-config gate
  • Bounded prepareBundle wait, plus new prepareBundleWithTimeout:
  • tvOS purge-restore budget measured from process start, with an offline skip
  • Lazy decoded constants; one shared NSURLSession
  • The native round moved into RCTPushyOrchestrator.mm (pure move)

Harmony

  • Resource copy, the pdiff origin bundle and the bundle hash moved off the UI thread (async fileIo, sha256HexRawFdAsync)
  • fileIo.copyFile
  • Unified config gate
  • New hypium suites

CI / packaging

  • Actions re-pinned to SHAs (a9793d6 had reverted them)
  • Narrower e2e path filters; one iOS build shared by both legs
  • Composite Android setup action
  • Path-gated macOS/C++ test jobs
  • publish waits for native-required
  • Dead deps/scripts removed; root RN types → 0.85; design docs moved to docs/

Behaviour changes (release notes)

  • iOS: the crash-hold handler installs only for a runnable config
  • iOS: new prepareBundleWithTimeout:completion:; the plain prepareBundleWithCompletion: now waits at most 30 minutes (reason timeout)
  • Android: the check-response cache moves to its own prefs file (migrated once)

Verification

  • JS: 348 tests pass, plus lint and tsc
  • C++: patch-core 34/34 and hbc_transform
  • iOS: purge-restore harness (ASan)
  • Android: Java compile, 34 JVM unit tests, .so alignment check
  • Constant and spec parity checks pass
  • Not run: device runs, a full iOS xcodebuild link and a full HAR build. Harmony is being verified locally now; this PR's e2e covers Android and iOS.

Docs: reactnativecn/pushy-site#40 (merge after release).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added iOS bundle preparation with a configurable timeout. If the wait expires, preparation continues in the background and can still apply on the next launch.
    • Added HarmonyOS support for package-diff updates.
  • Improvements
    • Configuration updates now recognize equivalent settings despite differences in key order, default values, or duplicate and trailing-slash URL entries.
    • Improved update reliability with stronger download verification, safer cleanup of older versions, and better handling of cached update checks.
    • Android and HarmonyOS now validate configuration more consistently before running updates.
    • Failed download mirrors are moved later in the retry order.

sunnylqm and others added 19 commits October 12, 2026 11:14
a9793d6 moved every action back to floating major tags while bumping
majors, which left publish.yml (id-token: write) trusting whatever a tag
points at on release day. Pin each one to the commit its current major
tag resolves to, with the exact release in a trailing comment so
Dependabot keeps the SHA/version pairs current (as dependabot.yml says).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- readNativeCheckCache: judge entry age before parsing anything else,
  compare configs with the shared canonical equality (C1:
  nativeConfigsEquivalent in updateFlowCore), and stop reading the cache
  for the rest of the process once it was seen absent or expired.
- markJsCheckCompleted: send once per config after it succeeds.
- syncNativeConfig: forget the synced value when ownership moves to
  'native' so switching back re-writes; a NATIVE_CONFIG_CONFLICT
  rejection (host called native configure()) now console.errors an
  actionable message, goes through emitError/onError and stops retries.
- markSuccess: share the in-flight native call between concurrent
  callers (one native call, one report); cleared once settled.
- performDownload: HEAD-rank mirrors once per strategy per download;
  a failed winner is demoted instead of re-racing every attempt.
- provider: the Linking effect reads parseTestPayload through a ref and
  handles the launch URL once, instead of replaying it on every
  strategy-callback identity change.
- utils: web ping no longer passes an unbound Promise.resolve; inline
  createNonSecureId replaces nanoid/non-secure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…valence and conflict

IO (UI thread):
- SaveFile: rawfile -> sandbox copy now uses async fileIo (was openSync/
  readSync/writeSync on the TurboModule thread); withRawFd serializes
  same-name getRawFd/closeRawFd so one close cannot pull an fd from under
  another reader.
- pdiff: stream the embedded bundle from getRawFd into the temp origin file
  instead of getRawFileContent + write (which also wrote the whole
  underlying ArrayBuffer of the view).
- bundleHash: new sha256HexRawFdAsync(fd, offset, length) in pushy.cpp
  (napi async work, pread loop, short read = reject); in-flight computation
  shared between JS and the native round. Removed the now-unused sync
  sha256Hex / sha256HexFile exports.
- copySandboxFile: fileIo.copyFile (old loop allocated per chunk and took a
  short read for EOF).

Archive limits:
- checkOriginalSize: getOriginalSize failures still refuse the archive, now
  with their own detail ("archive expansion size unavailable") and log line.
- planUnknownLengthFreeSpaceProbe extracted from the download write path.
- Documented why the compression ratio is checked on archive totals (zlib
  exposes no per-entry sizes) and how that differs from archive_limits.h.

Configuration:
- C1: canonical config equivalence (defaults, trailing-slash strip + dedupe
  of endpoints/queryUrls, key order ignored); used by hasJsRound and by
  setNativeConfig (equivalent configs no longer bump generations or drop the
  response cache).
- C2: syncNativeConfig rejects with NATIVE_CONFIG_CONFLICT once the host
  called configure() in this process (local constant until error_codes.h
  carries kNativeConfigConflict).
- SyncCoordinator: one configuration gate for preflight, host API and round,
  with a consistently trimmed appKey.
- Fixed the misplaced setKv doc comment.

Tests: hypium suites for the probe plan, original-size refusal and C1;
constant parity now asserts kUnknownLengthFreeSpaceProbeBytes is parsed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e2e-ios: a new e2e-ios-build job does pod install + xcodebuild (or the
cached-app bundle refresh) and prepares the local update artifacts once,
then uploads the .app and .e2e-artifacts/ios as a tarball (tar keeps the
executable bits upload-artifact drops). The core and native legs keep
their job id and "e2e-ios (core|native)" check names, download the build
and only run Detox. The e2e-cache action's trusted writer moves with the
work to e2e-ios-build; legs restore read-only.

e2e-android: the setup the three jobs duplicated (CLI checkout, node,
bun, caches, Java, KVM, deps, local link) is now the e2e-android-setup
composite action. The rn077 job keys its Gradle cache on the project
generator and the library build.gradle explicitly; it keeps a plain
`bun install` because the generated project has no lockfile to freeze.
Pushes trigger on master only (main does not exist).

Both: drop scripts/** and root bun.lock from the path filters (no e2e
build or test step reads them; package.json stays for codegenConfig)
and stop copying scripts/ into the linked package, which npm never
ships either.

Detox: the per-command --maxWorkers 1 moves into .detoxrc.js as an
Android-only configuration override (one AVD cannot boot twice). iOS
keeps its two workers on cloned simulators.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…build

test.yml: a dorny/paths-filter job gates cpp-test (cpp/, android/jni/,
scripts/test-*) and the macOS ios-purge-restore-test (ios/, cpp/, its
script and fixtures). Skipped jobs report success, so required checks
and native-gate (which reads the workflow conclusion) stay satisfied.
The fuzz step runs apt-get update before installing clang.

lint.yml no longer runs check-native-spec-parity.js a second time; bun
lint already ends with it. harmony-build keeps its own run because it
does not run bun lint.

ci-failure-email also watches harmony-build. Dependabot now scans the
local composite actions, which pin SHAs as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release commit must carry a green native-required status (posted by
native-gate.yml) before either publish job can run; pending or not yet
posted is waited out for up to 45 minutes. The full-history checkout
uses a blobless filter since only tags/commits/trees are read, and the
HAR comment now points at the files whitelist instead of the .npmignore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bundle hash computed once per process on its own thread (shared by JS
  getBundleHash and the native round) instead of queueing on the download
  executor behind cleanup/downloads.
- Downloads: 64KB reads with emitCompleteSegments; archive SHA-256 computed
  while streaming (resume hashes the on-disk prefix first); full packages
  hash index.bundlejs during unzip. Activation re-hash kept (tamper check).
  64KB copy buffers in SafeZipFile/UpdateFileUtils.
- JNI: registration failures are logged; result classes, constructors and
  field IDs cached once in JNI_OnLoad; a missing member logs and makes the
  native throw instead of defaulting fields.
- Cleanup keeps current/last/running (String[] keep) and only renames doomed
  entries into .trash under versionFilesLock; deletion runs after release.
- Native check response cache moved to its own SharedPreferences file
  (migrated); round-incomplete breadcrumb cleared in the commit itself.
- getConstants: launch markers applied asynchronously; bundleSha256 cached
  at launch resolution.
- One parseRunnableConfig for all config gates (appKey trimmed).
- Canonical config equivalence (shared contract) for the JS-round dedupe and
  config-change invalidation; syncNativeConfig rejects with
  NATIVE_CONFIG_CONFLICT once the host called PushyRuntime.configure().
- Shared OkHttpClient base; stale proguard comment fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- babel.config.js, @babel/core and @react-native/babel-preset: nothing at
  the root transpiles with Babel (bun runs the tests, tsc type-checks);
  every Example app carries its own babel config and preset.
- .npmignore: package.json `files` decides what ships and
  check-packlist.js guards it; npm pack --dry-run lists the same 143
  files before and after (only package.json's own size changes).
- scripts/verify-android-so.sh: unreferenced wrapper around
  verify-android-so.js.
- scripts/prepublish.ts and its prepublishOnly hook: on CI it rewrote
  package.json to the tag version (check-release-version.js already
  fails the job unless they match) and re-ran verify-android-so.js
  (publish.yml runs it explicitly in both publish jobs). Its local
  branch only served the also-removed publish:local script; releases go
  through publish.yml.
- nanoid: src is inlining its id generator.

generate-flow-vectors.ts takes URL from node:url: with prepublish.ts
gone the program's global URL (react-native's declaration) no longer
type-checks as a node:fs/node:url path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…host waits

- Config changes: persistConfiguration and hasJsRound compare configs by
  their canonical form (defaults filled, key order ignored, trailing '/'
  stripped and endpoints/queryUrls deduplicated). An equivalent config is a
  no-op; a real change bumps its own pushyNativeConfigGeneration, which the
  round commit checks next to pushyResetGeneration (now reset-only).
- syncNativeConfig rejects with NATIVE_CONFIG_CONFLICT once the host called
  +configure:completion: in this process.
- One PushyLoadRunnableConfig gate for crash-hold install, round start,
  prepareBundle and the round itself (same disabled/appKey rules).
- switchVersion hashes the bundle outside the state lock (+bundleURL takes
  it on the main thread); under the lock it re-checks the record and the
  bundle's inode/size/mtime before switching.
- prepareBundle runs the round on its own queue and waits a bounded time:
  new +prepareBundleWithTimeout:completion:, the old selector waits at most
  30 minutes. Expiry reports failed/timeout.
- tvOS purged-version restore: budget counts from process start, skipped
  when offline (nw_path_monitor) or with < 2s left, and never hashes the
  embedded bundle on that path.
- Encoded constants decode lazily into a byte buffer; the URL regex is
  compiled once; one long-lived NSURLSession; OS version from NSProcessInfo
  instead of main-thread-only UIDevice; the endpoint loop is shared.
- podspec: tvOS deployment target from react-native-tvos (or Expo), 15.1
  fallback, instead of the iOS one; link Network on tvOS.
- Purge-restore harness: late launch, offline and spent-budget cases plus
  canonical config equality.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The root devDependency was still react-native 0.73 while the e2e app
(and every supported host) runs 0.85; align react-native and
@react-native/typescript-config with Example/e2etest (0.85.2). react
and react-test-renderer stay on 18 for the bun:test render tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NATIVE_CHECKUPDATE_DESIGN, NATIVE_CHECK_FOLLOWUPS, BUNDLEHASH_MIGRATION
and NATIVE_HOST_API_MIGRATION leave the repository root. The README
links and the two path-style mentions (check-native-spec-parity.js,
harmony-build.yml) follow; code comments cite them by name only and
need no change. check-packlist.js now forbids docs/ outright instead of
matching the old root file names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pure move, no behavior change. RCTPushyOrchestrator (its statics, the
crash-hold handler and the HTTP/config helpers) now lives in its own file;
the helpers both files need (state lock, defaults, state transitions, key
constants, PushyHasCompletedVersionAtPath, bundle hash) switch from static
to PUSHY_HIDDEN and are declared in the private RCTPushyInternal.h, together
with PushyType, the private RCTPushy interface and the orchestrator
interface. The podspec keeps that header out of the public umbrella.

The purge-restore harness extracts from both sources (one definition across
them) and accepts PUSHY_HIDDEN definitions like static ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rchestrator in the packlist

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7647ed35-7446-488d-aaa4-bbdc72f69e59

📥 Commits

Reviewing files that changed from the base of the PR and between 5703a29 and 3ce96c7.


📒 Files selected for processing (1)
  • Example/e2etest/scripts/local-e2e-server.ts

🚧 Files skipped from review as they are similar to previous changes (1)
  • Example/e2etest/scripts/local-e2e-server.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.



📝 Walkthrough

Walkthrough

This pull request updates CI and E2E workflows, package tooling, and update behavior across JavaScript, Android, Harmony, and iOS. It adds shared configuration equivalence, changes download and cleanup paths, introduces iOS update orchestration, and extends Harmony package-diff E2E coverage.

Changes

CI and E2E workflows

Layer / File(s) Summary
Android E2E setup and execution
.github/actions/e2e-android-setup/action.yml, .github/workflows/e2e_android.yml, Example/e2etest/.detoxrc.js
Adds a shared Android setup action and uses it in Android jobs. Android Detox configurations use a one-worker runner.
Shared iOS E2E build and matrix tests
.github/actions/e2e-cache/action.yml, .github/workflows/e2e_ios.yml
Separates iOS app preparation from core and native test jobs. The build job uploads an app artifact for the test jobs.
Workflow triggers, gates, and pinned actions
.github/dependabot.yml, .github/workflows/*, .github/actions/e2e-cache/action.yml
Changes workflow path filters and native-test conditions, polls for the native-required status before publishing, and pins action references to commit SHAs.

Cross-platform configuration equivalence

Layer / File(s) Summary
Configuration rules and JavaScript implementation
src/updateFlowCore.ts, src/__tests__/*, cpp/update_flow_core/tests/config-equivalence-vectors.json, docs/NATIVE_HOST_API_MIGRATION.md
Adds JavaScript canonical configuration comparison, shared vectors, tests, and migration notes about configuration ownership and equivalent writes.
Android configuration equivalence and sync validation
android/src/main/java/cn/reactnative/modules/update/ConfigEquivalence.java, SyncCoordinator.java, UpdateContext.java, android/src/test/java/cn/reactnative/modules/update/ConfigEquivalenceTest.java
Adds Android canonical comparison and shared runnable-configuration validation. Sync checks use equivalent configurations, and tests replay shared vectors.
Harmony and iOS configuration equivalence
harmony/pushy/src/main/ets/UpdateContext.ts, SyncCoordinator.ts, ios/RCTPushy/RCTPushyOrchestrator.mm, scripts/tests/ios-purge-restore/*
Uses canonical configuration comparisons in Harmony and iOS state decisions. Platform tests exercise configuration-equivalence cases.

JavaScript client behavior

Layer / File(s) Summary
Cache reads and native-operation coordination
src/client.ts, src/__tests__/client.test.ts
Limits native cache reads by age, shares concurrent markSuccess operations, and deduplicates successful completion acknowledgements.
Download mirror ranking across retries
src/client.ts, src/__tests__/client.test.ts
Reuses mirror rankings across retries and moves a failed first-ranked mirror to the end of the ordering.
Launch links and generated IDs
src/provider.tsx, src/utils.ts, src/core.ts, src/__tests__/*
Processes the initial launch URL once, uses the latest URL parser, and generates web IDs with a local helper.

Android native runtime

Layer / File(s) Summary
Configuration, download hashing, and version cleanup
android/src/main/java/cn/reactnative/modules/update/*, cpp/patch_core/patch_core.*, cpp/patch_core/patch_core_android.cpp
Computes SHA-256 during download and extraction where available. Cleanup protects selected live versions, moves expired entries into trash, then purges the trash. Android sync uses shared configuration validation.
JNI metadata and registration
cpp/patch_core/jni_natives.h, jni_registration.cpp, update_core_android.cpp, jni_log.h, android/jni/Android.mk
Caches JNI result-class metadata and logs registration or metadata lookup failures. Cleanup JNI calls accept a keep-name array and trash path.

Harmony native runtime

Layer / File(s) Summary
Raw-file access, copying, and hashing
harmony/pushy/src/main/cpp/pushy.cpp, harmony/pushy/src/main/ets/SaveFile.ts, DownloadTask.ts, UpdateContext.ts, NativePatchCore.ts
Adds asynchronous descriptor-range hashing and serialized raw-file access. Bundle hashing shares in-flight work, and sandbox copies use asynchronous reads and complete writes.
Archive and free-space checks
harmony/pushy/src/main/ets/ArchiveLimits.ts, DownloadTask.ts, harmony/pushy/src/test/ArchiveLimits.test.ets
Validates reported archive expansion sizes and plans free-space reservations for unknown-length downloads. Tests cover validation errors and reservation boundaries.

Harmony E2E package-diff flow

Layer / File(s) Summary
Signed HAP package diff and E2E verification
Example/e2etest/scripts/prepare-local-update-artifacts.ts, local-e2e-server.ts, Example/e2etest/e2e/harmony/*, Example/harmony_use_pushy/e2e/*
Generates a Harmony package diff from the signed HAP, records reported bundle hashes, and adds an E2E step that checks the hash and applies the v3 package diff.

iOS synchronization and activation

Layer / File(s) Summary
Internal interface and activation checks
ios/RCTPushy/RCTPushyInternal.h, RCTPushy.h, RCTPushy.mm, RCTPushyConfiguration.mm
Adds shared internal declarations, verifies install records and file stamps during version switching, and adds a timeout-taking host preparation method.
Update rounds, crash rescue, and tvOS restore
ios/RCTPushy/RCTPushyOrchestrator.mm
Adds update-round scheduling, endpoint querying, downloads, guarded state commits, crash rescue, and bounded tvOS purged-launch restore.
Purge-restore extraction and tests
scripts/tests/ios-purge-restore/*, scripts/test-ios-purge-restore.sh
Extends extraction across both iOS implementation files. Tests cover elapsed-time budgets, offline skips, late launches, and configuration vectors.

Package and build maintenance

Layer / File(s) Summary
Package contents and release tooling
.npmignore, babel.config.js, package.json, scripts/prepublish.ts, scripts/verify-android-so.sh, scripts/check-packlist.js
Removes prepublish and package-local scripts, the nanoid dependency, .npmignore, and Babel configuration. Updates React Native development dependencies and package-list rules.
Platform settings and repository references
react-native-update.podspec, README.md, README-CN.md, CODE_AUDIT.md, scripts/check-native-spec-parity.js, scripts/generate-flow-vectors.ts
Sets separate iOS and tvOS deployment targets, adds the tvOS Network framework, and updates documentation and script references.

Priority: ➖ Normal

Estimated code review effort: 5 (Critical) | ~90 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant RCTPushyOrchestrator
  participant NSURLSession
  participant RCTPushy
  App->>RCTPushyOrchestrator: schedule startup or prepare a round
  RCTPushyOrchestrator->>RCTPushyOrchestrator: validate configuration and claim the round
  RCTPushyOrchestrator->>NSURLSession: query configured endpoints
  NSURLSession-->>RCTPushyOrchestrator: return response body
  RCTPushyOrchestrator->>RCTPushy: download an update when required
  RCTPushy-->>RCTPushyOrchestrator: return downloaded version metadata
  RCTPushyOrchestrator->>RCTPushyOrchestrator: commit when generations still match
  RCTPushyOrchestrator-->>App: return round result
Loading

Merge Risk: 🔵 Low · up to 3ce96

The Harmony hash check is connected correctly. An existing iOS bundle-verification concern remains open and should have owner follow-up.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 34.12% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 170 functions across 58 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main changes: deduplicating cold-start checks, moving I/O off UI threads, and reducing CI costs.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

















🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

sunnylqm and others added 2 commits October 12, 2026 11:42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Host configure() followed by a JS takeover in the default 'javascript'
mode is a documented, supported flow (last completed write wins).
Rejecting it with NATIVE_CONFIG_CONFLICT would break existing
integrations. The underlying harm is already gone: equivalent configs
(C1) no longer count as a change, so they neither drop the response
cache nor cancel in-flight rounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/client.ts (1)

1094-1104: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

A config mismatch does not set nativeCheckCacheExhausted, so later checks call the bridge again.

A missing or expired entry sets the flag. A fresh entry that fails the config or request match does not. The age gate is 120 seconds. During that window, every check with a different extra or a changed config reads and parses the cache again. Each of these reads is a miss. This costs extra work but returns correct results. The flag comment says the flag covers all near-certain misses. A mismatched entry does not become a match later in the same process, unless options revert. Consider setting the flag in this case as well, or narrow the comment.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Review comment at @src/client.ts around lines 1094 - 1104:
Update the cache-miss path around nativeConfigsEquivalent and jsonValuesEqual to
set nativeCheckCacheExhausted when a fresh entry fails the config or request
match, so subsequent checks skip repeated reads and parsing. Preserve the
existing behavior for matching entries and missing or expired entries.

  • 🪄 Fix CodeRabbit comments on this PR
🤖 Prompt to fix review comments
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Inline comments:
Review comments at @.github/workflows/test.yml:
- Around line 37-43: Update the cpp path filter to include src/updateFlowCore.ts
and the inputs used by its golden vectors, ensuring changes to the TypeScript
reference or those vectors trigger the C++ parity job.

Review comments at
@android/src/main/java/cn/reactnative/modules/update/DownloadTask.java:
- Around line 782-786: In the phase-two trash purge in doCleanUp, catch
RuntimeException from cleanupOldEntries and log the failure so a purge error
does not fail the cleanup task; leave phase-one behavior unchanged.

Review comments at
@android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java:
- Line 49: Restore the default-mode configuration handover caveat in the
PushyRuntime documentation: note that a later JavaScript configuration write can
replace the value saved by PushyRuntime.configure, and that setting
nativeConfigSource to 'native' retains native ownership.

Review comments at @ios/RCTPushy/RCTPushy.mm:
- Around line 835-842: Update PushyRunHostPreparation to create hostQueue as a
concurrent queue so each request’s timeout begins when its call is made, while
preserving the existing single-flight round coordination and main-queue
completion delivery.

Review comments at @package.json:
- Line 103: Update the root manifest’s React dependency to 19.2.3 and align
react-test-renderer and @types/react-test-renderer with React 19. Regenerate
bun.lock so the locked dependency tree reflects these compatible versions.

---

Nitpick comments:
Review comments at @src/client.ts:
- Around line 1094-1104: Update the cache-miss path around
nativeConfigsEquivalent and jsonValuesEqual to set nativeCheckCacheExhausted
when a fresh entry fails the config or request match, so subsequent checks skip
repeated reads and parsing. Preserve the existing behavior for matching entries
and missing or expired entries.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli?utm_source=ghpr

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: eb6470d4-3b59-4145-84af-4fc9db89d067
📥 Commits

Reviewing files that changed from the base of the PR and between b33cc5c and 0d3a6a9.

⛔ Files ignored due to path filters (5)
  • android/lib/arm64-v8a/librnpushy.so is excluded by !**/*.so
  • android/lib/armeabi-v7a/librnpushy.so is excluded by !**/*.so
  • android/lib/x86/librnpushy.so is excluded by !**/*.so
  • android/lib/x86_64/librnpushy.so is excluded by !**/*.so
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (87)
  • .github/actions/e2e-android-setup/action.yml
  • .github/actions/e2e-cache/action.yml
  • .github/dependabot.yml
  • .github/workflows/ci-failure-email.yml
  • .github/workflows/e2e_android.yml
  • .github/workflows/e2e_harmony.yml
  • .github/workflows/e2e_ios.yml
  • .github/workflows/harmony-build.yml
  • .github/workflows/lint.yml
  • .github/workflows/native-gate.yml
  • .github/workflows/publish.yml
  • .github/workflows/test.yml
  • .npmignore
  • CODE_AUDIT.md
  • Example/e2etest/.detoxrc.js
  • README-CN.md
  • README.md
  • android/jni/Android.mk
  • android/proguard.pro
  • android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java
  • android/src/main/java/cn/reactnative/modules/update/ConfigEquivalence.java
  • android/src/main/java/cn/reactnative/modules/update/DownloadTask.java
  • android/src/main/java/cn/reactnative/modules/update/HttpClients.java
  • android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java
  • android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java
  • android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java
  • android/src/main/java/cn/reactnative/modules/update/UpdateContext.java
  • android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java
  • android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java
  • android/src/test/java/cn/reactnative/modules/update/CleanupKeepNamesTest.java
  • android/src/test/java/cn/reactnative/modules/update/ConfigEquivalenceTest.java
  • babel.config.js
  • cpp/patch_core/digest.h
  • cpp/patch_core/jni_log.h
  • cpp/patch_core/jni_natives.h
  • cpp/patch_core/jni_registration.cpp
  • cpp/patch_core/patch_core.cpp
  • cpp/patch_core/patch_core.h
  • cpp/patch_core/patch_core_android.cpp
  • cpp/patch_core/tests/patch_core_test.cpp
  • cpp/patch_core/update_core_android.cpp
  • docs/BUNDLEHASH_MIGRATION.md
  • docs/NATIVE_CHECKUPDATE_DESIGN.md
  • docs/NATIVE_CHECK_FOLLOWUPS.md
  • docs/NATIVE_HOST_API_MIGRATION.md
  • harmony/pushy/src/main/cpp/pushy.cpp
  • harmony/pushy/src/main/ets/ArchiveLimits.ts
  • harmony/pushy/src/main/ets/DownloadTask.ts
  • harmony/pushy/src/main/ets/NativePatchCore.ts
  • harmony/pushy/src/main/ets/SaveFile.ts
  • harmony/pushy/src/main/ets/SyncCoordinator.ts
  • harmony/pushy/src/main/ets/UpdateContext.ts
  • harmony/pushy/src/test/ArchiveLimits.test.ets
  • harmony/pushy/src/test/List.test.ets
  • harmony/pushy/src/test/NativeConfig.test.ets
  • harmony/pushy/src/test/check-constant-parity.js
  • ios/RCTPushy/RCTPushy.h
  • ios/RCTPushy/RCTPushy.mm
  • ios/RCTPushy/RCTPushyConfiguration.mm
  • ios/RCTPushy/RCTPushyInternal.h
  • ios/RCTPushy/RCTPushyOrchestrator.mm
  • package.json
  • react-native-update.podspec
  • scripts/check-native-spec-parity.js
  • scripts/check-packlist.js
  • scripts/generate-flow-vectors.ts
  • scripts/prepublish.ts
  • scripts/test-ios-purge-restore.sh
  • scripts/tests/ios-purge-restore/README.md
  • scripts/tests/ios-purge-restore/extract.py
  • scripts/tests/ios-purge-restore/purge_restore_test.mm
  • scripts/verify-android-so.sh
  • src/__tests__/client.test.ts
  • src/__tests__/core.test.ts
  • src/__tests__/jniRegistration.test.ts
  • src/__tests__/nativeConfiguration.test.ts
  • src/__tests__/nativeHostApi.test.ts
  • src/__tests__/nativeHostApiNaming.test.ts
  • src/__tests__/nativeTextEncoding.test.ts
  • src/__tests__/provider.render.test.tsx
  • src/__tests__/updateFlowCore.test.ts
  • src/__tests__/utils.test.ts
  • src/client.ts
  • src/core.ts
  • src/provider.tsx
  • src/updateFlowCore.ts
  • src/utils.ts
💤 Files with no reviewable changes (4)
  • scripts/verify-android-so.sh
  • babel.config.js
  • .npmignore
  • scripts/prepublish.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread .github/workflows/test.yml
Comment thread android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java Outdated
Comment thread ios/RCTPushy/RCTPushy.mm
Comment thread package.json
sunnylqm and others added 4 commits October 12, 2026 11:58
The Harmony suite never exercised the package diff (patching against the
bundle embedded in the hap), and nothing checked that the bundleHash the
client reports matches that bundle. Both paths now read the rawfile via
an fd, so cover them on device:

- prepare builds v3 for harmony and, once the hap exists, diffs it with
  hdiffFromApp; build-harmony-e2e.sh reruns that step after assembleHap
- the local server serves pdiff v3 to harmony and records the last
  bundleHash each platform sent (/control/last-bundle-hash)
- the suite asserts bundleHash == sha256(hap rawfile bundle) and walks
  v2 -> pdiff v3 -> v2-track v4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ios: run host prepareBundle calls on a concurrent queue so each
  timeout counts from its own call (a serial queue queued a timed call
  behind an earlier call's full wait)
- android: a failed trash purge is logged, not a failed cleanup task
- android: restore the configure() note that JS writes can replace the
  config unless nativeConfigSource is 'native'
- ci: run the C++ parity job when the TS reference or vector generator
  changes
- deps: react / react-test-renderer 19.2.3 to match react-native 0.85
- js: clarify when the native check cache is marked exhausted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tforms

cpp/update_flow_core/tests/config-equivalence-vectors.json (58 cases, not
packed: !cpp/**/tests) is now the single contract for native config
equivalence. Each case is checked in both argument orders by:
- JS + Harmony: src/__tests__/configEquivalenceVectors.test.ts (Harmony's
  UpdateContext.ts evaluated in a vm with imports stripped)
- Android: ConfigEquivalenceTest (locates the file by walking up from the
  module dir, works in-repo and from node_modules)
- iOS: the purge-restore harness's config_equivalence case

The vectors exposed two outliers, fixed to the agreed rule:
- JS did not dedupe non-string entries of endpoints/queryUrls
  ([1,1] stayed [1,1]); Android, iOS and Harmony dedupe by JSON value.
- Android accepted trailing content after the object ('{"a":1} x' and two
  concatenated objects) because JSONTokener.nextValue() stops after the
  first value; JS, iOS and Harmony reject it as invalid JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Require a digest in current-schema install records. · RCTPushy.mm:244-245

ios/RCTPushy/RCTPushy.mm:244-245
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Require a digest in current-schema install records.

If Sha256File returns an empty value during installation, the writer omits bundleSha256. This branch then accepts the record without checking the bundle bytes, so switchVersion: can select an unverified bundle. Fail the install when hashing fails, and reject current-schema records without a digest. Continue to allow legacy empty records.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Review comment at @ios/RCTPushy/RCTPushy.mm around lines 244 - 245:
Update the install writer to fail when Sha256File produces an empty digest, and
update the expected-digest validation so current-schema records without
bundleSha256 are rejected before switchVersion: can select them. Preserve
acceptance of legacy empty records.

  • 🪄 Fix CodeRabbit comments on this PR
🤖 Prompt to fix review comments
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Inline comments:
Review comments at @Example/e2etest/e2e/harmony/local-update.test.ts:
- Around line 127-132: Update the `checkUpdate` request handler that records
`lastBundleHash` so every request overwrites the platform’s stored value, using
an empty string when `payload.bundleHash` is missing or not a string; preserve
an explicitly supplied empty string. This ensures `lastReportedBundleHash()`
reflects the latest request rather than an earlier non-empty hash.

---

Outside diff comments:
Review comments at @ios/RCTPushy/RCTPushy.mm:
- Around line 244-245: Update the install writer to fail when Sha256File
produces an empty digest, and update the expected-digest validation so
current-schema records without bundleSha256 are rejected before switchVersion:
can select them. Preserve acceptance of legacy empty records.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli?utm_source=ghpr

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c89a0808-33bd-4d9d-b892-75bb479d3ff0
📥 Commits

Reviewing files that changed from the base of the PR and between 0d3a6a9 and 5703a29.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • .github/workflows/test.yml
  • Example/e2etest/e2e/harmony/README.md
  • Example/e2etest/e2e/harmony/local-update.test.ts
  • Example/e2etest/scripts/build-harmony-e2e.sh
  • Example/e2etest/scripts/local-e2e-server.ts
  • Example/e2etest/scripts/prepare-local-update-artifacts.ts
  • Example/harmony_use_pushy/e2e/entry.v3.ts
  • Example/harmony_use_pushy/e2e/localUpdateConfig.ts
  • android/src/main/java/cn/reactnative/modules/update/ConfigEquivalence.java
  • android/src/main/java/cn/reactnative/modules/update/DownloadTask.java
  • android/src/test/java/cn/reactnative/modules/update/ConfigEquivalenceTest.java
  • cpp/update_flow_core/tests/config-equivalence-vectors.json
  • ios/RCTPushy/RCTPushy.mm
  • package.json
  • scripts/tests/ios-purge-restore/README.md
  • scripts/tests/ios-purge-restore/purge_restore_test.mm
  • src/__tests__/configEquivalenceVectors.test.ts
  • src/client.ts
  • src/updateFlowCore.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/tests/ios-purge-restore/README.md
  • src/client.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Comment thread Example/e2etest/e2e/harmony/local-update.test.ts
…on-empty one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sunnylqm
sunnylqm merged commit 7e4e095 into master Oct 12, 2026
14 checks passed
@sunnylqm
sunnylqm deleted the claude/optimization-opportunities-89caab branch October 12, 2026 05:14
sunnylqm added a commit that referenced this pull request Oct 12, 2026
Bump package.json and harmony/pushy/oh-package.json5 together. Ships the
cross-layer cleanup (#657): the native round, patch-manifest parsing,
install records and archive limits now live once in the shared C++ core,
plus the unreleased #654/#655 fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant