Skip to content

Return the response body for 4xx errors#77

Merged
openshift-merge-bot[bot] merged 1 commit intoopenshift-assisted:masterfrom
carbonin:4xx-error-details
Aug 28, 2025
Merged

Return the response body for 4xx errors#77
openshift-merge-bot[bot] merged 1 commit intoopenshift-assisted:masterfrom
carbonin:4xx-error-details

Conversation

@carbonin
Copy link
Copy Markdown
Collaborator

@carbonin carbonin commented Aug 27, 2025

In these cases the response should contain information that would be good for the model to provide back to the user such as permissions or validation failures.

Before the model would just say that it encountered an error without more detail. In particular this will allow errors with the cluster name to show the actual requirements.

Example log:

2025-08-27 18:43:20,506 - root - ERROR - 140044886067008:5 - API error during create_cluster: Status: 400, Reason: Bad Request, Body: {"code":"400","href":"","id":400,"kind":"Error","reason":"Cluster name format is not valid: 'test_cluster'. Name must start and end with either a letter or number and consist of lower-case letters, numbers, and hyphens. Subdomains in cluster names are only valid with none platform."}

Summary by CodeRabbit

  • Bug Fixes

    • 4xx API errors now include response body details in the error message when available, improving clarity during troubleshooting (e.g., “API error: Status 404, Details: {...}”). Behavior for non-API errors is unchanged. No breaking changes.
  • Tests

    • Expanded coverage to assert that 4xx error messages include HTTP body details, ensuring consistent user-facing diagnostics.

In these cases the response should contain information that would be
good for the model to provide back to the user such as permissions or
validation failures.

Before the model would just say that it encountered an error without
more detail. In particular this will allow errors with the cluster name
to show the actual requirements.

Example log:

`2025-08-27 18:43:20,506 - root - ERROR    - 140044886067008:5 - API error during create_cluster: Status: 400, Reason: Bad Request, Body: {"code":"400","href":"","id":400,"kind":"Error","reason":"Cluster name format is not valid: 'test_cluster'. Name must start and end with either a letter or number and consist of lower-case letters, numbers, and hyphens. Subdomains in cluster names are only valid with none platform."}`
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 27, 2025

Walkthrough

The decorator sanitize_exceptions now appends HTTP body details to 4xx ApiException messages. It logs and rethrows as AssistedServiceAPIError with "API error: Status {status}, Details: {body}" when a body exists. Tests were updated to construct ApiException with http_resp and assert the detailed message.

Changes

Cohort / File(s) Summary
Exception handling core
service_client/exceptions.py
Updated sanitize_exceptions to include response body details for 4xx ApiException errors in the raised AssistedServiceAPIError message; adjusted docstring. No public signatures changed.
Tests for API error details
tests/test_assisted_service_api.py
Modified test to create ApiException via http_resp mock (status 404, body JSON), assert message includes status and serialized body details; minor test docstring update.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant DecoratedFunc as Decorated API Call
  participant API as Assisted Service API
  participant Logger as Logger

  Caller->>DecoratedFunc: invoke()
  DecoratedFunc->>API: HTTP request
  API-->>DecoratedFunc: 4xx response (body present)
  Note over DecoratedFunc: ApiException raised
  DecoratedFunc->>Logger: log(status, reason, body)
  alt 4xx with body
    DecoratedFunc-->>Caller: raise AssistedServiceAPIError("API error: Status {status}, Details: {body}")
  else other errors
    DecoratedFunc-->>Caller: raise AssistedServiceAPIError("An internal error occurred")
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

lgtm, approved

Suggested reviewers

  • omertuc

Poem

A bunny debugs with twitching ears,
Now 4xx whispers are crystal clear.
"Status and details," the carrot decree—
Errors laid bare for all to see.
I hop through stacks with gentle cheer,
Logging the tale without a fear. 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@openshift-ci openshift-ci Bot requested review from eranco74 and jhernand August 27, 2025 18:46
@openshift-ci openshift-ci Bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Aug 27, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
service_client/exceptions.py (1)

50-53: Include reason and harden for bytes body / non-int status

  • Some SDKs surface e.body as bytes; this patch decodes with fallback to avoid raw bytes appearing in messages.
  • A defensive cast of status guards against non-integer values (e.g., unexpected strings) without altering any existing calls—our grep found only integer statuses passed internally.
  • Appending e.reason improves diagnostics and won’t break current tests, since they assert on substrings.

