SAFETY-112: say plainly that control waits for Home Assistant to start #188

Merged
admin merged 2 commits from SAFETY-112 into release/1.0 2026-10-13 02:48:32 +02:00
Owner

SAFETY-112 (Trello 6acd509d). Do not merge from here: review first.

Cause (proven)

The Supervisor proxy refuses every add-on request with 502 until Core is RUNNING. Both APIProxy.api() (REST reads and number.set_value) and APIProxy.websocket() begin with if not await ...check_api_state(): raise HTTPBadGateway, and check_api_state() is core_state == "RUNNING" or offline_db_migration (Supervisor 2026.08.0 image; upstream main has the same). The add-on itself does not wait for anything. A Core restart does not restart the add-on.

Repro (safety112-* stack, all removed afterwards)

HA 2026.9.4, a test integration that holds bootstrap, fake meter and inverters updating from load, the 2026.08.0 image's own APIProxy + HomeAssistantAPI (only the token lookup, Core token and container check stubbed), and the add-on on homewizard_local as live.

run entities live Core RUNNING proxy 502 until first write wait after live readings
base 3050fa5 N=2 17.1 s 253.1 s 252.7 s 254.7 s 237.6 s
base 3050fa5 N=1 18.1 s 256.1 s 255.1 s 257.2 s 239.1 s
this PR N=2 17.2 s 313.2 s 312.5 s 317.6 s 300.4 s
this PR N=1 17.2 s 255.2 s 254.1 s 257.1 s 240.0 s

The first write always comes 1.0-4.4 s after RUNNING. Before this PR the page said "Not ready — Battery level: no usable reading … Check it in Developer Tools" for the whole wait.

Change (text only, no route around the Supervisor)

  • While the API does not answer, the first status row is red and is the banner: "Not ready — Waiting for Home Assistant to finish starting". Home shows the pill "Waiting for Home Assistant" / "Wacht op Home Assistant" and "Control resumes by itself when Home Assistant answers." A safety stop, a stalled loop or shadow mode keeps its own headline.
  • Log: the WARNING now says "perhaps it is still starting. Control waits and resumes by itself.", a "Still waiting … 5 minutes so far." INFO line every 5 minutes, and "answers again after N minutes." at the end.
  • Unchanged: cycle(), writes, heartbeat, the stand-down at HA shutdown, and every watchdog.
  • DECISIONS/SAFETY-112-waiting-for-home-assistant.md has the evidence and the options for the owner. One option: Core accepts a direct number.set_value while NOT_RUNNING (measured 200), but that needs a stored long-lived token.

Tests

test_safety112.py covers the log, the row, banner and pill for N=1 and N=2 in EN and NL, what outranks the wait, and that control is unchanged with the flag on or off. 14 mutations, 14 killed (list in the dev report). Full suite in goodwe-addon-checks:1: 73/73 files pass.

🤖 Generated with Claude Code

