HotupdaterHot Updater
Operate

Diagnose setup and updates

Check Hot Updater package versions, native setup, and server infrastructure.

Overview

Use hot-updater doctor to check the parts that must agree for OTA updates to work:

  • All Hot Updater packages use compatible versions
  • React Native native files are connected to Hot Updater
  • fingerprint.json still matches the project, for the fingerprint update strategy
  • The app adds the client plugins that the server's plugins need
  • The server's release catalogs match their Releases
  • Your deployed infrastructure server is not outdated

With --fix, doctor also repairs what it can repair itself.

Run it during setup, after upgrading Hot Updater, or when OTA updates behave differently after a package update. The ordinary command validates local configuration and server version. The scoped infrastructure checks also verify scaffold files and authenticated server access. Complete Test an OTA update to verify actual device delivery and that the installed app sends its client key.

npx hot-updater doctor

In an interactive terminal, the command asks for your server base URL. Press Enter to skip the server check.

For AI agents and other automation, use JSON output:

npx hot-updater doctor --json

--json does not prompt for the server URL. Pass --server-base-url when you want the infrastructure check included.

Verify infrastructure scaffolding

Use an explicit scope when an agent or CI needs a completion gate. These checks run without evaluating the app config and do not require a React Native app in the current directory.

npx hot-updater doctor --scope scaffold --infra-dir ./infra --json

scaffold checks the selected directory against the templates packaged with the invoked CLI: supported manifest identity and versions, required files and safe paths, unresolved deployment placeholders, JSON syntax, and provider settings. Provider checks cover Cloudflare DB/BUCKET bindings and runtime entry point, Supabase Function authentication configuration, AWS catalog/artifact/version routes and API-key cache separation, Firebase project/functions selection, the Vercel rewrite to the supplied Function, and Azure's Function package and route prefix. Use the CLI version that generated the scaffold. An older scaffold requires its matching CLI or a fresh upgrade scaffold; editing its version fields is not an upgrade.

A newly extracted scaffold normally fails until resource placeholders are filled. Doctor preserves custom settings and does not demand unchanged template hashes. It derives required files from the installed CLI, so deleting a manifest entry cannot hide a missing file. It never runs scripts from the scaffold.

After deployment, require the live server checks too:

npx hot-updater doctor --scope infrastructure --infra-dir ./infra --json \
  --server-base-url https://updates.example.com \
  --platform ios --channel production --app-version 1.0.0

For fingerprint projects, replace --app-version with --fingerprint. Provide all target inputs explicitly. When the scaffolded server's plugins protect client routes, as the managed servers' apiKeys() does, doctor reads the client credential privately from the variable the scaffold's clientAuth names (HOT_UPDATER_API_KEY), the app directory's .env.hotupdater, or scaffold app/client-credential.local. Conflicting saved credentials block verification.

infrastructure reruns scaffold checks, rejects unresolved deployment records, and verifies /version against the scaffold target. It checks the same Release Catalog URL without a credential (401) and with the saved one (valid catalog 200 or the empty-catalog 404 marked x-hot-updater-catalog: none); when the scaffolded server's client routes are public, it checks only that the catalog answers without a credential. A 404 without that mark is a failure. An agent-written verifiedSteps entry never replaces a live check.

Both scopes return details.verification with schemaVersion: 1, scope, checks, and notChecked. Each check has a stable code, a status of pass, fail, or blocked, and actionable paths, fixability, and resolution when available. Exit code 0 and success: true require every check in the selected scope to pass. A failed prerequisite leaves dependent checks blocked.

fixability describes remediation; it does not grant or revoke provider access. Agents may continue already authorized repairs and then rerun doctor. CI should use the exit code as the gate, rather than parsing human-readable output or accepting a recorded completion claim.

These scopes do not verify actual remote resource bindings or migration history, local storage credentials/write access, artifact downloads, JS/native integration, or native OTA execution. These boundaries appear in notChecked; a scope pass must not be reported as native OTA success. Run the ordinary doctor for its existing package/native checks and complete the provider checklist for the remaining checks.

Check Native Setup

When @hot-updater/react-native is installed and native iOS or Android directories exist, doctor checks:

  • iOS AppDelegate uses HotUpdater.bundleURL()
  • Android MainApplication uses HotUpdater.getJSBundleFile(...)
  • If updateStrategy is fingerprint, fingerprint.json exists, native fingerprint hashes match it, and it matches the project's fingerprint now
  • Signing settings match native public key entries

The native status also shows each platform's app version, the version the native project builds (appVersion in details.native.ios and details.native.android). To read only the versions, such as in a release script, run hot-updater app-version --json, which prints { "android": ..., "ios": ... } with null for a platform it can't read.

If a required native setting is missing, run the command shown by doctor, then rebuild the native app.

doctor does not require a default channel in native files. If HOT_UPDATER_CHANNEL in iOS Info.plist or com.hotupdater.CHANNEL in Android manifest metadata exists, the command reports it as context only.

