Skip to content

Retry on HTTP 400 / "failedPrecondition"#2724

Open
ttung wants to merge 1 commit intogoogleapis:mainfrom
ttung:main
Open

Retry on HTTP 400 / "failedPrecondition"#2724
ttung wants to merge 1 commit intogoogleapis:mainfrom
ttung:main

Conversation

@ttung
Copy link

@ttung ttung commented Feb 28, 2026

This PR extracts out the function to decode the error codes. For HTTP 403, certain error codes are already retried. This adds a path for HTTP 400 where failedPrecondition is also retried.

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #2723

@ttung ttung requested a review from a team as a code owner February 28, 2026 18:56
@product-auto-label product-auto-label bot added the size: m Pull request size is medium. label Feb 28, 2026
@google-cla
Copy link

google-cla bot commented Feb 28, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the client's robustness by improving its error handling and retry mechanisms. It centralizes the logic for parsing error reasons from HTTP response bodies and extends the automatic retry functionality to include specific HTTP 400 errors, alongside existing HTTP 403 rate limit retries. This change aims to make the client more resilient to transient API issues without requiring manual intervention.

Highlights

  • Error Decoding Refactoring: Extracted the logic for decoding the error reason from the HTTP response content into a new private helper function, _decode_reason_from_content.
  • HTTP 400 Retry Logic: Implemented a new retry condition for HTTP 400 (Bad Request) responses specifically when the error reason is "failedPrecondition", which is observed in some Gmail API scenarios.
  • HTTP 403 Retry Logic Update: Refactored the existing retry logic for HTTP 403 (Forbidden) responses to utilize the newly extracted _decode_reason_from_content helper function, improving code reusability and readability.
Changelog
  • googleapiclient/http.py
    • Extracted error decoding logic into a new helper function _decode_reason_from_content.
    • Added retry mechanism for HTTP 400 responses with a 'failedPrecondition' reason.
    • Updated HTTP 403 retry logic to use the new _decode_reason_from_content function.
Activity
  • The pull request addresses and fixes issue HTTP 400 / preconditionFailed should be retried #2723.
  • The author has marked the task to open an issue as a bug/issue as complete.
  • Remaining checklist items include ensuring tests and linter pass, code coverage does not decrease, and appropriate docs are updated.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the logic for decoding error reasons into a new helper function, _decode_reason_from_content, to improve code clarity and enable a retry mechanism for HTTP 400 errors with a failedPrecondition reason. However, the implementation introduces several security and robustness issues. The response parsing logic is fragile, potentially leading to unhandled exceptions (Denial of Service) with unexpected response structures. Furthermore, logging unsanitized data from the response body risks log injection and sensitive data exposure (PII/secrets in logs). I also have a couple of suggestions for the new helper function to improve its robustness and compatibility.

reason = reason["reason"]
else:
reason = data[0]["error"]["errors"]["reason"]
except (UnicodeDecodeError, ValueError, KeyError):

Choose a reason for hiding this comment

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

high

The logic in the else block on line 110 (reason = data[0]["error"]["errors"]["reason"]) can raise a TypeError if data[0]["error"]["errors"] is a list, which is a common format for error details. This TypeError is not currently caught, which could lead to an unhandled exception. Please add TypeError to the list of caught exceptions to make this more robust.

Suggested change
except (UnicodeDecodeError, ValueError, KeyError):
except (UnicodeDecodeError, ValueError, KeyError, TypeError):

if "reason" in reason:
reason = reason["reason"]
else:
reason = data[0]["error"]["errors"]["reason"]

Choose a reason for hiding this comment

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

security-medium medium

The _decode_reason_from_content function lacks robust error handling when parsing the response content. Specifically, it only catches UnicodeDecodeError, ValueError, and KeyError. However, the logic on line 110 (reason = data[0]["error"]["errors"]["reason"]) can raise IndexError (if data is an empty list) or TypeError (if data is not a list or if the nested structure is not as expected, e.g., data[0]["error"]["errors"] is a list instead of a dict). Since these exceptions are not caught, a malformed response from the server will cause the entire request process to crash, leading to a denial of service for the client application.

return False
reason = _decode_reason_from_content(content)

LOGGER.warning('Encountered 403 Forbidden with reason "%s"', reason)

Choose a reason for hiding this comment

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

security-medium medium

The _should_retry_response function logs the reason extracted from the server response without any sanitization. Since the reason is derived from the response body, which can be influenced by an attacker (e.g., via error messages that echo back user input), an attacker could include newline characters in the reason field to forge log entries or manipulate the log output.

else:
reason = data[0]["error"]["errors"]["reason"]
except (UnicodeDecodeError, ValueError, KeyError):
LOGGER.warning("Invalid JSON content from response: %s", content)

Choose a reason for hiding this comment

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

security-medium medium

In _decode_reason_from_content, the raw response content is logged when JSON parsing fails or when expected keys are missing. This response body could potentially contain sensitive information, such as PII or API keys, if the server includes them in error messages. Logging the entire response body increases the risk of sensitive data exposure in log files.

_LEGACY_BATCH_URI = "https://www.googleapis.com/batch"


def _decode_reason_from_content(content) -> str | None:

Choose a reason for hiding this comment

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

medium

The | operator for type hints was introduced in Python 3.10. To maintain compatibility with older Python versions (e.g., 3.9 and earlier) that this library may support, it is recommended to use Optional[str] from the typing module. Using a string forward reference "Optional[str]" avoids needing the import at module load time, but you will still need to add from typing import Optional (likely under a TYPE_CHECKING guard) for type checkers to work correctly.

Suggested change
def _decode_reason_from_content(content) -> str | None:
def _decode_reason_from_content(content) -> "Optional[str]":

Extracts out a function to decode the contents.
@ttung
Copy link
Author

ttung commented Feb 28, 2026

Except for the usage of | for type hints (which I am removing now), all the other suggestions are preexisting issues. Let me know if you'd like me to change those.

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

Labels

size: m Pull request size is medium.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP 400 / preconditionFailed should be retried

2 participants