Repository navigation
v1.46.0
Most of us carry 46 chromosomes, two copies of each. I did not plan it that way, but a second copy of the thing that matters is more or less what this release is about.
I meant 1.46 to be a quiet one. Then I asked myself what would happen if your server died tonight, and the answer was: your record is gone, unless you had made a database dump yourself. For a health record that is not good enough. So HealthLog now dumps the whole database every night and copies it to your bucket if you have one. One command puts it back onto an empty server. The app also tells you when a backup stops, and a deleted account now waits fourteen days before it is really gone.
There is a lot more below. The database moves to Postgres 18 on its own, and a good share of the fixes are for self-hosted AI models that HealthLog either gave up on too early or kept asking forever.
A backup that can rebuild your server
Every night, and before every update that changes the database, the app dumps the whole database. It keeps the last seven on the new backups volume and copies each one to your off-site bucket if you have set one up. A small manifest sits next to each dump with the versions, the fingerprints of the keys you will need, and the row count of every table.
If the worst happens, docker compose run --rm db-restore latest puts the newest dump back, and latest --from-bucket fetches it from the bucket when the host itself is gone. It only ever restores into an empty database. Before it says "Restored" it compares the keys and the checksum with the manifest and counts every table again. I throw away a test server every night and rebuild it from its bucket, so I find out first if this ever stops working.
Admins get a card that says whether the server could be recovered today, and a notification when a backup stops, once and then every day until it recovers. The recovery kit lists every key a copy still needs, retired ones included, and asks you again when that list changes.
Fourteen days to change your mind
Deleting an account or a managed profile now locks it straight away and erases it after 14 days. Until then an admin can undo it. "Delete all data" keeps a copy you can restore for the same 14 days.
Postgres 18
The compose file runs Postgres 18. If you update from 1.45 or earlier with the new compose file, your database moves across on the first start. A short-lived db-upgrade container copies it into a new volume, compares the row count of every table, and only then lets the app start. If something goes wrong the app stays off and the log tells you why. The old volume is left exactly as it was, so it stays your way back. My own instance, about 600 MB, took 29 seconds.
If you run your own Postgres 16, nothing changes for you and HealthLog keeps working on it. Thanks to @sttrebo, whose question in discussion #1282 turned into the move itself rather than a guide for doing it by hand.
Smaller new things
Deleted documents get a Recently deleted list with their type, date, where they came from and how many days they have left. You can restore them from there or delete them for good. The import picker can bring a deleted document back as well; automatic imports never do. This came out of @frubino asking how to undo a deletion in discussion #1275.
A measurement token, the narrow kind a scale or Home Assistant uses, has a new "Write readings to Apple Health" switch. With it on, the iPhone app adds that token's readings to Apple Health. They still show that they came from an external device, and the token still cannot read or change anything. Turning the switch on later also covers what the token sent before. It needs an iPhone app build that knows about the switch; until then the app keeps mirroring Withings, imported and hand-entered readings as it does today. Thanks to @cnrd for the careful proposal in #1272.
The questionnaires work in every app language. There are new validated versions of PHQ-9 in French, Dutch, Korean and Brazilian Portuguese, of GAD-7 in Dutch, Korean and Brazilian Portuguese, of WHO-5 in Dutch and Korean, and of the sleep questionnaire in Polish. Where no validated version exists, you get HealthLog's own translation with a note that the score is for orientation and a one-tap switch to the English original. The doctor report marks those totals.
The JSON import reads every field that the full backup and the JSON export write, sleep stages, ranges, ECG verdicts and mood details included. A full backup also brings your own mood tags, factors and groups along without overwriting the ones you already have.
gpt-6-sol, gpt-6-luna and gpt-6.1-sol work with reasoning control and image input, both with an OpenAI key and with the ChatGPT sign-in. A restore that is waiting in line says what it is waiting for (the running backup or another large job) and since when.
Chart axes got round values, dates on a proper calendar grid, and the average and target labels moved beside the plot instead of sitting on your readings. The app starts sooner and holds about 55 MB less memory right after a start; pages and charts are as fast as in 1.45. On an iPad the app fills the window after a rotation and under Safari's toolbars, every sidebar destination is reachable in landscape, and a vertical swipe over tabs scrolls the page. And there is a guide for running HealthLog from the TrueNAS Community train, including its backups and its Encryption Key field.
Self-hosted AI models
If you run a slow local model, the daily briefing gave up long before the response timeout you had set. @gesus14 spotted it (#1274). From this release the briefing gets your timeout for each of its calls, the Insights page waits as long as that allows, and the status cards and the other AI background jobs follow the same setting.
@Tio61 sent wire captures showing the Coach putting a model's reasoning where its answer should be (#1276). That happened with models that always reason, behind a gateway or a local server. The reasoning stays out of the reply, there is room for it in the token limit, tool calls run whenever the reply carries them, and a model that only reasons gets a message of its own.
Tio61 also counted the requests against a model that never answered (#1279), and the numbers showed HealthLog asking again and again. One call makes at most two requests now. Background work pauses a provider that keeps failing, for five minutes at first and up to a day, while the Coach and "Re-run analysis" can still reach it; saving your AI settings ends the pause. Failed status cards wait up to two hours before the next try, money spent on empty replies counts toward the daily budget, and "Index all documents" stops after three failures in a row.
@asmala came back to tell me that "Extract lab values" on a stored document still failed on long reports (#1257). It reads page by page or in parts, up to 400 readings, the same way the lab scan does. If the provider stops early, the readings found so far are kept and the review warns you that some may be missing.
Other fixes
@haritowa reported two bugs and sent the fix for both. Two copies of pdf.js in the server broke each other, so previews, vision reads and search could fail one after the other (#1277, PR #1278). The server ships one copy, and the first start repairs the previews and search entries the clash broke. "End" on a medication never worked at all (#1280, PR #1281). It does now, and it ends the course right away: a dose you took earlier today still counts, and a later one is no longer due, reminded or counted as missed. End shows up only while a course is running, for anyone who manages the record, and undoing it brings the later doses back.
On a new install or right after a large import, the analytics page and the long-range charts could take seconds or run into the database's time limit, because Postgres had not yet learned what the table held. They take a fraction of a second and give the same results.
On the night the clocks went back, the list of today's doses stopped an hour early, so a late evening dose was missing from it, and a dose logged for a weekend plan that week could end up filed as unscheduled. Both are fixed.
Also fixed:
- The app's own backup and export import again, and the portable backup keeps a reading's glucose context, sleep stage, range, rhythm verdict and device.
- Imports you deleted stay deleted after a restore onto a fresh server.
- Memory stays flat under bursts of requests. Each rendered page used to stay in memory for ten minutes after it was sent.
- Three readings in one sitting no longer make a 7-day trend of thousands of mmHg; a trend needs readings on enough days.
- The ChatGPT sign-in works without a model override, and the default model list starts at gpt-5.6-terra.
- Workout splits and GPX times are back for synced routes.
- A backup that hangs no longer blocks restores.
- API answers match the published API reference, including the medication writes, a stable order for medications and doses, and the note on readings opened by day.
- Your language is no longer saved on every page view (which counted against your write limit), a reload right after a new reading shows it, the briefing keeps its chart notes, an AI key that cannot be decrypted counts as no key, history imports from Fitbit or Google Health show up at once, averages and streaks use the same clock, a stat's value and unit stay on one line, and the neutral score label is readable in the dark theme.
Security
A crafted PDF or image can no longer crash the server. pdf.js runs in its own process with memory and time limits, and an image whose size cannot be read is refused. Rate limits count the real caller instead of an address a direct caller made up, and writes coming from other websites are refused. The MCP consent page names where the access goes. With Labs switched off, lab values stay out of every AI feature, the Coach included. Reading documents takes turns between accounts, so one account cannot hold up everyone else, and over HTTPS the session cookie can no longer be planted from a sibling subdomain.
There are smaller fixes for share links, medication tokens, the way MCP marks your own text, the deploy webhook (which can now be signed) and the GlitchTip relay. The minimum versions of body-parser and fast-uri under the MCP server went up (CVE-2026-111181, CVE-2026-110180, CVE-2026-110306), as did ip-address and a development-only undici.
If you host this yourself
- Update with
docker compose up -dand the new compose file to get the move to Postgres 18. Pulling only the new image keeps you on 16, which still works. Leave free disk space of about the size of your database. Rolling back, and the steps for a Postgres you run yourself, are indocs/ops/postgres-major-upgrade.md. - The new
backupsvolume holds the local dumps. Adddatabase/to your bucket's lifecycle rule. The newDB_BACKUP_*settings andACCOUNT_DELETION_GRACE_DAYSstart on sensible defaults, and the recovery kit asks you once after the update. - If your reverse proxy reaches HealthLog from a public address, set
TRUSTED_PROXY_CIDRSto that address, or every user ends up sharing one sign-in rate limit. A proxy that rewrites the Host header needs a correctAPP_URL. More indocs/self-hosting/reverse-proxy.md. - Migrations 0475 to 0489 (0481 and 0484 are not used) took under a second together on a copy of a real instance. 0476 links existing readings to their measurement token where an account has exactly one. 0477 builds an index while writes carry on. 0478 and 0479 make the first start redo PDF previews that failed and re-index PDFs whose search entry failed, using your AI provider where one is set up and allowed. 0485 drops an unused column, and 0486 to 0489 add the tables for the dumps, the recovery readiness and the deletion window. 0487 removes old columns, so do not run the previous image beside the new schema.
- If a concurrent index build stops half-way, mark the migration rolled back; the next start drops the broken index and builds it again.
docs/ops/deploy.mdexplains that, and how to spot a job the worker cannot load after an update. /api/settings/preview-featuresis retired and answers 410. Backups from 1.44 and 1.45 still restore.- If you set
CODEX_MODEL_FALLBACK_CHAINonly to get past the rejected ChatGPT models, you can remove it. - The memory floor stays at 1536 MB.
Thank you
A lot of this release started with one of you. @haritowa found two bugs and fixed both, and the pull requests went in as written. @Tio61 sent the kind of captures and counts that make a bug obvious instead of a guessing game. @gesus14 noticed the briefing giving up early, @asmala came back after the first fix to say it was not finished, and @cnrd wrote the proposal that became the Apple Health switch. @muhdusama sent a fix for what the MCP labs tool says it returns (PR #1284). @frubino and @sttrebo asked questions in the discussions that turned into Recently deleted and the Postgres move.
Thank you as well to everyone who wrote in the discussions, sent me an email or bought me a coffee.