Apply this diff in service_client/exceptions.py (lines 50–53):

-            error_msg = f"API error: Status {e.status}"
-            if e.status and 400 <= e.status <= 499 and e.body:
-                error_msg += f", Details: {e.body}"
-            raise AssistedServiceAPIError(error_msg) from e
+            status = e.status
+            try:
+                status_int = int(status) if status is not None else None
+            except (TypeError, ValueError):
+                status_int = None
+
+            error_msg = f"API error: Status {status}" if status is not None else "API error"
+            if getattr(e, "reason", None):
+                error_msg += f" ({e.reason})"
+
+            if status_int is not None and 400 <= status_int <= 499 and e.body:
+                body_text = (
+                    e.body.decode("utf-8", "replace")
+                    if isinstance(e.body, (bytes, bytearray))
+                    else str(e.body)
+                )
+                error_msg += f", Details: {body_text}"
+            raise AssistedServiceAPIError(error_msg) from e
tests/test_assisted_service_api.py (1)

196-221: Good coverage for 4xx body passthrough — consider adding bytes/5xx cases.

  • Current test validates string body. Add one with bytes body to verify decoding.
  • Add a 5xx-with-body test to ensure details are not appended outside 4xx.

Additional tests (placed near this block):

@pytest.mark.asyncio
async def test_get_cluster_api_exception_bytes_body(client: InventoryClient) -> None:
    """4xx with bytes body should be decoded and included in the message."""
    with patch.object(client, "_installer_api") as mock_installer_api:
        mock_api = Mock()
        mock_http_resp = Mock()
        mock_http_resp.status = 400
        mock_http_resp.reason = "Bad Request"
        mock_http_resp.data = b'{"message":"bad name","code":"INVALID_NAME"}'
        mock_http_resp.getheaders.return_value = {}
        mock_api.v2_get_cluster.side_effect = ApiException(http_resp=mock_http_resp)
        mock_installer_api.return_value = mock_api

        with pytest.raises(AssistedServiceAPIError) as exc_info:
            await client.get_cluster("test-id")
        msg = str(exc_info.value)
        assert "API error: Status 400" in msg
        assert 'Details: {"message":"bad name","code":"INVALID_NAME"}' in msg

@pytest.mark.asyncio
async def test_get_cluster_api_exception_5xx_body_omitted(client: InventoryClient) -> None:
    """5xx responses should not include response body details."""
    with patch.object(client, "_installer_api") as mock_installer_api:
        mock_api = Mock()
        mock_http_resp = Mock()
        mock_http_resp.status = 500
        mock_http_resp.reason = "Internal Server Error"
        mock_http_resp.data = '{"message":"oops"}'
        mock_http_resp.getheaders.return_value = {}
        mock_api.v2_get_cluster.side_effect = ApiException(http_resp=mock_http_resp)
        mock_installer_api.return_value = mock_api

        with pytest.raises(AssistedServiceAPIError) as exc_info:
            await client.get_cluster("test-id")
        msg = str(exc_info.value)
        assert "API error: Status 500" in msg
        assert "Details:" not in msg
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 375d76a and 052adf7.

📒 Files selected for processing (2)
  • service_client/exceptions.py (2 hunks)
  • tests/test_assisted_service_api.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_assisted_service_api.py (2)
service_client/exceptions.py (1)
  • AssistedServiceAPIError (15-16)
service_client/assisted_service_api.py (1)
  • get_cluster (119-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Red Hat Konflux / assisted-service-mcp-saas-main-on-pull-request
🔇 Additional comments (1)
service_client/exceptions.py (1)

29-31: Docstring update matches the new behavior — LGTM.
Clear and accurate about including 4xx response bodies in messages.

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Aug 27, 2025

@keitwb: changing LGTM is restricted to collaborators

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Aug 27, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: carbonin, keitwb

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link
Copy Markdown
Collaborator

@eranco74 eranco74 left a comment

Choose a reason for hiding this comment

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

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Aug 28, 2025
@openshift-merge-bot openshift-merge-bot Bot merged commit 5a1256e into openshift-assisted:master Aug 28, 2025
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. size/S Denotes a PR that changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants