Repository navigation
fix(exchange): send the RI exchange accept once and halt on an unknown outcome - #442
Conversation
…n outcome AcceptReservedInstancesExchangeQuote used the default SDK retryer and carries no client token, so a committed exchange whose response was lost came back as a plain error and RunAutoExchange marked its record failed. The accept now makes one attempt, and its error is classified like purchasecfg.ClassifyPurchaseError: 4xx and throttling stay definite, anything else, and a 200 without an ExchangeId, wrap ErrOutcomeUnknown. On an unknown outcome RunAutoExchange leaves the record processing and stops the run instead of failing it. Closes #299 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 6 billable files and costs up to $1.50.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 51 minutes for your next included review. View limit details
Comment |
|
@coderabbitai review |
|
|
CI is green on head |
Independent review verdict: CLEAN at 91adf00Reviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. The PR body was treated as a claim. Coordinator: claude-cc-reviewer. Blocking findings: none. No double-send path was found. Confirmed:
Non-blocking (follow-ups):
What was attacked and how (
Gate:
|
Summary
Part 2 of #299: the RI exchange accept in
pkg/exchange.AcceptReservedInstancesExchangeQuote(exchange.go) used the default SDK retryer and carries no client token. A retry after a lost response does not double-exchange, because attempt 1 consumed the source RIs. But the committed exchange came back as a plain error, andRunAutoExchangethen calledFailRIExchange, recording a real exchange as failed.RetryMaxAttempts = 1;EC2ExchangeAPIalready takesoptFns).classifyAcceptError(pkg/exchange/outcome.go). 4xx and throttle codes stay definite. Transport errors, timeouts, 5xx and undeserializable responses wrapcommon.ErrOutcomeUnknown. A 200 without anExchangeIdis also unknown.classifyAcceptErrormirrorspurchasecfg.ClassifyPurchaseError.pkgcannot importproviders/aws/internal, so each file has a comment pointing at the other. Extracting one shared helper needs a pin bump and is left for later.RunAutoExchangeleaves the recordprocessingand stops the run, for both standalone and ladder runs. It no longer callsFailRIExchange. Plain errors still fail the record, as before.pkg/go.mod:smithy-gobecomes a direct dependency (go mod tidy; same version providers use, nogo.sumchange).Consumer notes:
processingrecord keeps its reserved daily-spend slot until reconciled.GetStaleProcessingExchangesalready surfaces such records. Picking up this change needs apkgpin bump.Closes #299. Merge after #429 (part 1, independent code) so the issue doesn't close before both parts land.
Verification (macOS, Go 1.26.9)
(cd pkg && GOWORK=off go test -race -short ./...): pass.make build,make vet: pass.GOWORK=off go build ./...in all five modules: pass.gocyclo -over 10on the touched files: clean.TestExecute_AcceptSendsOnceAndClassifiescovers success, transport error (unknown), 500 (unknown), 400 (definite), 503Throttling(definite) and emptyExchangeId(unknown). Each case recordsRetryMaxAttempts == 1through a fake that appliesoptFnsto a realec2.Options. API errors carry a real HTTP status.TestClassifyAcceptErrorchecks nil input and that it never double-wraps.TestRunAutoExchange_UnknownAcceptLeavesRecordProcessingAndHaltsuses two RIs: one execute, zeroFailRIExchangecalls, and the record is not failed.exchange.goandauto.go, all six accept subtests and the auto-exchange test fail.ExchangeIdcheck;FailRIExchangeon an unknown outcome;7d37dacc.All evidence uses SDK-shaped fakes. No AWS call or exchange was made.
🤖 Generated with Claude Code