diff --git a/CHANGELOG.md b/CHANGELOG.md index f21c7b4..28bc2b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## [Unreleased] +## [0.5.2] - 2026-06-30 + +### Fixed +- `_make_cognito_client()` now creates the boto3 client with EC2 instance + metadata (IMDS) credential discovery disabled and explicit connect/read + timeouts. On non-EC2 hosts (e.g., Home Assistant Yellow) the IMDS endpoint + at 169.254.169.254 may accept TCP connections but never respond, causing the + calling thread to block for the full OS-level TCP timeout — well beyond the + 20-second setup window — and triggering a spurious `ConfigEntryNotReady`. + ## [0.5.1] - 2026-06-30 ### Fixed diff --git a/pyproject.toml b/pyproject.toml index 0386948..1eade40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "quilt-hp-python" -version = "0.5.1" +version = "0.5.2" description = "Async Python client for Quilt mini-split HVAC systems" readme = "README.md" license = { file = "LICENSE" } diff --git a/src/quilt_hp/auth.py b/src/quilt_hp/auth.py index 76f6317..684f1eb 100644 --- a/src/quilt_hp/auth.py +++ b/src/quilt_hp/auth.py @@ -15,6 +15,8 @@ from typing import Protocol, cast import boto3 +import botocore.session +from botocore.config import Config from botocore.exceptions import ClientError from quilt_hp.const import COGNITO_CLIENT_ID, COGNITO_REGION @@ -66,10 +68,24 @@ def _expires_in_s(result: CognitoAuthResult) -> int: def _make_cognito_client() -> _CognitoClient: - """Create a boto3 Cognito Identity Provider client.""" + """Create a boto3 Cognito Identity Provider client. + + Creates a botocore session with IMDS credential discovery disabled. + The EC2 instance metadata service (169.254.169.254) can hang on + non-EC2 hosts that accept the TCP connection but never respond, + blocking the thread for the full OS-level TCP timeout. Cognito + user-pool auth flows (CUSTOM_AUTH, REFRESH_TOKEN_AUTH) don't + require AWS IAM credentials, so skipping IMDS is safe here. + """ + session = botocore.session.get_session() + session.set_config_variable("metadata_service_num_attempts", 0) return cast( "_CognitoClient", - boto3.client("cognito-idp", region_name=COGNITO_REGION), + boto3.Session(botocore_session=session).client( + "cognito-idp", + region_name=COGNITO_REGION, + config=Config(connect_timeout=5, read_timeout=15), + ), )