Repository navigation
fix(givtcp): treat a 200 REST response that is not a JSON object as a failed read (#2828) - #5619
Conversation
… failed read (#2828) GivTCP can answer readData with a 200 and an empty body while it is starting up, and a proxy in front of it can answer with an error page or a bare value. get_data() called r.json() unguarded, so the decode error (or, for a bare number, a TypeError from read_data()'s "Control" check) escaped GivTCPComponent.run() and cut that poll short, leaving later inverters unread and nothing published for the pass. get_data() now returns None for anything that is not a JSON object, the same as a non-200, so read_data() retries it and then gives up as it already does for any other failed GET. The warning includes the first 100 characters of the body so a booting GivTCP can be told apart from a misconfigured URL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
springfall2008
left a comment
There was a problem hiding this comment.
Automated comment from the triage bot.
Automated review of head 2397510: 0 findings inline, 0 in this body — the change looks sound.
Verified: get_data()'s only production caller is read_data() (givtcp_rest.py:472), which handles None through its existing retry ladder and now always receives a dict or None — so both the empty-body JSONDecodeError from #2828 and the bare-number "Control" in json TypeError are contained where they previously escaped GivTCPComponent.run(); the component's poll (givtcp.py:491) and rediscover() (givtcp.py:577) only use the read result when truthy. The old #2828 traceback's rest_getData in inverter.py is the pre-#4864 layout; the fix location on current main is correct. The new test fails on main for all three bodies (raises on the empty and HTML bodies, returns the bare 503 under return r.json()), exercises every branch of the fix including the log line, is registered in run_givtcp_rest_tests (test_givtcp_rest.py:717), and restores givtcp_rest.requests in a finally. No settings, entities or defaults change, so no docs update is needed.
Posted by Claude on behalf of @chalfontchubby.
Fixes #2828.
Summary
GivTCP can answer the
readDatastatus request with a 200 and an empty body while it is starting up, and a proxy in front of it can answer with an error page or a bare value.GivTCPRest.get_data()calledr.json()unguarded. The decode error (or, for a bare number, aTypeErrorfromread_data()'s"Control" in jsoncheck) escapedGivTCPComponent.run()and cut that poll short. Later inverters went unread and nothing was published for the pass.get_data()now returnsNonefor any 200 whose body is not a JSON object, which is what it already returns for a non-200.read_data()then retries it and gives up through its existing path. The warning includes the first 100 characters of the body, so a booting GivTCP (empty body) can be told apart from a misconfigured URL or proxy (HTML).Behaviour change
A non-JSON 200 now goes through
read_data()'s retry ladder (20s, then 40s sleeps) instead of failing the component pass at once. That is the same treatment a 503 already gets, and the ladder exists for exactly this case: a GivTCP that is still booting.Tests
test_get_data_rejects_a_200_that_is_not_jsonintest_givtcp_rest.pycovers an empty body, an HTML error page and a bare503. It fails on main (raises, or returns503) and passes with the fix../run_all --quick: all tests pass.run_pre_commit: all hooks pass.Review
/code-review highraised 8 findings. Three are fixed here: the code comment and commit message gave the wrong rationale, valid JSON that is not an object still crashed, and the warning didn't log the body.read_data()'s retries, sincetest_read_data_retries_a_bad_response_then_gives_upalready covers it.🤖 Generated with Claude Code