Repository navigation
Releases: culiplan/home-assistant-culiplan
Release list
v0.16.0: setup is now one login
Setup is now one login. Most people linking Home Assistant are new to Culiplan, so the flow is shorter and an empty account no longer looks broken.
Changed
- Setup is OAuth only. After you log in and approve, the integration is created straight away with Cloud AI. The initial-setup copies of the AI provider steps (
ai_provider,ai_byok,ai_local,local_endpoint_remote_warning) are gone; the AI mode is chosen only in Configure → Change AI mode, which reuses the existingadvanced_aioptions steps. The Mealie import offer still appears only when a Mealie config entry exists in this Home Assistant. - Configure shows the AI mode you actually use. The options form read the mode from
entry.dataonly, so after switching to BYOK or Local it still said "cloud". It now readsentry.optionsfirst, matching the runtime. - No more Premium wording. Culiplan is free. Setup, options, services and Repairs copy no longer mention Premium, tiers or upgrades, in all five languages. A 403 from the backend now raises a neutral, non-fixable Repairs issue ("A Culiplan feature isn't available") that links to Culiplan support instead of an upgrade page, and the service error says the same.
- The meal-plan calendar always exists, even for an account with nothing planned (it shows "off" instead of being missing).
Added
- "Plan your first week in Culiplan" Repairs notice. Shown once, only on installs created with 0.16.0 or later, when the account has no meals in the backend's window (last 7 days plus the next 14) and none in the past year. It waits two minutes after setup so a first week seeded on link can arrive first. It links to the web planner and disappears by itself as soon as a meal is planned; submitting it also hides it for good. Existing installs never see it.
- Redirect tip on the login step and in the README for when the browser does not return to Home Assistant after approval (set an external URL or enable My Home Assistant, then try again).
async_create_fix_flowinrepairs.py, so HA registers it as a valid repairs platform (it previously logged "Invalid repairs platform").
Fixed
- An empty Mealie token in the import wizard showed the BYOK "API key required" error; it now has its own message.
translations/en.jsonhad drifted fromstrings.json(options, services and some errors were missing, so HA showed raw keys). It is regenerated fromstrings.json.
Upgrading
Existing installs keep their AI mode, BYOK provider, Local AI endpoint and options unchanged. No entry migration runs. The new translations for de / es / fr / nl were machine-written and should get a native-speaker check.
v0.15.2 — Pantry expiry no longer lands a day early east of UTC
Fixed
- Pantry expiry no longer lands a day early for users east of UTC.
culiplan.pantry_add, theadd_to_pantryLLM tool and theCuliplanAddToPantryAssist intent now send Home Assistant's configured timezone (hass.config.time_zone) with every/api/voice/executecall. The backend counts "expires in N days" from that zone's calendar day instead of the server's UTC instant, so an 08:00 AEST "add milk to the fridge, expires in 3 days" gets the 3-days-from-today date the user meant. Previously it fell back to the timezone synced from the Culiplan mobile app, and to UTC for HA-only accounts — where anything added before the local UTC offset had elapsed that morning was stored one day short. Reported by a user in Australia; the matching backend and app fixes ship separately.
v0.15.1 — Dependency fix: Anthropic SDK 1.x broke imports on fresh installs
Dependency fix only; no functional change from 0.15.0.
Fixed
anthropicis now constrained to>=0.49.0,<1. The Anthropic Python SDK 1.x (August 2026) moved its HTTP layer tohttpx2, andimport anthropicthen fails withValueError: httpx2.Timeout must either include a default…under the pydantic versions Home Assistant pins. On a fresh install that would have broken the BYOK Anthropic dispatcher at load time. Home Assistant core pins the 0.x series too, so the cap does not conflict with the built-in Anthropic integration. Migrating the dispatcher to SDK 1.x is tracked as follow-up work.- CI lint is pinned to
ruff==0.15.16/mypy==2.1.0, matching the versions the repo is linted with. ruff 0.16 turned on 413 default rules (up from 59), which made the unpinned Validate workflow fail on every push since 2 September with findings in untouched files. Adopting the new defaults is a separate, deliberate change.
v0.15.0 — "Add milk to the fridge" now works, and so do the other voice commands
Adds the first pantry write path for Home Assistant, and fixes two bugs that meant no Culiplan Assist sentence has ever been recognised in production.
Added
culiplan.pantry_addservice —name(required),quantity,unit,location(pantry/fridge/freezer/counter/spice_rack/other, defaultpantry),expiration_days. Turns a "new item on the fridge list" automation into a one-line service call.add_to_pantryLLM tool — the seventh Culiplan tool exposed to any HA conversation agent (OpenAI / Anthropic / Google / Ollama), so "add milk to the fridge" works through whichever LLM you already run in HA.CuliplanAddToPantryAssist intent in en / nl / de / fr / es — "add milk to the fridge", "zet melk in de koelkast", "füge Milch zum Kühlschrank hinzu", "ajoute du lait au frigo", "añade leche a la nevera" — with a location-aware spoken confirmation. The location slot is optional and defaults to the pantry.
All three go through the backend's voice-tool executor (POST /api/voice/execute, tool add_to_pantry), which matches an existing catalog item before creating one and writes real stock. The raw POST /api/pantry/items endpoint was deliberately not used: it creates a catalog entry with no stock, so "add milk to the fridge" would have put nothing in the fridge.
Fixed
- Assist sentences are now actually installed. Home Assistant only reads custom sentences from
<config>/custom_sentences/<lang>/; the integration shipped itsintents/*.yamlbut never put them there, so every Culiplan sentence fell through to "Sorry, I couldn't understand that". Setup now copies each language file tocustom_sentences/<lang>/culiplan.yaml(byte-compared, never rewritten when unchanged) and callsconversation.reloadwhen something was written. The files are left in place when the integration is removed, in case you edited them. - Assist intents no longer 400. The generic intent handler posted to
/api/voice/ha-assist, an endpoint that only acceptssuggest_meal/fill_shopping_listand requires Premium, so "what's for dinner", "what's in my pantry" and "add … to the shopping list" all failed even when recognised. They now go through/api/voice/executewith the correct slot-to-parameter mapping; the non-existentget_expiring_pantrytool is remapped toget_expiring_items, and the registry's tool names are guarded by a test.
Tests
- 756 passing, 2 skipped (was 683). Coverage 95.37%. New tests cover the API method, service, LLM tool, hassil recognition of 12 real sentences across the five languages, custom-sentences install/skip/overwrite/reload, and a snapshot of the backend voice-tool names.
v0.14.3 — The panel iframe actually fills the panel now
If you installed v0.14.2 and saw no change, this is the release you want.
v0.14.2 fixed the panel's outer element but not the iframe inside it, so the panel still showed its nav bar, about one card, and then empty background. From the outside nothing looked different — because the iframe was still exactly the same height as before.
What was still wrong
The panel's content sits inside a wrapper element that had no styling rule of its own. v0.14.2 made the outer element fill the window and told the iframe to "grow to fill the space" — but that instruction only works when the element directly above it is set up to distribute space, and this wrapper wasn't. The instruction was silently ignored.
An iframe with no height ends up at the browser's built-in default, which happens to be 150px — the same number as the original bug. That's why updating appeared to change nothing.
The wrapper is now styled correctly, so the sizing runs unbroken from the window down to the iframe.
Upgrading
Update through HACS or the Culiplan update entity and restart Home Assistant. If the panel still looks short afterwards, hard-refresh the browser once (Ctrl/Cmd + Shift + R) to clear the cached panel script.
Everything fixed in v0.14.2 — duplicate Lovelace resources and their automatic cleanup, the 404'ing dashboard cards, and the startup blocking-I/O warnings — is unchanged and still applies.
On the miss
The v0.14.2 tests checked the outer element and the iframe individually, and both were correct in isolation. The bug was in the element between them, which nothing looked at. The tests now check the whole chain from window to iframe, and each new test was confirmed to fail against the v0.14.2 code before being committed.
v0.14.2 — The sidebar panel fills the screen again
If the Culiplan panel showed its nav bar, about one card, and then what looked like a black overlay for the rest of the screen — this release fixes it. Four bugs, all of which affected every installation.
The panel collapsed to a sliver
The panel's stylesheet asked for height: 100%. A percentage height only works when the parent has a definite height of its own, and Home Assistant doesn't give custom panels one — so it silently became "as tall as the content", roughly 150px. What looked like an overlay covering the app was just the page background showing through beneath a very short iframe.
The panel is now sized against the viewport instead of its parent, so it fills the window. Nothing to do on your side beyond updating; if the old layout lingers, hard-refresh once.
Dashboards were firing hundreds of failed requests
Culiplan registers three Lovelace card resources on setup, and that registration was supposed to skip cards it had already added. The check never worked — it called two Home Assistant APIs the wrong way, and the resulting errors were swallowed — so Culiplan appended three more copies on every restart. One reported instance had built up 1398 resources: 466 duplicates of each card, every one of them requested on every dashboard load.
This release registers each card once and, crucially, refuses to register anything at all if it can't first confirm what's already there. It also cleans up after itself: dead Culiplan entries and duplicate rows are removed automatically on the next start. Resources you added yourself are never touched.
The card files didn't exist
All three card URLs returned 404. They pointed into /hacsfiles/culiplan/..., which assumes HACS copies the whole repository into your www folder — it doesn't. For an integration, HACS copies the custom_components folder and nothing else, so those files never arrived.
The card bundles now ship inside the integration itself and are served directly by it, which works the same whether you installed through HACS, copied the files manually, or used the built-in updater.
Startup warnings are gone
Home Assistant logged Detected blocking call to read_text ... inside the event loop by custom integration 'culiplan' on every boot. Culiplan re-read its own manifest from the event loop in two places. It's now read once when the integration loads.
Upgrading: update through HACS or the Culiplan update entity and restart Home Assistant. The duplicate-resource cleanup runs by itself on that restart — if you had accumulated thousands of entries, expect the first start after updating to tidy them.
Reported with unusually precise diagnostics (measured DOM sizes, exact call sites, and confirmation it reproduced with themes disabled), which is why all four were fixable in one pass. Thank you.
v0.14.1 — Stuck on "Error setting up entry"? This update fixes it
If your Culiplan integration has shown "Error setting up entry" since late June: update to this version and restart Home Assistant. You will then get a Reauthenticate prompt — sign in once and everything comes back. No need to delete and re-add the integration.
Why this happened
The Culiplan backend migrated to new infrastructure on 2026-06-25 and server-side OAuth refresh tokens did not survive the move, so every linked Home Assistant lost its session. Two integration bugs then made the failure silent:
Fixed
- A dead refresh token now triggers Home Assistant's reauthentication repair instead of a generic setup error retried forever. Token refresh failures are now mapped properly: 4xx →
ConfigEntryAuthFailed(reauth prompt), 5xx / network errors →ConfigEntryNotReady(transient retry). Applies at setup and at runtime. - The reauthentication flow now actually repairs the entry. Previously a reauth dead-ended on
already_configuredand left the invalid token in place; it now updates the existing entry (with a same-account check) and reloads it.
Also included (previously unreleased 0.14.0)
- Blueprint: mirror the Culiplan shopping list into any Home Assistant to-do list (Microsoft To Do, Todoist, …).
- Sidebar panel recovers from SSO bridge failures with a diagnostic trace (
auth_rejectedvsbackend_unavailable).
672 tests passing; ruff + mypy --strict clean.
🤖 Generated with Claude Code
v0.13.2 — Updater no longer blocks the event loop
Bugfix
Clears the Detected blocking call to open / scandir warnings the self-updater raised inside the Home Assistant event loop.
Fixed
- The self-updater no longer does blocking file I/O on the event loop.
async_perform_updateopened the temp zip file and lettempfile.TemporaryDirectory()run itsscandircleanup directly in the loop. The download now streams into memory (async), and all filesystem work — writing the zip, extracting, the backup/copytreeswap, and temp-dir cleanup — runs in a singleasync_add_executor_job. No change to what gets installed: the zip-slip guard, backup/rollback, and empty/missing-component checks are unchanged.
Tests
- 18 updater tests +
mypy --strict+ruffclean.
Full changelog: https://github.com/culiplan/home-assistant-culiplan/blob/main/CHANGELOG.md
v0.13.1 — No more forced reauth
Reliability fix
Stops long-lived entries from being forced into reauthentication once the access token ages past its TTL.
Fixed
- The integration no longer drops into "reauthentication required" after running for a while.
CuliplanApiClientcaptured the access token once at setup and reused it for every REST call. The coordinator only refreshed it on a Socket.IO (re)connect, so an event-driven refresh or a user service call made hours later sent a stale token, got a401, andConfigEntryAuthFailedforced a full reauth. The client now resolves the bearer through a shared async token provider on every request —async_ensure_token_valid()refreshes it transparently when it is near expiry. A singleOAuth2Sessionis now shared between the REST client and the Socket.IO coordinator, so the two consumers cannot race the backend's single-use refresh-token rotation. Pairs with the backend rotation grace window shipped the same day.
Tests
- 667 passed, 2 skipped;
mypy --strictclean.
Full changelog: https://github.com/culiplan/home-assistant-culiplan/blob/main/CHANGELOG.md
v0.13.0 — Test infrastructure overhaul + multi-account unique IDs
v0.13.0 — Test infrastructure overhaul + multi-account unique IDs
Quality release. Fixes a real multi-account bug, drives the test suite from
52% with 98 skips to 95.11% with zero skips, and tightens the Platinum-tier
mypy / ruff / pytest gates so CI now enforces the same floor end-to-end.
Targeted at Home Assistant Core inclusion review.
Fixed
- Adding a second Culiplan account no longer breaks half of the entities.
The four sensor entities (meals_planned_this_week,shopping_items,
expiring_pantry,planned_kwh_today), both binary sensors
(pantry_has_expiring,dinner_party_active) and the update entity all
used a literalculiplan_<suffix>as theirunique_id. Home Assistant
rejects a duplicate(platform, unique_id)tuple, so the second account
silently dropped each entity that collided with the first. Switched every
entity that was not already keyed on a per-resource ID (calendar and todo
were fine) tof"{entry.entry_id}_<suffix>". The config entryVERSION
is bumped from 1 to 2 and a newasync_migrate_entryrewrites every
existing entity-registry record on first load after upgrade, so users do
not lose entity history or break automations that target the old
entity_id. The migration is idempotent and bounded to the entry being
migrated. - Cooking timer entity IDs no longer have a double underscore when the
session ID ends on one._timer_entity_id("session_id_abc", "pasta")
previously producedtimer.culiplan_session_session__pasta(note the
__). Both the truncated session ID and the label are now slugified
the same way before being joined.
Changed
- Coverage gate raised from 50% to 95% in CI.
tests/conftest.pyno
longer ships a_BROKEN_TEST_IDSskip list; the corresponding
pytest_collection_modifyitemshook is gone. The 98 tests that lived on
that list have all been either repaired (drift fixes, lazy-import patch
paths, typed-error contract migrations) or rewritten against the current
API surface. mypy --strictis now clean on every source file. The
disable_error_codelist gainedarg-type/call-overload/
attr-definedwith a comment explaining the openai / anthropic /
google-genai stub-drift trade-off.ruff formatandruff checkare both clean with a documented
per-file ignore for test-only patterns.
Tests
- 664 tests passing on HA 2026.6 / 655 on HA 2024.10 + 2025.1
(google-genai SDK deliberately skipped on those lanes due to
httpx<0.28 pin conflict). - Coverage 52.10% → 95.11% on the HA 2026.6 lane.
- New test files:
test_application_credentials.py,test_update_entity.py,
test_updater.py,test_api_client.py,test_init_migrate.py,
test_init_misc.py,test_config_flow_options.py,test_cooking_extras.py,
test_services_extras.py,test_platform_setup.py,test_misc_coverage.py,
test_integration_setup.py(skipped withouthass_frontend).
CI
- Pinned
josepy<2on HA 2024.10/2025.1 lanes (newer HA on those versions
still importsComparableX509from josepy v1). - Pinned
hassil<2on HA 2024.10 (UnmatchedRangeEntitydropped in
hassil v2). - Installed
google-genaionly on the HA 2026.6 lane (older lanes' httpx
pin makes every google-genai 1.x release unresolvable).