SAFETY-112 (Trello 6acd509d). **Do not merge from here: review first.** ## Cause (proven) The Supervisor proxy refuses every add-on request with **502 until Core is `RUNNING`**. Both `APIProxy.api()` (REST reads and `number.set_value`) and `APIProxy.websocket()` begin with `if not await ...check_api_state(): raise HTTPBadGateway`, and `check_api_state()` is `core_state == "RUNNING" or offline_db_migration` (Supervisor 2026.08.0 image; upstream `main` has the same). The add-on itself does not wait for anything. A Core restart does not restart the add-on. ## Repro (`safety112-*` stack, all removed afterwards) HA 2026.9.4, a test integration that holds bootstrap, fake meter and inverters updating from load, **the 2026.08.0 image's own `APIProxy` + `HomeAssistantAPI`** (only the token lookup, Core token and container check stubbed), and the add-on on `homewizard_local` as live. | run | entities live | Core RUNNING | proxy 502 until | first write | wait after live readings | |---|---|---|---|---|---| | base 3050fa5 N=2 | 17.1 s | 253.1 s | 252.7 s | 254.7 s | 237.6 s | | base 3050fa5 N=1 | 18.1 s | 256.1 s | 255.1 s | 257.2 s | 239.1 s | | this PR N=2 | 17.2 s | 313.2 s | 312.5 s | 317.6 s | 300.4 s | | this PR N=1 | 17.2 s | 255.2 s | 254.1 s | 257.1 s | 240.0 s | The first write always comes 1.0-4.4 s after RUNNING. Before this PR the page said "Not ready — Battery level: no usable reading … Check it in Developer Tools" for the whole wait. ## Change (text only, no route around the Supervisor) - While the API does not answer, the first status row is red and is the banner: **"Not ready — Waiting for Home Assistant to finish starting"**. Home shows the pill **"Waiting for Home Assistant"** / **"Wacht op Home Assistant"** and "Control resumes by itself when Home Assistant answers." A safety stop, a stalled loop or shadow mode keeps its own headline. - Log: the WARNING now says "perhaps it is still starting. Control waits and resumes by itself.", a "Still waiting … 5 minutes so far." INFO line every 5 minutes, and "answers again after N minutes." at the end. - Unchanged: `cycle()`, writes, heartbeat, the stand-down at HA shutdown, and every watchdog. - `DECISIONS/SAFETY-112-waiting-for-home-assistant.md` has the evidence and the options for the owner. One option: Core accepts a direct `number.set_value` while NOT_RUNNING (measured 200), but that needs a stored long-lived token. ## Tests `test_safety112.py` covers the log, the row, banner and pill for N=1 and N=2 in EN and NL, what outranks the wait, and that control is unchanged with the flag on or off. 14 mutations, 14 killed (list in the dev report). Full suite in `goodwe-addon-checks:1`: 73/73 files pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
admin added 1 commit 2026-10-13 01:08:15 +02:00
SAFETY-112: say plainly that control waits for Home Assistant to start
checks / unit (pull_request) Successful in 36m38s
infra-pin / fresh (pull_request) Successful in 2s
d94cb0c263
The Supervisor proxy answers 502 to every add-on read, write and websocket
until Core is RUNNING (proxy.py api()/websocket() -> check_api_state()), so
the add-on cannot control while a slow integration holds HA's start, even
with its entities live. Measured on a safety112 repro stack with the
2026.08.0 proxy code: first write 1.0-4.4 s after RUNNING, 238-300 s after
live readings. Not routed around (DECISIONS/SAFETY-112-...).

While the API does not answer, the first status row is red and is the
banner ("Waiting for Home Assistant to finish starting"), Home shows a
"waiting" state in EN/NL instead of "Needs you now", and the log adds a
"still waiting" line every 5 minutes and the wait's length at the end.
A safety stop, a stalled loop or shadow mode keeps its own headline.
Control, writes, heartbeat and the stand-down are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
admin added 1 commit 2026-10-13 02:10:33 +02:00
SAFETY-112 review: grade the waiting flag where hass.py sets it
checks / unit (pull_request) Successful in 36m27s
infra-pin / fresh (pull_request) Successful in 1s
df6bc0f76b
- a 502 or refused read turns waiting on, the next good read off, and a
  read timeout never (two mutants survived without this)
- the flag changes no write with live readings, not only with 502 ones
- the page script's amber dot and batteries-tab banner for "waiting"
- DECISIONS: about 5 minutes per start stage, not "at most"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Review of d94cb0c (SAFETY-112)

Cause: confirmed. In supsrc/proxy.py, api(), websocket() and stream() all raise HTTPBadGateway unless check_api_state() is true. That is core_state == "RUNNING" or an offline DB migration (ha_api.py:452). The repro logs agree, from my own pass over probe.jsonl and standin.jsonl. Every non-200 was a 502, from 0.9-1.4 s after the restart until just before RUNNING. The first set_value returned 200 1.0-4.4 s after RUNNING. In app/, nothing reads Core's state, and nothing adds a wait or backoff after RUNNING.

Control is unchanged: confirmed. OUTAGE.waiting() is read only in Controller.checks(). checks() is called only by status_payload(), which has no side effects and is called only from web.py. cycle(), the writes, the heartbeat, the shutdown stand-down and the watchdogs do not touch it.

Pushed (df6bc0f), tests and docs only:

  • test_safety112.py now grades the flag where hass.py sets it. A 502 or a refused connection turns it on, the next good read turns it off, and a timeout never turns it on. Before this, mutants R4 (a good read does not clear it) and R5 (a timeout counts) both survived.
  • A control check with live readings. The old part 3 used 502 readings, which give 0 W whatever the flag says, so it could not see a flag that forces 0 W.
  • Static checks on app.js for the amber dot and the batteries-tab banner. Mutant R10 survived before this.
  • DECISIONS: "about 5 minutes at most" was contradicted by the 414 s live start. It now says about 5 minutes per start stage.

Findings, none blocking:

  1. Low. When Core hangs or crashes, not just when it starts, the proxy also gives 502. The pill then stays amber, "Waiting for Home Assistant … resumes by itself", for as long as it lasts. The banner stays red, and the log has a reminder every 5 minutes. A possible follow-up: escalate to "Needs you now" after, say, 15 minutes. That is a behaviour change, so it was not made here.
  2. Low. The banner and the 5-minute log line say "to finish starting" as a fact. The row hedges with "perhaps". This is fine for the start-up case this card is about.
  3. Info. CI: "run 1916" in the dev report is the Actions task id. The real run is 1253 (#564, checks / unit, success on d94cb0c), and task 1917 is run 1254 (infra-pin).

Open points:

  • Per-battery "no usable reading" rows while waiting: keep them. They are below the headline. Hiding them would also hide a real missing entity during an outage. Rewording their "Check it in Developer Tools" hint while waiting could be a small UX card.
  • "inverter-a cannot be controlled" for about 4 s after RUNNING: this is DEV-02 liveness. A write refused with 502 sets rejected_since, so the unit stays out until its next good write. That is also why the first N=2 write came 4.4 s after RUNNING, against 1-2 s for N=1. Not counting proxy-down failures as refusals would change control, so it needs its own card.
## Review of d94cb0c (SAFETY-112) **Cause: confirmed.** In `supsrc/proxy.py`, `api()`, `websocket()` and `stream()` all raise `HTTPBadGateway` unless `check_api_state()` is true. That is `core_state == "RUNNING"` or an offline DB migration (`ha_api.py:452`). The repro logs agree, from my own pass over `probe.jsonl` and `standin.jsonl`. Every non-200 was a 502, from 0.9-1.4 s after the restart until just before RUNNING. The first `set_value` returned 200 1.0-4.4 s after RUNNING. In `app/`, nothing reads Core's state, and nothing adds a wait or backoff after RUNNING. **Control is unchanged: confirmed.** `OUTAGE.waiting()` is read only in `Controller.checks()`. `checks()` is called only by `status_payload()`, which has no side effects and is called only from `web.py`. `cycle()`, the writes, the heartbeat, the shutdown stand-down and the watchdogs do not touch it. **Pushed (df6bc0f), tests and docs only:** - `test_safety112.py` now grades the flag where `hass.py` sets it. A 502 or a refused connection turns it on, the next good read turns it off, and a timeout never turns it on. Before this, mutants R4 (a good read does not clear it) and R5 (a timeout counts) both survived. - A control check with **live** readings. The old part 3 used 502 readings, which give 0 W whatever the flag says, so it could not see a flag that forces 0 W. - Static checks on `app.js` for the amber dot and the batteries-tab banner. Mutant R10 survived before this. - DECISIONS: "about 5 minutes at most" was contradicted by the 414 s live start. It now says about 5 minutes per start stage. **Findings, none blocking:** 1. Low. When Core hangs or crashes, not just when it starts, the proxy also gives 502. The pill then stays amber, "Waiting for Home Assistant … resumes by itself", for as long as it lasts. The banner stays red, and the log has a reminder every 5 minutes. A possible follow-up: escalate to "Needs you now" after, say, 15 minutes. That is a behaviour change, so it was not made here. 2. Low. The banner and the 5-minute log line say "to finish starting" as a fact. The row hedges with "perhaps". This is fine for the start-up case this card is about. 3. Info. CI: "run 1916" in the dev report is the Actions *task* id. The real run is 1253 (#564, `checks / unit`, success on d94cb0c), and task 1917 is run 1254 (`infra-pin`). **Open points:** - Per-battery "no usable reading" rows while waiting: **keep them**. They are below the headline. Hiding them would also hide a real missing entity during an outage. Rewording their "Check it in Developer Tools" hint while waiting could be a small UX card. - "inverter-a cannot be controlled" for about 4 s after RUNNING: this is DEV-02 liveness. A write refused with 502 sets `rejected_since`, so the unit stays out until its next good write. That is also why the first N=2 write came 4.4 s after RUNNING, against 1-2 s for N=1. Not counting proxy-down failures as refusals would change control, so it needs its own card.
admin merged commit e3e4ba674d into release/1.0 2026-10-13 02:48:32 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/goodwe-addon#188