Skip to content

Fix: timed-out support-code request silently treated as successful - #41

Open
githubhjs wants to merge 4 commits into
qqaatw:masterfrom
githubhjs:fix/support-code-timeout-false-positive
Open

Fix: timed-out support-code request silently treated as successful#41
githubhjs wants to merge 4 commits into
qqaatw:masterfrom
githubhjs:fix/support-code-timeout-false-positive

Conversation

@githubhjs

Copy link
Copy Markdown

Fixes the root cause behind #40 (and the same symptom in the older closed reports #38/#31/#40 on JciHitachiHA).

Root cause

In JciHitachiAWSMqttConnection.publish(), the "support" branch's inner fn() called:

self._mqtt_events.device_support_event[thing_name].wait(timeout)

...and discarded the return value. threading.Event.wait(timeout) returns False on a genuine timeout, but since _wrap_async() always returns the thing_name identifier unconditionally (regardless of what fn() internally observed), execute()'s support_results ends up containing the thing name even when the device never responded at all.

This violates execute()'s own documented contract ("Each result is a list containing thing names if the execution was successful or BaseException(s) if an error occurred") and causes refresh_status() to take the wrong branch when gathering results — it sees thing.thing_name in support_results, assumes a response arrived, and raises:

RuntimeError: An event occurred but wasn't accompanied with data when refreshing <name> support code.

...instead of correctly falling into the else branch that already exists for this exact case, with a much more accurate and actionable message:

RuntimeError: Timed out refreshing <name> support code. Please ensure the device is online and avoid opening the official app.

So the fix is small: make the timeout actually raise inside fn(), so asyncio.gather(..., return_exceptions=True) correctly captures it as an exception rather than a false-positive success.

Fix

def fn():
    publish_future, _ = self._mqttc.publish(
        support_topic, json.dumps(default_payload), QOS
    )
    publish_future.result(timeout)
    if not self._mqtt_events.device_support_event[thing_name].wait(timeout):
        raise TimeoutError(
            f"Timed out waiting for a support-code response from {thing_name}."
        )

Testing

Verified against a real account with two devices — one that reproducibly fails this exact way (3/3 attempts, isolated via device_names=) and one that reliably succeeds (3/3 attempts). Results:

Before patch After patch
Failing device, alone RuntimeError: ...wasn't accompanied with data... RuntimeError: Timed out refreshing... Please ensure the device is online... (correct, existing message)
Working device, alone (regression check, 3x) LOGIN OK LOGIN OK (unchanged)

No behavior change for devices that respond normally — this only changes what happens when a device's support-code request never gets a response, replacing a misleading error with the message the code already intended to show in that case.

Scope note

The "status" and "control" branches of the same publish() method have the identical discard-the-wait-result pattern and likely have the same latent issue. I scoped this PR to just "support" since that's the specific reported/reproduced case — happy to open a follow-up for the other two if that's wanted, but didn't want to change more behavior than I could actually verify against a live reproduction.

githubhjs and others added 2 commits July 6, 2026 00:54
publish()'s "support" closure called Event.wait(timeout) but discarded
its return value, so a genuine timeout (no MQTT response ever arrives)
was indistinguishable from a real response once _wrap_async
unconditionally returned the thing_name identifier. This made
refresh_status() take the wrong branch in its result-gathering loop —
it saw the thing_name in support_results and assumed a response had
arrived, then hit the "event occurred but wasn't accompanied with
data" RuntimeError instead of the existing, correctly-worded "Timed
out refreshing ... support code" path that already exists for this
exact situation.

Verified against a real account with two devices, one of which
reproducibly times out waiting for its support-code response (3/3
attempts) while the other succeeds reliably (3/3 attempts). Before
this fix, the failing device raised the misleading "wasn't
accompanied with data" message; after, it correctly raises the
existing timeout message. The working device is unaffected (3/3
successful logins after the change).

See qqaatw#40 for the original report and full reproduction details.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.81%. Comparing base (2a56414) to head (da6f784).

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #41      +/-   ##
==========================================
- Coverage   92.63%   87.81%   -4.82%     
==========================================
  Files           8        8              
  Lines         787      788       +1     
==========================================
- Hits          729      692      -37     
- Misses         58       96      +38     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@qqaatw

qqaatw commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Thanks. Wonder if we should handle the status and control timeout as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants