Troubleshooting¶
Start with:
wml-update --config updater.json doctor --current-version 1.2.3
wml-update --config updater.json status
doctor checks config parsing, state directory writability, install parent,
target symlinks, signing key loading, pending transactions and source
connectivity.
No configuration file found¶
The updater looks for updater.json, .wml-update.json and
wml-update.json in the current directory, or WML_UPDATE_CONFIG. Relative
paths inside the config resolve against the config file, not the shell's current
directory. Prefer an absolute --config path from a desktop app.
current version is unknown¶
Pass the application's compiled version:
current_version in JSON and the recorded state are fallbacks. A stale state
file can cause repeated or missed updates; the flag is authoritative.
GitHub API rate limit¶
Set source.token_env and provide a token at runtime:
The error mentions the rate limit. If token_env is set but missing, the updater
fails rather than proceeding anonymously. Token scopes: public repositories need
public read; private repositories need release read access.
no_asset: no matching release asset¶
The error lists available asset names. Add an explicit asset_rules entry:
{"os": "linux", "arch": "amd64", "match": "myapp-{version}-linux-x86_64.tar.gz", "format": "tar.gz"}
Check:
{version}has no leadingv; use{tag}if your filename includes it.- Asset names are case-sensitive for
path.Match. - The release is not a draft.
- The release is not flagged prerelease when channel is
stable. --os/--archare not overriding the platform unexpectedly.
no_release: nothing eligible¶
- Tag is valid strict SemVer after
tag_prefixis removed. - Stable channel excludes prereleases.
- Draft releases are always skipped.
- The repository has releases, not only tags.
Use --channel beta or source.include_prerelease: true for prereleases.
Signature failures¶
- The public key must match the private key that signed
manifest.json. - The signature must cover the exact bytes served. Re-run
wml-update publish; editingmanifest.jsonafter signing invalidates it. - For GitHub, both
checksums.txtandchecksums.txt.sigmust be release assets with the configured names. - A
.sigfile may be base64 text or raw 64 bytes; both are accepted. - Verify manually:
Checksum mismatch¶
The manifest or checksum file expects different bytes than the server returned. Common causes:
- Asset overwritten after publishing
- CDN serving stale/manipulated content
- Signed manifest points at an old filename with new bytes
- Partial upload
Republish artifacts, then manifest. Clear .part files if you suspect a stale
resume:
A truncated download reports unexpected EOF as an integrity error and retries
from scratch.
HTTP refused / insecure URL¶
Plain HTTP to a non-loopback host is refused. Use HTTPS. For isolated test networks only:
Host allowlist rejected¶
Add every host used, including redirect targets:
"allowed_hosts": [
"api.github.com",
"github.com",
"objects.githubusercontent.com",
"release-assets.githubusercontent.com",
"updates.example.com"
]
For GitHub, the exact asset redirect host may differ. Check the error message, which names the rejected host.
Directory install fails: file in use / access denied (Windows)¶
Close the application and update, or use the deferred flow:
The application should exit after it receives apply_scheduled. A detached
helper performs the replacement and can relaunch via install.relaunch_path.
If it still fails, another process (antivirus, DLL host, crash reporter) holds a file. Check the target directory with Process Explorer or close the locking process.
Permission denied¶
The updater user must be able to:
- create staging and backup directories next to the install target,
- rename the target,
- write inside the target.
/Applications, /opt and C:\Program Files commonly require elevation.
Prefer per-user install locations. If elevation is unavoidable, run the updater
elevated and protect its config/public key from unprivileged modification.
another update is already running¶
A lock exists at <state_dir>/update.lock. Find the other updater process or
wait. Locks are advisory and released when the process exits; a stale file is
harmless.
If the lock holder crashed during an install, the next mutating command recovers
the journal automatically. Check wml-update status for Pending install.
Pending transaction after a crash¶
The next update or rollback runs recovery while holding the lock:
- An interrupted swap restores the previous version.
- A swap that completed before the journal update is finalized.
- A command installer journal cannot be verified and is cleared with a warning.
If recovery itself fails, inspect state.json and the sibling
.wml-update-backup-* directory. Do not delete a backup until you know which
version is live. A manual restore is:
Run wml-update status before and after.
No backup available for rollback¶
- The install was a
commandorexternalinstall (no backup is created). - The previous update already rolled back.
- The backup directory was removed manually.
mac app fails Gatekeeper after update¶
Replacing files changes a macOS bundle. The new bundle must be signed and notarized before packaging. WML Update does not re-sign or strip quarantine. Build, sign, notarize, staple, then archive. Verify with:
codesign --verify --deep --strict /Applications/MyApp.app
spctl --assess --type execute /Applications/MyApp.app
Command installer failed¶
Run with --json and inspect the log event containing captured output. The
command is not executed through a shell, so pipes/redirection do not work.
Wrap complex logic in a script and point install.command at it.
Check placeholders:
{artifact}is the verified downloaded file.{install_dir}may be empty for command installs without a configured path.- Use the platform-appropriate installer, for example
msiexec.exe /i {artifact} /qnon Windows.
Local API returns 401¶
- Use
Authorization: Bearer <token>orX-WML-Token: <token>. - A generated token is printed only in the
serve --jsonready line. - If you use
--token-env, the variable must be set in the server's environment. - Tokens under 16 characters are rejected.
Local API returns 409 job_conflict¶
Only one update, fetch or rollback job may run at a time. Poll the current
job or cancel it before starting another.
SSE stream is empty¶
The stream closes when the job has finished and all events have been delivered.
Connect while the job is running, or poll GET /v1/jobs/{id} for the complete
snapshot. Reconnect with Last-Event-ID to resume.
State file is corrupt¶
The updater fails rather than silently resetting. Inspect the file. If no
transaction is pending and the recorded version is no longer useful, stop the
app, back up the file, then remove it; the next run recreates state. Never delete
state while pending is set without first inspecting the target and backup.
Debugging with --json¶
JSON mode is the best source of truth:
Every progress/log/warning is a JSON line on stdout; stderr.log contains any
human diagnostics. The terminal error object has a stable code suitable for
support tickets.