For fingerprint projects, doctor computes the project's fingerprint as fingerprint create does, which hashes the native inputs and can take a few seconds. When a platform's fingerprint differs from fingerprint.json, doctor reports FINGERPRINT_JSON_STALE and lists the sources that changed, in changes with --json. When the hash changed because the fingerprint engine changed rather than a native input, it reports FINGERPRINT_ENGINE_CHANGED instead and names both engines. A fingerprint.json that doesn't record the current engine, because it predates recorded engines or another engine version computes the same hash, gets the warning FINGERPRINT_ENGINE_UNRECORDED; recording the engine keeps the fingerprint the same. When it cannot compute the fingerprint, for example because react-native config fails, it reports FINGERPRINT_GENERATION_FAILED with the reason.

Under the App Version strategy, a fingerprint.json left in the project is FINGERPRINT_JSON_UNUSED: native builds embed its fingerprint, so the app would report the fingerprint strategy. Delete it, or set updateStrategy to "fingerprint" if that is the strategy you meant.

Fix missing or stale fingerprint settings with fingerprint create, or with doctor --fix, which runs the same repair:

npx hot-updater fingerprint create

Check client plugins

A server plugin can name the client plugin an app adds for it, as insights() names the Insights client plugin. In a project that installs @hot-updater/react-native and sets database in hot-updater.config.ts, doctor reads the client plugins that the server plugins in the config's plugins name. It then warns MISSING_CLIENT_PLUGIN for each client plugin that no source file in the project imports:

The server runs a plugin whose client plugin insights the app does not add.

Import it from the module the warning names and pass it to HotUpdater.init({ plugins }); the issue's fixability is auto. When doctor cannot load the config's database and plugins, it warns CLIENT_PLUGINS_UNCHECKED with the reason, with fixability blocked: make them load, then rerun doctor.

Repair issues

--fix runs the repairs doctor can make itself, then checks again:

IssuesRepair
MISSING_FINGERPRINT_JSON, FINGERPRINT_JSON_STALE, FINGERPRINT_ENGINE_UNRECORDEDWhat fingerprint create writes, where it differs: fingerprint.json
FINGERPRINT_JSON_UNUSEDfingerprint.json, deleted to match the configured updateStrategy
MISSING_PUBLIC_KEY, PUBLIC_KEY_MISMATCHWhat keys export-public --yes writes: the configured signing public key, in the native files
RELEASE_CATALOG_STALEEach stale scope's catalog, rebuilt from its Releases in the server's database
UNREFERENCED_ARTIFACTSThe artifact records no Release uses, deleted from the server's database
npx hot-updater doctor --fix

--fix is the confirmation, so it does not ask again. It names every file, release catalog, and artifact record it writes, and the checks after it describe the project as the repairs left it. When it writes a native file, its output ends by asking you to rebuild the native app: an installed app keeps the native files it was built with. A replaced public key only reaches new builds; apps already installed keep the previous key and reject bundles signed with the configured one.

--fix leaves an issue with more than one remedy to you. It never rewrites fingerprint.json for FINGERPRINT_ENGINE_CHANGED: go back to the engine fingerprint.json records to keep updating the apps you shipped, or run fingerprint create and rebuild them. That choice is the user's, so the issue's fixability is blocked and it lists no command. It never removes a public key. For ORPHAN_PUBLIC_KEY, remove the key with keys remove or enable signing yourself: removing it turns off signature verification in the app, which is wrong when signing is set up where this machine cannot see it, such as an environment variable only CI sets.

On an Expo project whose ios and android folders expo prebuild generates, --fix skips the native-file repairs and says so, since the next prebuild would overwrite them; an Expo project outside git counts as one. Follow the issue's resolution there instead. Issues that need a code edit, such as the bundle provider or a client plugin, and infrastructure issues are not repaired by --fix.

With --json, details.fixes lists each repair: its repair, the issue codes it repairs, a status of applied, skipped, or failed, what it wrote, whether that included a native file, and a note with the reason or what to know. success describes the project after the repairs.

AI Agent Iteration

doctor --json returns structured issues that an agent can use without parsing human-readable terminal output.

Each native issue includes:

  • code: stable issue code, such as MISSING_ANDROID_BUNDLE_PROVIDER
  • platform: ios, android, or project
  • paths: related local files when available
  • fixability: whether the issue is suitable for an agent loop
  • commands: safe local commands when the fix is command-driven

Read fixability as the repair prerequisite, within the user's authorized work:

FixabilityMeaning
autoThe agent may inspect and edit local project files, then rerun doctor.
commandThe agent may run the listed local command, then rerun doctor.
blockedThe repair requires external context/access beyond doctor’s local fixes.

Native wiring, fingerprint creation, and native public key export/removal are intended to be iterated locally by an agent. doctor --fix creates the fingerprint and exports the public key in one step, naming every file it writes; it never removes a public key. Infrastructure failures are marked as blocked because applying migrations or redeploying a server usually depends on provider credentials and environment-specific access. During a requested setup or upgrade, an agent with that access should follow the generated infrastructure instructions and verify the result. Pause only for a missing prerequisite, uncertain remote state, or a new consequential decision.

Check Infrastructure

To check the deployed server in CI or a non-interactive shell, pass the public update-check base URL.

