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.
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.
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)
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>
- 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>
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.
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:
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.
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.
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.02026-10-13 02:48:32 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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. BothAPIProxy.api()(REST reads andnumber.set_value) andAPIProxy.websocket()begin withif not await ...check_api_state(): raise HTTPBadGateway, andcheck_api_state()iscore_state == "RUNNING" or offline_db_migration(Supervisor 2026.08.0 image; upstreammainhas 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 onhomewizard_localas live.3050fa5N=23050fa5N=1The 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)
cycle(), writes, heartbeat, the stand-down at HA shutdown, and every watchdog.DECISIONS/SAFETY-112-waiting-for-home-assistant.mdhas the evidence and the options for the owner. One option: Core accepts a directnumber.set_valuewhile NOT_RUNNING (measured 200), but that needs a stored long-lived token.Tests
test_safety112.pycovers 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 ingoodwe-addon-checks:1: 73/73 files pass.🤖 Generated with Claude Code
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>Review of
d94cb0c(SAFETY-112)Cause: confirmed. In
supsrc/proxy.py,api(),websocket()andstream()all raiseHTTPBadGatewayunlesscheck_api_state()is true. That iscore_state == "RUNNING"or an offline DB migration (ha_api.py:452). The repro logs agree, from my own pass overprobe.jsonlandstandin.jsonl. Every non-200 was a 502, from 0.9-1.4 s after the restart until just before RUNNING. The firstset_valuereturned 200 1.0-4.4 s after RUNNING. Inapp/, nothing reads Core's state, and nothing adds a wait or backoff after RUNNING.Control is unchanged: confirmed.
OUTAGE.waiting()is read only inController.checks().checks()is called only bystatus_payload(), which has no side effects and is called only fromweb.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.pynow grades the flag wherehass.pysets 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.app.jsfor the amber dot and the batteries-tab banner. Mutant R10 survived before this.Findings, none blocking:
checks / unit, success ond94cb0c), and task 1917 is run 1254 (infra-pin).Open points:
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.