Skip to content

fix: raise ISYConnectionError on SSL/TLS handshake failures#507

Merged
shbatm merged 1 commit into
v3.x.xfrom
fix/surface-ssl-handshake-errors
May 8, 2026
Merged

fix: raise ISYConnectionError on SSL/TLS handshake failures#507
shbatm merged 1 commit into
v3.x.xfrom
fix/surface-ssl-handshake-errors

Conversation

@shbatm
Copy link
Copy Markdown
Collaborator

@shbatm shbatm commented May 8, 2026

Summary

aiohttp.ClientConnectorSSLError is a subclass of ClientOSError, so the existing catch-all branch silently classified TLS handshake failures as a generic DEBUG ISY not ready or closed connection. — and on the retry path returned a silent None that looks indistinguishable from a transient miss.

This PR catches aiohttp.ClientSSLError (covers both ClientConnectorSSLError and ClientConnectorCertificateError) before the generic ClientOSError branch and always raises ISYConnectionError with the SSL detail in the message. The original aiohttp error rides along in __cause__. No separate WARNING/ERROR log — the failure surfaces exactly once.

Why always raise (not just on retries=None)

An SSL handshake failure isn't a transient network blip — it's a config mismatch:

  • Controller pinned below the tls_ver='auto' floor of TLS 1.2 (e.g. ISY-994 manually downgraded to TLS 1.1, or modern OpenSSL with MinProtocol=TLSv1.2)
  • verify_ssl=True against the controller's self-signed cert (ClientConnectorCertificateError)

Neither recovers from retry. Callers (HA Core) need a definitive failure to translate into ConfigEntryNotReady, not silent retries.

Before / after (ISY-994 at TLS 1.1, modern OpenSSL host)

Before — silent debug, then a generic wrapper error from test_connection:

DEBUG ISY not ready or closed connection.
ISYConnectionError: Could not connect to the ISY with the parameters provided.

After — raises directly with SSL context, original cause preserved:

ISYConnectionError: SSL/TLS error: Cannot connect to host 192.168.30.186:443 ssl:<...> [[SSL: UNSUPPORTED_PROTOCOL] unsupported protocol]
  ↑ __cause__: aiohttp.ClientConnectorSSLError
  ↑ __cause__: ssl.SSLError: [SSL: UNSUPPORTED_PROTOCOL] unsupported protocol

Test plan

  • pytest -q (438 passed)
  • Pre-commit clean
  • Smoke-tested against an ISY-994 manually downgraded to TLS 1.1 with MinProtocol=TLSv1.2 on the host
  • New regression tests:
    • test_request_ssl_error_always_raises_connection_error — covers retry path
    • test_request_ssl_error_raises_on_test_connection_path — covers retries=None init path
    • Both assert __cause__ carries the original ClientSSLError

Compatibility note

ISYConnectionError is the same exception type that was raised previously on the retries=None (init) path — so the ISY.initialize() contract is unchanged, just with a more specific message. The behavioral shift is for non-init request() calls that previously returned None on SSL failure: those now raise. In practice that path is reached only if a long-lived session sees its TLS context become invalid mid-flight (cert rotation, etc.), which warrants a definitive failure rather than a silent retry loop.

🤖 Generated with Claude Code

aiohttp.ClientConnectorSSLError is a subclass of ClientOSError, so the
existing catch-all branch silently classified TLS handshake failures as
generic "ISY not ready or closed connection." debug — leaving callers
with no signal to look at TLS settings and the retry path returning a
silent None that looks like a transient miss.

Catch ClientSSLError (covers both ClientConnectorSSLError and
ClientConnectorCertificateError) before the generic ClientOSError
branch and always raise ISYConnectionError with the SSL detail in the
exception message. The original aiohttp error rides along in the
``__cause__`` chain. No separate WARNING/ERROR log so the failure
surfaces exactly once.

Always raise (not just on retries=None) because an SSL handshake
failure isn't a transient network issue — it's a config mismatch
(controller pinned below the ``tls_ver='auto'`` floor of TLS 1.2, or
``verify_ssl=True`` against the controller's self-signed cert) that
won't recover from retry. Callers (HA Core) need a definitive failure
to translate into ConfigEntryNotReady, not silent retries.

Smoke-tested against an ISY-994 manually downgraded to TLS 1.1 with
``MinProtocol=TLSv1.2`` on the host: previously surfaced as the opaque
debug + a generic ISYConnectionError from test_connection's "Could not
connect" wrapper; now raises ``ISYConnectionError("SSL/TLS error:
... [SSL: UNSUPPORTED_PROTOCOL] ...")`` directly with the
ClientConnectorSSLError preserved in __cause__.
@shbatm shbatm force-pushed the fix/surface-ssl-handshake-errors branch from 74c959f to ce6be08 Compare May 8, 2026 14:23
@shbatm shbatm merged commit 5896072 into v3.x.x May 8, 2026
4 checks passed
@shbatm shbatm deleted the fix/surface-ssl-handshake-errors branch May 8, 2026 14:28
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.

1 participant