npx hot-updater doctor --server-base-url https://updates.example.com

doctor appends /version and checks:

https://updates.example.com/version

A passing version check confirms the server meets the installed CLI's infrastructure requirement. It does not verify a client key, artifact download or native update application. Complete the OTA test to verify delivery through the app's actual configuration. For a separate server check during agent provisioning, use the generated server probe.

Check client access

If the app shows Update failed, check the connection settings before changing your deployment:

  1. Use the deployed base URL. Copy the full URL from provider setup, including any path prefix. The app must be able to reach it over HTTPS.
  2. Pass the client key if your server requires one. Set the registered key in HotUpdater.init's requestHeaders. Managed providers use x-api-key; a custom server may configure a different header or public access. Provider and admin credentials are not client keys.
  3. Check the build environment. The value must be available when building both the native Release app and OTA bundles. .env.hotupdater configures the CLI; it is not automatically loaded into the mobile app. See app configuration.
  4. If using a key, check that it is still active. Inspect its metadata in the Console's API keys view. For a custom server, follow API key management. Listing a key confirms registration, not that the installed app sends it.

After correcting the initial app configuration, rebuild the Release app and repeat Test an OTA update. Deploy a compatible test update before checking: a new channel may not have a catalog yet.

For error details, inspect the checkForUpdate callback's onError value and the request status in your server logs. A 401 points to the client key or header. A 404 can mean an empty channel or an incorrect URL; it does not prove authentication succeeded. Keep keys and request headers out of logs.

A successful download followed by the changed screen after restart verifies the app's update path. A healthy /version, a key listing or a null check result alone does not.

Check Release Catalogs

When hot-updater.config.ts sets database, doctor opens it once for the whole run and reads its release catalogs. It checks every scope that has a catalog row or a Release, comparing each catalog with a rebuild from its scope's Releases without writing, and reports each scope in details.releaseCatalogs.scopes:

  • state: verified, stale, missing (Releases but no catalog row), or unchecked (core could not compile it);
  • generation: the stored catalog's, or null without a row;
  • byteSize and descriptorCount: what its Releases compile to.

Devices read the catalog, not the Releases, so a catalog that differs from its Releases is the error RELEASE_CATALOG_STALE. Rebuild the stale catalogs from their Releases with:

npx hot-updater doctor --fix

Then retry Check and apply update in the app and verify that it applies.

When doctor cannot reach the database, for example because the server's credentials are not available where it runs, it warns RELEASE_CATALOGS_UNCHECKED with the reason and does not fail: success stays true for agents that gate on it. Without database in the config, doctor skips the check.

A scope with Releases but no catalog row answers devices with 404. Doctor reports it as the error RELEASE_CATALOG_IDENTITY_MISSING, and --fix does not create the row: restore it from a backup before rebuilding or deploying, since a new identity would not match the history devices already hold. A scope core cannot compile is the error RELEASE_CATALOG_CHECK_FAILED with the reason, beside what the other scopes report. These checks rebuild projections; they do not recover missing source policy or replace a native OTA delivery test.

The same run lists the artifact records no Release uses. Doctor warns UNREFERENCED_ARTIFACTS with their IDs, since storage prune keeps the files of every artifact record. --fix deletes the records, and a later storage prune reclaims the files. When it cannot read the artifact records, it warns ARTIFACTS_UNCHECKED; like the catalog warning, it never fails the run.

To preview a proposed change to one Bundle's policy, use bundle update --dry-run.

Update Package Versions

If doctor reports version mismatches, update the CLI, SDK and required plugins to compatible stable versions using Install packages. Keep the lockfile and rerun doctor after installation.

Update Outdated Infrastructure

If your server is outdated, doctor prints:

Infrastructure update required. Deploy server infrastructure version x.x.x or newer.

For managed providers (aws, cloudflare, supabase, firebase, vercel, azure), choose your upgrade workflow. If the app/server is still on v0, follow Move from v0 to v1 before applying templates.

With the Hot Updater skill attached, ask your agent:

$hot-updater Upgrade the server infrastructure at https://updates.example.com and verify it with doctor.

The agent identifies the provider and existing resources, generates templates with hot-updater agent infra upgrade, and reads the relevant versioned upgrade files before applying changes. If it needs access, authenticate through the provider or a local credential file; keep secret values out of the conversation. See Agent Infrastructure.

For a self-hosted server using createHotUpdater, update all Hot Updater packages together, including @hot-updater/server, apply the relevant migrations, then redeploy your server and verify it with doctor.

Options

OptionDescription
--server-base-url <url>Server base URL for version checks and scoped live catalog checks.
--jsonPrint a machine-readable result for agents and scripts.
--fixRun the repairs doctor can make itself, then check again. Cannot be combined with scoped verification options.
--scope <scope>Require scaffold or infrastructure verification.
--infra-dir <directory>Exact directory returned by infrastructure scaffolding.
--platform <platform>ios or android for a live catalog check.
--channel <channel>Actual app channel for a live catalog check.
--app-version <version>App-version catalog target; exclusive with fingerprint.
--fingerprint <hash>Fingerprint catalog target; exclusive with app version.

On this page