Reference

gohealthcli query

Execute a single SQL statement against the Health Archive. The binary refuses anything that would write or alter the archive — query is for inspection, not maintenance.

Flags must appear before the SQL argument because Go's flag parser stops at the first positional argument. To explore the schema, query the sqlite_master table or run gohealthcli export for the canonical normalised datasets.

Daily and Rollup Normalized Views do not synthesize rows for absent days or windows. An absent day means no qualifying record in the Health Archive, not measured zero. Non-wear, delayed device sync, or a day outside the synced range are possible causes; absence alone does not establish the cause or archive completeness. Stored zeros remain zeros. Do not average absent days as zeros. Include stored zeros and report the observed-day count and requested range. When averaging daily_steps, filter or group by source_family_filter. For total_calories_rollups, filter or group by rollup_kind. Count observed days with COUNT(DISTINCT civil_date).

The default no-flag output is the --plain key/value shape: every cell appears on its own row.<row>.<column>: <value> line, with \n / \t / \r / \\ escaped so a downstream parser can split on the first : and recover the value verbatim. The legacy Row N: column=value column=value shape — which silently broke on values containing spaces or = — was removed in PRD #144 slice 7; the default now produces byte-identical output to --plain, with no stderr warning, so scripted and LLM consumers get a parseable shape by default.

In --json mode, JSON-typed columns pass through as nested JSON objects so downstream consumers can read them with one parse instead of two. The recognised columns are raw_json, data_source_json, timezone_metadata, token_metadata_json, google_identity_json, and any column whose name ends in _json. Pass --raw-text to opt out and receive the literal stored string instead. NULL JSON-typed cells stay null; invalid JSON payloads fall back to the stored string so no row ever fails the query.

BLOB columns in --json mode are wrapped in a {"__blob_base64__": "<base64>"} marker object so raw bytes survive the JSON path without UTF-8 replacement-character corruption. Detection covers both schema-declared BLOB columns (sql.ColumnType.DatabaseTypeName() == "BLOB") and typeless expressions whose scan result is a byte slice (e.g. SELECT randomblob(8)). Decode the payload with any base64 decoder (jq -r '.rows[0][0].__blob_base64__' | base64 -d). The BLOB marker takes precedence over the JSON-typed allowlist, so a raw_json column that comes back as a BLOB is base64-encoded, never double-parsed. NULL BLOB cells stay null.

BLOB columns in --plain mode are emitted as a <blob:base64><payload> string so the row.N.M: line stays parseable; without the prefix today's path emits the raw bytes and prints \ufffd replacement characters wherever the bytes are not valid UTF-8.

Reviewed Sync and Health Archive failures may add remediation to JSON results and zero-based remediation.N fields to explicit plain results; human/default output stays unchanged. An Initial Backfill uses the fixed gohealthcli sync --from YYYY-MM-DD template without copying invocation arguments. Fan-out keeps actions only on the affected child. Canceled, corrupt, invalid-query, and unknown failures omit remediation. Building or rendering these steps performs no Provider I/O, OAuth, or archive mutation and never interpolates error text.

#Usage

gohealthcli query <sql>

#Flags

FlagTypeDefaultDescription
--configstring—config file path
--dbstring—SQLite Health Archive path
--jsonboolfalsewrite stable JSON to stdout
--plainboolfalsewrite plain key/value output to stdout
--no-inputboolfalsenever prompt, never wait for browser input
--raw-textboolfalsein JSON mode, return JSON-typed columns as strings instead of nested objects