Skip to content

Fix AttributeError when port is already in use#8675

Open
dcabib wants to merge 5 commits into
aws:developfrom
dcabib:fix/port-already-in-use-error-7244
Open

Fix AttributeError when port is already in use#8675
dcabib wants to merge 5 commits into
aws:developfrom
dcabib:fix/port-already-in-use-error-7244

Conversation

@dcabib

@dcabib dcabib commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

Issue

Fixes #7244

Problem

When SAM CLI attempts to bind to a port that's already in use, users see a cryptic AttributeError: 'str' object has no attribute 'decode' instead of a helpful error message about which port is occupied.

This occurs because:

  1. In docker-py >= 7.0, APIError.explanation is a str (not bytes)
  2. The code calls .decode() on it, causing AttributeError
  3. Case-sensitive check ("Ports") misses lowercase variations ("ports")

Solution

  • Added safe_decode_docker_message() function that handles both bytes (docker-py < 7.0) and str (docker-py >= 7.0)
  • Updated error detection to use case-insensitive check (.lower())
  • Added 8 comprehensive unit tests covering all scenarios

Testing

  • ✅ All 6931 existing tests pass
  • ✅ Added 8 new tests (100% passing)
  • ✅ Coverage maintained at 94.04%
  • ✅ Lint and formatting checks pass
  • ✅ Tested with samdev local invoke on occupied port

Result

Users now see the actual port error message:

Error: ports are not available: exposing port TCP 127.0.0.1:5005 -> 127.0.0.1:0: listen tcp4 127.0.0.1:5005: bind: address already in use

Instead of:

AttributeError: 'str' object has no attribute 'decode'

Checklist

  • Code builds correctly
  • New/updated tests pass
  • All tests pass (make test)
  • Coverage maintained at 94%+
  • Lint checks pass (make lint)
  • Formatting is correct (make black-check)
  • Issue linked in commit and PR description
  • Added comprehensive unit tests
  • Backwards compatible (no breaking changes)

This commit fixes two related issues when Docker port binding fails:

1. AttributeError on ex.explanation.decode() - In docker-py >= 7.0,
   the explanation property is already a str, not bytes. Calling
   .decode() on a string raises AttributeError, completely masking
   the actual "port already in use" error.

2. Case sensitivity in error detection - The code checked for
   "Ports are not available" (capital P) but Docker may return
   "ports are not available" (lowercase p), causing the condition
   to fail and miss the specific error handling.

Changes:
- Added safe_decode_docker_message() function in utils.py that handles
  both bytes (docker-py < 7.0) and str (docker-py >= 7.0)
- Updated container.py to use case-insensitive check (.lower())
- Added comprehensive unit tests covering both scenarios

This ensures users see the helpful port error message instead of a
cryptic AttributeError, regardless of their docker-py version.

Fixes aws#7244

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@dcabib dcabib requested a review from a team as a code owner February 20, 2026 15:56
@github-actions github-actions Bot added area/local/start-api sam local start-api command area/local/invoke sam local invoke command area/local/start-invoke pr/external stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at. labels Feb 20, 2026
with self.assertRaises(APIError):
self.container.start()

def test_docker_api_error_port_in_use_with_str_explanation(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we shorten the tests here with parameterization?

@vicheey vicheey added need-customer-response Waiting for customer response and removed stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at. labels Mar 26, 2026

@aws-sam-tooling-bot aws-sam-tooling-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Results

Reviewed: 8f25f26..c3a280a
Files: 4
Comments: 1

Comment thread tests/unit/local/docker/test_container.py Outdated
roger-zhangg
roger-zhangg previously approved these changes Jul 2, 2026

@roger-zhangg roger-zhangg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM — the fix is correct and minimal. The safe_decode_docker_message utility properly handles both bytes (docker-py < 7.0) and str (docker-py >= 7.0) return types, and the case-insensitive check is a good defensive improvement.

Two minor observations (non-blocking):

  1. If ex.explanation is ever None (the APIError.__init__ signature allows explanation=None), the utility will return None. This is unlikely in practice since the "ports are not available" check on str(ex) implies the error is well-formed, but you could add a if message is None: return "" guard for extra safety.
  2. Agree with the existing feedback from @vicheey — the three test cases for test_container.py could be consolidated with parameterization (e.g., @parameterized.expand) to reduce repetition.

Thanks for the contribution!

Handle edge case where ex.explanation is None (returns empty string)
and ensure non-bytes/non-str types are coerced via str().

@aws-sam-tooling-bot aws-sam-tooling-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Results

Reviewed: 8f25f26..4c8af9b
Files: 4
Comments: 1

with self.assertRaises(APIError):
self.container.start()

def test_docker_api_error_port_in_use_with_str_explanation(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[GENERAL] Re-raising unresolved feedback from @vicheey: the three new tests (test_docker_api_error_port_in_use_with_str_explanation, test_docker_api_error_port_in_use_with_bytes_explanation, test_docker_api_error_port_in_use_case_insensitive) share nearly identical setup and assertions, differing only in the explanation value passed to APIError and the expected substring. The file already imports parameterized at line 11, so consolidating these into a single parameterized test would reduce duplication and make it easier to add future cases (e.g. mixed-case, None, extra whitespace) without another copy-paste block.

Example:

@parameterized.expand([
   (
       "str_explanation_docker_py_ge_7",
       "ports are not available: listen tcp 127.0.0.1:5005: bind: address already in use",
       "ports are not available",
   ),
   (
       "bytes_explanation_docker_py_lt_7",
       b"ports are not available: listen tcp 127.0.0.1:5005: bind: address already in use",
       "ports are not available",
   ),
   (
       "case_insensitive_uppercase_ports",
       "Ports are not available: listen tcp 127.0.0.1:5005: bind: address already in use",
       "Ports are not available",
   ),
])
def test_docker_api_error_port_in_use(self, name, explanation, expectedsubstring):
   self.container.is_created.return_value = True

   container_mock = Mock()
   self.mock_docker_client.containers.get.return_value = container_mock

   message = explanation.decode() if isinstance(explanation, bytes) else explanation
   container_mock.start.side_effect = APIError(message, response=None, explanation=explanation)

   with self.assertRaises(PortAlreadyInUse) as context:
       self.container.start()

   self.assertIn(expected_substring, str(context.exception))

This has not been explicitly dismissed by the author, so flagging again per review policy.

Consolidate three nearly-identical test methods into a single
@parameterized test covering str, bytes, and case-insensitive variants.

@roger-zhangg roger-zhangg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

All review feedback addressed:

  • None guard added to safe_decode_docker_message
  • Tests parameterized per @vicheey's suggestion

Ready to merge.

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

Labels

area/local/invoke sam local invoke command area/local/start-api sam local start-api command area/local/start-invoke need-customer-response Waiting for customer response pr/external

Projects

None yet

Development

Successfully merging this pull request may close these issues.

'str' object has no attribute 'decode' when PortAlreadyInUse error is raised.

3 participants