Durable CLI jobs and automation
Health.md treats connected export and context-acquisition work as durable jobs. The job lifetime is separate from the process that started it. A terminal can close or a network connection can fail without discarding completed partitions.
This page applies to file export, strict raw export, canonical extraction, and fresh encrypted-context acquisition unless a command documents a narrower rule. Direct jobs from iPhone (protocol v1) and Android (protocol v2) sources follow the same durable lifecycle.
The central rule
Section titled “The central rule”A timeout or disconnect does not mean cancellation.
Do not start a duplicate after an unknown outcome. Save the returned job ID, inspect its state, and resume the same job.
Export, raw, and extraction jobs use the top-level lifecycle commands:
healthmd status --job JOB_UUIDhealthmd resume JOB_UUID --timeout 300Encrypted-context acquisition jobs use the local agent lifecycle:
healthmd agent job status JOB_UUIDhealthmd agent job resume JOB_UUID --timeout 300Seven-day lifetime
Section titled “Seven-day lifetime”A durable job has a fixed expires_at seven days after creation. Progress does not extend it. Both peers persist the immutable request and enough committed transfer state to resume safely.
A job can persist:
- exact dates or resolved all-history identifiers.
- metric, category, source, and detail scope.
- paired-device binding.
- settings policy.
- raw profile or extraction selection.
- file destination identity.
- request fingerprint.
- session and transfer manifests.
- partition digest chain.
- committed partition and byte frontier.
- completion or cancellation acknowledgement.
Resume cannot reinterpret any of these fields.
State is not just running or finished
Section titled “State is not just running or finished”A job response may include:
| Field | Meaning |
|---|---|
durable |
Whether the operation has recoverable job state |
state |
Current durable lifecycle state |
job_id |
Stable job identifier |
session_id |
Bound transfer session identifier |
paused |
Whether work needs the same phone to reconnect |
processed_days / total_days |
Logical owner-day progress |
committed_partitions |
Partitions durably acknowledged by the receiver |
committed_bytes |
Payload bytes safely committed |
fraction_complete |
Health-free progress fraction |
expires_at |
Fixed job expiration timestamp |
Status fields contain dates, IDs, counts, bytes, and safe errors. They should not contain health samples.
Start a job with an explicit output plan
Section titled “Start a job with an explicit output plan”Raw export:
healthmd export --iphone --last 30 --raw \ --output health-month.jsonCanonical extraction:
healthmd extract --category Sleep --last 30 \ --output sleep-month.jsonDirect generated files:
healthmd export --last 30 \ --destination "$HOME/Documents/HealthVault"Pick the final output or destination before the request starts. A raw job binds its output behavior. A direct file job binds the exact destination root into the immutable request.
Resume
Section titled “Resume”healthmd resume JOB_UUID --timeout 300healthmd resume JOB_UUID --output recovered.jsonhealthmd resume JOB_UUID --output recovered.json --allow-partialFor direct mode, select the same device, transport, port, and phone used by the original request:
healthmd --device DEVICE_UUID \ --transport manual-ip --port 17647 \ resume JOB_UUID --timeout 300 --output recovered.jsonPending bytes may be discarded after a disconnect. Committed partitions are not retransmitted or reinterpreted. The receiver accepts an already committed partition only when every immutable descriptor matches.
A file job does not accept a replacement destination during resume. If the original root changed, Health.md fails closed rather than writing into a different folder.
Cancel
Section titled “Cancel”Use the lifecycle that created the job:
# Export, raw, or extractionhealthmd cancel JOB_UUID
# Encrypted-context acquisitionhealthmd agent job cancel JOB_UUIDCancellation has two stages:
- the CLI records and sends a durable cancellation request.
- the phone acknowledges cancellation and makes it terminal.
If the phone is unavailable, the job remains cancellation_pending. Reopen the same phone and retry cancel. Do not report a job as cancelled based only on local intent.
A process receiving Ctrl-C should exit without fabricating terminal cancellation. Use the explicit cancel command when cancellation is intended.
Output channels
Section titled “Output channels”Health.md separates command results from progress:
| Channel | Content |
|---|---|
| stdout | Versioned JSON command result, error, or requested JSON/JSONL stream |
| stderr | Plain pairing instructions, health-free progress, JSONL receipt when streaming, and usage text |
--output PATH |
Atomically committed health-bearing JSON or JSONL |
OUTPUT.receipt.json |
Health-free extraction receipt for JSONL file output |
--help is plain text. Argument failures before execution use stderr and exit 2. Once a command executes, runtime failures use machine-readable JSON.
Do not merge stdout and stderr in an automation parser.
Exit status and data status
Section titled “Exit status and data status”Process exit status is only one signal. Parse the response before claiming success.
| Result | Default exit behavior |
|---|---|
| Complete success | Zero |
| Complete-empty requested scope | Zero |
| Validated partial strict raw or extraction | Nonzero |
Partial with explicit --allow-partial |
Zero, but response stays partial |
| Argument error | Exit 2, plain text on stderr |
| Validation or transport failure | Nonzero with structured runtime error |
--allow-partial is acceptance policy, not data repair. Every missing day, failed query, unsupported type, and warning remains visible.
Page traversal is separate from job completion
Section titled “Page traversal is separate from job completion”Typed query responses are paged. A fresh acquisition job can complete while the query still has another page.
Without --all-pages, inspect next_cursor. When a next page exists, the high-level CLI reports partial_success rather than claiming full traversal.
healthmd query --category Sleep --last 90 --all-pages--all-pages follows opaque cursors, checks for repeats, and enforces an aggregate page and byte ceiling. If the ceiling is reached, narrow the scope or use the low-level API to page manually. There is no hidden total-result cap, but one invocation remains bounded.
Fresh, cached, and reused coverage
Section titled “Fresh, cached, and reused coverage”High-level query commands acquire fresh iPhone data by default:
healthmd query --metric resting_heart_rate --last 30Use cached data only when stale context is acceptable:
healthmd query --metric resting_heart_rate --last 30 --cachedUse --reuse-covered to skip acquisition only after Health.md verifies complete metric-aware summary coverage for the requested days:
healthmd query --metric resting_heart_rate --last 30 --reuse-coveredThe reuse shortcut does not apply to lossless data or newly projected sleep-session operations. It never treats a different provider or an older stale blob as proof of this request’s fresh completion.
Shell example
Section titled “Shell example”This example keeps the health payload in a protected file and prints only safe status fields. It assumes GNU timeout is installed. Other automation hosts should apply their own process deadline.
#!/usr/bin/env bashset -euo pipefail
output="${HOME}/Private/healthmd/sleep-week.json"mkdir -p "$(dirname "$output")"chmod 700 "$(dirname "$output")"
set +eNO_COLOR=1 TERM=dumb timeout 300 \ healthmd extract --category Sleep --last 7 --output "$output" \ </dev/null > /tmp/healthmd-command.jsonexit_code=$?set -e
if [ -s /tmp/healthmd-command.json ]; then jq '{status, job_id, error, message}' /tmp/healthmd-command.jsonfi
if [ "$exit_code" -ne 0 ]; then echo "healthmd did not report complete success" >&2 exit "$exit_code"fiDo not enable set -x around a command that may stream health JSON or include sensitive paths.
Agent behavior after an unknown outcome
Section titled “Agent behavior after an unknown outcome”An agent or scheduler should follow this order:
- Read the structured error and job ID.
- Run
status --joblocally. - Check whether the job is paused, terminal, expired, or awaiting acknowledgement.
- Reopen the same phone when fresh work or acknowledgement is needed.
- Resume the existing job with the same device.
- Start a new job only after the prior outcome is known or expiration is explicitly accepted.
Retrying a mutation blindly can duplicate source work even when file commits themselves are idempotent.
Common machine-readable errors
Section titled “Common machine-readable errors”| Code | Meaning | Safe response |
|---|---|---|
timed_out |
The command stopped waiting before the job finished | Inspect the returned job and resume it |
job_not_found |
No local durable record exists for that ID | Confirm the state directory before starting over |
job_expired |
The fixed seven-day deadline elapsed | Record the gap and create a new request if appropriate |
direct_export_paused |
Direct work needs the paired phone again | Reopen the phone and resume |
direct_cancellation_pending |
Local cancel intent lacks phone acknowledgement | Reopen the phone and retry cancel |
invalid_direct_raw_response |
Strict raw validation failed | Do not consume the output |
invalid_direct_file_receipt |
File manifest or commit receipt failed validation | Do not repair or append files manually |
partial_canonical_extraction |
Requested extraction is incomplete | Inspect receipt; opt into partial only when accepted |
unvalidated_response_too_large |
One result cannot be exposed under current validation bounds | Narrow scope or use an appropriate output mode |
stale_cursor |
Encrypted context changed after the page cursor was issued | Restart that query against the current corpus |
Progress without payload logging
Section titled “Progress without payload logging”Use --progress-json for high-level query phases and page traversal:
healthmd query --category Sleep --last 30 \ --all-pages --progress-json --output result.json \ 2> progress.jsonlProgress JSONL can include phase, page count, item count, dates, and safe diagnostics. It must not include health values. Keep it separate from the final result and apply an appropriate retention policy anyway.