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.jsonstill 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 doctorIn 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 --jsonscaffold 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.0For 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
AppDelegateusesHotUpdater.bundleURL() - Android
MainApplicationusesHotUpdater.getJSBundleFile(...) - If
updateStrategyisfingerprint,fingerprint.jsonexists, 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 createCheck 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:
| Issues | Repair |
|---|---|
MISSING_FINGERPRINT_JSON, FINGERPRINT_JSON_STALE, FINGERPRINT_ENGINE_UNRECORDED | What fingerprint create writes, where it differs: fingerprint.json |
FINGERPRINT_JSON_UNUSED | fingerprint.json, deleted to match the configured updateStrategy |
MISSING_PUBLIC_KEY, PUBLIC_KEY_MISMATCH | What keys export-public --yes writes: the configured signing public key, in the native files |
RELEASE_CATALOG_STALE | Each stale scope's catalog, rebuilt from its Releases in the server's database |
UNREFERENCED_ARTIFACTS | The 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 asMISSING_ANDROID_BUNDLE_PROVIDERplatform:ios,android, orprojectpaths: related local files when availablefixability: whether the issue is suitable for an agent loopcommands: safe local commands when the fix is command-driven
Read fixability as the repair prerequisite, within the user's authorized work:
| Fixability | Meaning |
|---|---|
auto | The agent may inspect and edit local project files, then rerun doctor. |
command | The agent may run the listed local command, then rerun doctor. |
blocked | The 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.comdoctor appends /version and checks:
https://updates.example.com/versionA 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:
- 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.
- Pass the client key if your server requires one. Set the registered key
in
HotUpdater.init'srequestHeaders. Managed providers usex-api-key; a custom server may configure a different header or public access. Provider and admin credentials are not client keys. - Check the build environment. The value must be available when building
both the native Release app and OTA bundles.
.env.hotupdaterconfigures the CLI; it is not automatically loaded into the mobile app. See app configuration. - 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), orunchecked(core could not compile it);generation: the stored catalog's, ornullwithout a row;byteSizeanddescriptorCount: 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 --fixThen 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
| Option | Description |
|---|---|
--server-base-url <url> | Server base URL for version checks and scoped live catalog checks. |
--json | Print a machine-readable result for agents and scripts. |
--fix | Run 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. |