Skip to content

Fix firmware update error message missing exception type#760

Merged
TheJulianJES merged 2 commits intozigpy:devfrom
TheJulianJES:tjj/fix-empty-update-error-message
May 8, 2026
Merged

Fix firmware update error message missing exception type#760
TheJulianJES merged 2 commits intozigpy:devfrom
TheJulianJES:tjj/fix-empty-update-error-message

Conversation

@TheJulianJES
Copy link
Copy Markdown
Contributor

@TheJulianJES TheJulianJES commented May 8, 2026

Proposed change

This fixes an issue where a TimeoutError during an update showed "Update was not successful: " as an error message. With this PR, we now show the exception type itself (repr(ex)) if the exception string (str(ex)) is empty.

A test is also added, though I'm not sure how necessary it really is. It does run and test the added code, so I've kept it for now (as this isn't done otherwise).

When this happens

This only really happens with devices that have a PollControl cluster. Otherwise, we should always get a DeliveryError. Maybe something to address in a future zigpy PR(?)

AI summary

Long AI summary (click to expand)

Preserve exception type in firmware update error messages

Problem

When a firmware update fails because the underlying update_firmware() call raises a bare TimeoutError() — the canonical case being asyncio.timeout() cancelling its inner await in zigpy (zigpy/device.py:689, async with asyncio_timeout(timeout): await future) — the user-facing error message is empty after the colon:

Update was not successful:

BaseFirmwareUpdateEntity.async_install catches the exception and re-raises it as:

raise ZHAException(f"Update was not successful: {ex}") from ex

For TimeoutError() (and any other exception constructed without arguments), str(ex) == "", so the f-string interpolates nothing and the user sees no clue about what went wrong.

Fix

Fall back to repr(ex) when str(ex) is empty:

raise ZHAException(
    f"Update was not successful: {str(ex) or repr(ex)}"
) from ex

Now a bare TimeoutError() renders as Update was not successful: TimeoutError(). Exceptions with non-empty str() are unaffected — "bad version" still shows up as Update was not successful: bad version, not ValueError('bad version').

The or form is safe across exceptions reachable from except Exception:

  • KeyboardInterrupt / SystemExit / asyncio.CancelledError derive from BaseException, so they don't reach this clause.
  • The fallback only evaluates repr(ex) when str(ex) is empty, and BaseException.__repr__ is built-in and never raises.
  • Whitespace-only or "0"-style strings stay as-is (truthy in or); contrived but accurate.

The second raise ZHAException(f"Update was not successful: {result}") branch (the result != Status.SUCCESS path) is left alone — Status always renders meaningfully.

Tests

  • test_firmware_update_empty_exception_message — patches Device.update_firmware to raise a bare TimeoutError() and asserts the resulting ZHAException message is exactly Update was not successful: TimeoutError() and that __cause__ chaining is preserved. Confirmed to fail before the fix (message was Update was not successful: with a trailing space).

A bare `TimeoutError()` from zigpy's `asyncio.timeout()` has an empty
`str()`, which produced the unhelpful user-facing message
`Update was not successful: ` with no clue about the failure. Fall back
to `repr(ex)` when `str(ex)` is empty so the exception type is always
visible.
Patches `Device.update_firmware` to raise a bare `TimeoutError()` and
asserts the resulting `ZHAException` carries an identifiable message
and preserves cause chaining.
Copilot AI review requested due to automatic review settings May 8, 2026 07:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improve user-facing firmware update failure messaging by ensuring exceptions with empty str(ex) (e.g., bare TimeoutError()) still surface an identifiable error string in ZHA’s update platform.

Changes:

  • Update firmware install exception handling to fall back to repr(ex) when str(ex) is empty.
  • Add a regression test covering the empty-exception-message case and verifying exception chaining.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
zha/application/platforms/update.py Adjusts the raised ZHAException message to include exception type/details when str(ex) is empty.
tests/test_update.py Adds a regression test asserting the improved error message for bare TimeoutError() and preserved __cause__.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_update.py
Comment on lines +535 to +544
with (
patch(
"zigpy.device.Device.update_firmware",
AsyncMock(side_effect=raised),
),
pytest.raises(ZHAException) as exc_info,
):
await entity.async_install(
version=f"0x{fw_image.firmware.header.file_version:08x}"
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is copied from the existing tests. This is technically somewhat correct, but we generally don't guard for failure cases in tests like this. I'd just leave this as is.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.64%. Comparing base (f443146) to head (2a4c2c5).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev     #760   +/-   ##
=======================================
  Coverage   97.64%   97.64%           
=======================================
  Files          62       62           
  Lines       10873    10873           
=======================================
  Hits        10617    10617           
  Misses        256      256           

☔ View full report in Codecov by Sentry.
📢 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.

@TheJulianJES TheJulianJES merged commit cb4941f into zigpy:dev May 8, 2026
15 checks passed
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.

2 participants