Skip to content

Don't resend a POST or PATCH that may have reached the server - #100

Closed
Chen-ROE wants to merge 1 commit into
mainfrom
stop-replaying-sent-posts
Closed

Chen-ROE wants to merge 1 commit into
mainfrom
stop-replaying-sent-posts

Conversation

@Chen-ROE

Copy link
Copy Markdown
Contributor

Summary

RoeRetryTransport retried every httpx.TransportError for every HTTP method. Read timeouts, dropped connections and protocol errors can all happen after the server has received the request, so a POST could be replayed up to max_retries times.

workflows.run(wait=True), new in #99, makes this likely. The server holds the request for up to 60 seconds, and the client's default timeout is also 60 seconds. Against a local server slower than the timeout, one POST arrived 4 times. On the real API, that means one call starts four workflow runs. Greptile flagged the timeout on #99.

Change

  • POST and PATCH now retry only ConnectError, ConnectTimeout and PoolTimeout. These are raised before any bytes are sent, so resending cannot duplicate work.
  • GET, PUT and DELETE keep retrying every transport error.
  • Status-code retries (5xx, 408, 429) are unchanged.

This matches the other SDKs:

  • TypeScript: retryMiddleware only retries on response status codes.
  • Go: shouldRetry refuses to retry a request that hit its deadline.

The module docstring claimed alignment with them; I've corrected it.

Why this survives later release PRs

The roe-main SDK fan-out regenerates only src/roe/_generated, src/roe/api, openapi/, README.md and SDK_EXAMPLES.md (see roe-sdk/targets.yml). Each release PR also branches from main. A fix in src/roe/utils/transport.py is therefore kept by every future generated PR.

Merge this before #99. #99 doesn't touch this file, and merging #99 publishes 1.1.20 from main, so the release will include the fix.

Test Plan

  • New tests/unit/test_retry_transport.py covers six cases:

    • POST after a read timeout and PATCH after a protocol error: sent once.
    • POST after a connect error or connect timeout: retried.
    • GET and DELETE after a read timeout: retried.

    The two non-retry cases fail without the fix. The file is force-added like the rest of tests/, which .gitignore lists.

  • uv run pytest: 68 passed.

  • ruff check and ruff format --check are clean.

  • Against a real local server slower than the client timeout: POST reached the server once with the fix (4 times before); GET still retried 4 times.

Not changed

All three SDKs still retry a POST that gets a 502 or 504. Whether that can duplicate a run depends on the gateway in front of the API, which I didn't check.

🤖 Generated with Claude Code

RoeRetryTransport retried every httpx.TransportError for every method. A
read timeout, a dropped connection, or a protocol error can happen after the
server has received the request, so a POST was replayed up to max_retries
times. With workflows.run(wait=True), the server holds the request for 60
seconds and the client times out at 60, so a long run reached a local test
server four times: one call would start four runs.

POST and PATCH now retry only ConnectError, ConnectTimeout and PoolTimeout,
which are raised before any bytes are sent. Idempotent methods and the
status-code retries are unchanged.

This matches the other SDKs: the TypeScript retry middleware only retries on
response status codes, and Go does not retry a request that hit its
deadline. The module docstring's claim of alignment is corrected.

The fix lives in src/roe/utils, outside the paths the roe-main fan-out
regenerates, so later release PRs keep it. The test file is force-added like
the rest of tests/, which .gitignore lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

RoeRetryTransport stops replaying POST and PATCH requests after transport errors that may happen after the server receives them.

  • POST and PATCH requests only retry when a transport error happens before sending.

Chen-ROE explicitly acknowledged that POST requests still retry after 502 or 504 responses. The PR leaves this unchanged because the gateway's behavior was not checked.

No actionable issues were found. Tests were reviewed but not run.

Reviews (1) · Last reviewed commit: "Don't resend a POST or PATCH that may ha..." · Reviewed by Greptile

@Chen-ROE

Copy link
Copy Markdown
Contributor Author

Moved into #99 as 78d512b so it ships with 1.1.20. The fix is outside the fan-out's regenerated paths, so later release PRs keep it.

@Chen-ROE Chen-ROE closed this Oct 27, 2026
@Chen-ROE
Chen-ROE deleted the stop-replaying-sent-posts branch October 27, 2026 18:41
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