Skip to content

Honor per-operation retry policies in the generated client - #94

Merged
jeremy merged 1 commit into
basecamp:mainfrom
code-monger-givenall:agent/retry-policy-fix
Sep 10, 2026
Merged

Honor per-operation retry policies in the generated client#94
jeremy merged 1 commit into
basecamp:mainfrom
code-monger-givenall:agent/retry-policy-fix

Conversation

@code-monger-givenall

@code-monger-givenall code-monger-givenall commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

What changed

The generated Go client now reads each operation's x-hey-retry contract:

The patch is limited to the Go client template, its regenerated output, and one Go-only conformance case plus the runner support needed to discover it. The case uses DeleteBoxDesignation: one call stops immediately on undeclared 500, then a second call retries declared 503 once and stops at the operation's two-attempt limit before a 200 sentinel.

Rust retry behavior and the higher-level hey.Client default retry override are intentionally unchanged.

Validation

  • GOWORK=off mise exec -- make check
  • Go conformance: 188/188
  • Rust conformance: 187/187
  • generated output reproduces byte-for-byte

@jeremy

jeremy commented Sep 9, 2026

Copy link
Copy Markdown
Member

Checking in on this one since it has been in draft for a while and the branch is now conflicting with main.

The gap you describe still looks real on main: the generated client takes its attempt count from RetryConfig.MaxRetries alone and has no notion of a per-operation retryOn list, so the x-hey-retry metadata isn't honoured. But main has moved underneath the branch since you opened it: #137 made the generated client honour WithMaxRetries and retry once after a 401 refresh, and #139 routed generated-client retries and refresh resends through Hooks.OnRetry. Both touch the same template and http.go paths this PR rewrites.

Could you rebase onto main, keep the change scoped to the template plus one conformance case that proves a per-operation status list and attempt count are what the client actually uses, and then mark it ready for review? If the rebase shows main already covers what you needed, say so and this can close. Either way it isn't reviewable as a conflicting draft, so I'll leave it out of the review queue until it moves.

@code-monger-givenall
code-monger-givenall marked this pull request as ready for review September 10, 2026 00:46
Copilot AI balanced review requested due to automatic review settings September 10, 2026 00:46
@code-monger-givenall

Copy link
Copy Markdown
Contributor Author

Rebased onto current main around #137 and #139 and narrowed to the generated Go template/output plus one Go-only conformance case in 1fca6aa.

The generated client now uses each operation's maxAttempts and retryOn. Explicit WithRetryConfig still overrides the attempt count; the operation contract continues to define retryable statuses. The single DeleteBoxDesignation case proves both boundaries: an undeclared 500 is not retried, then declared 503 is retried once and stops at the two-attempt cap. Credential refresh, body replay, and retry hooks remain intact.

Validation: full GOWORK=off mise exec -- make check, Go conformance 188/188, Rust conformance 187/187, and byte-for-byte regeneration all pass. Rust retry behavior and the higher-level wrapper default remain unchanged, matching the requested scope.

The PR is now ready for review.

Copilot AI 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.

🟢 Approval recommended

The implementation and conformance coverage consistently enforce the declared retry contracts without disrupting refresh behavior.

Pull request overview

Updates the generated Go client to honor operation-specific retry attempt limits and HTTP statuses.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Changes:

  • Generates retry policies from x-hey-retry.
  • Preserves explicit retry-count overrides and existing refresh/hook behavior.
  • Adds Go-only conformance coverage and discovery support.
File summaries
File Description
go/templates/client.tmpl Generates and applies operation retry policies.
go/pkg/generated/client.gen.go Regenerates the Go client with those policies.
conformance/tests/go/retry-policy.json Tests retry status and attempt limits.
conformance/runner/go/main.go Discovers Go-only cases and supports repeated operations.
Review details
  • Files reviewed: 3/4 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@jeremy
jeremy merged commit 998bc0e into basecamp:main Sep 10, 2026
15 checks passed
jeremy added a commit that referenced this pull request Sep 10, 2026
…s policy

The generated client has honored each operation's retry policy since #94, but
hey.Client passed WithRetryConfig, which replaced the operation's attempt count
with MaxRetries+1: a route modelled with two sends got four, an operation the
contract gives no policy got four on transport failures, and the policy's base
delay was never read. The policy is now what an operation may spend, and the
client's settings only make it gentler: MaxRetries caps the sends, BaseDelay is
the floor under the first wait, MaxDelay the ceiling over every backoff wait.
The operation an answer came from rides on the request context, so
FollowPagination reads every later page under the same policy; a path the
caller wrote still runs on the client's own settings.

The shared retry-policy fixture gains hey-layer cases that prove the ceiling
from both sides, and both runners read the ceiling from the case's
configOverrides.
jeremy added a commit that referenced this pull request Sep 10, 2026
…s policy (#164)

* Hold the Go client's retry settings as a ceiling over each operation's policy

The generated client has honored each operation's retry policy since #94, but
hey.Client passed WithRetryConfig, which replaced the operation's attempt count
with MaxRetries+1: a route modelled with two sends got four, an operation the
contract gives no policy got four on transport failures, and the policy's base
delay was never read. The policy is now what an operation may spend, and the
client's settings only make it gentler: MaxRetries caps the sends, BaseDelay is
the floor under the first wait, MaxDelay the ceiling over every backoff wait.
The operation an answer came from rides on the request context, so
FollowPagination reads every later page under the same policy; a path the
caller wrote still runs on the client's own settings.

The shared retry-policy fixture gains hey-layer cases that prove the ceiling
from both sides, and both runners read the ceiling from the case's
configOverrides.

* Declare the workflow stage read's retry policy in the spec

The behavior model gives every @readonly operation a default policy of three
sends on 429 and 503, so the Rust route table carries one for GetWorkflowStage,
while the OpenAPI document — and with it the Go client's table — only carries
what @heyRetry declares. Declaring it puts the same policy in both tables, so
the two clients send the HTML route the same number of times.
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.

3 participants