Repository navigation
fix(homeassistant): surface question reply failures and trace the reply lifecycle - #54
Merged
Merged
Conversation
The question reply swallowed every failure with an empty catch, so a failed reply was indistinguishable from a successful one. Any error now goes to the opencode log via client.app.log, covering rejections, thrown errors, a set error field, and non-2xx status codes, as well as an option index that falls outside the offered options. The raw post method is also no longer extracted from its owner. It is called as rawClient.post(...) so the receiver is preserved. The generated client builds post as a closure rather than a this-dependent method, so the previous unbound call did work, but relying on that is fragile and a mock cannot detect the difference. Reply failures remain non-fatal: reporting is best effort and never rejects, so a logging outage cannot break the plugin.
A failed remote answer was indistinguishable from a successful one, so diagnosis relied on guessing which step broke. Every stage of the reply now reports to the opencode log: poll start, config resolution, matched or foreign responses, cancellation, timeout, index resolution and the reply request itself. These outcomes are now distinguishable in the log: poll never started, Home Assistant config unavailable, entity unreadable, response never observed, response for a different request, poll canceled, timed out, invalid index, out-of-range index, reply failed and reply succeeded. Progress goes to debug and only real faults to error, so a working setup stays quiet. Observed helper values are summarised as kind plus request ID rather than logged verbatim, keeping arbitrary entity content out of logs. pollForResponse now returns an explicit outcome instead of undefined so callers can tell these cases apart.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the silent failure path that made the 0.7.1 work-laptop bug undiagnosable, and preserves the receiver on the raw
postcall.The unbound-method hypothesis was disproven
Tested against a real opencode process. The generated client builds
postas a closure, not athis-dependent method:A probe plugin inside a live opencode server called the unbound
postand gotstatus=200, no error, and the agent resumed. So the extraction was not the root cause.The receiver-preserving call is applied anyway — depending on closure-vs-method is fragile, and a mock cannot detect the difference.
Why the root cause is still unknown, and what this PR does about it
The reported timeline shows the helper cleared 5.003s after being written, which is HA's backstop, not the plugin. That points at a failure before the HTTP reply. The empty
.catch(() => {})made every one of those causes look identical.Rather than guess again, this PR makes each cause name itself. These are now distinguishable in the log:
already polling, not starting a second pollHome Assistant config unavailable (haApiUrl, haToken or ${ENV_VAR} expansion missing), not pollingcannot read entity input_text.… from Home Assistanttimed out after 60000ms (23 polls, 0 responses for other requests, 0 failed reads)observed a response for a different request (kind=question, requestID=que_OTHER)poll canceled after 1 polls (answered elsewhere)response present but option index is not a valid indexoption index 99 is out of range (2 options offered)reply threw: boom/reply rejected: {…}/reply returned HTTP 404reply accepted (HTTP 200)Design notes:
client.app.log— noconsole, so the repo'sno-consolerule holds.debug, only real faults areerror, so a healthy setup stays quiet.pollForResponsereturns an explicit outcome (no-config/canceled/timeout/ result) instead ofundefined.Verification
Real opencode, production timing — question waits 44s, HA writes the response, an independent backstop clears the helper 5s later:
Full lifecycle from the real opencode log:
Also verified:
errorfield, out-of-range — all observable.{url, path, body}, path interpolated,application/json, colons/emoji in labels intact.level=ERROR message="question que_…: option index 99 out of range".mise run typecheck,lint,buildall pass.Note on the work laptop
I could not reproduce the failure locally, including at 44s with the backstop race. The next step is to run this build there: the log will state whether the poll started, whether the response was seen, or whether the reply failed. My earlier
class.Workdouble-notification theory was wrong, as the two webhook targets have separate consumers.