Repository navigation
feat(antd): send retryable as gRPC metadata (x-antd-retryable) - #373
Conversation
REST tells a caller whether a failed paid write kept its payment with the error body's structured `retryable`. gRPC carried only the error code as metadata; the retryable bit was in the message text, so every gRPC SDK end-anchors a parser on "(paid attempt retained: call finalize again with the same upload_id ...)". Rewording that message would silently drop the safe retry: the caller re-prepares and pays twice, or gives up on a payment that could still be stored. Attach `x-antd-retryable: true|false` to the gRPC status whenever `retryable` is known: a pointer-finalize CLOSE_GROUP_SHORTFALL that kept the paid state, and every PARTIAL_UPLOAD. It's left off when unknown, as REST leaves the field out. Additive: clients that don't read it keep parsing the message, which is unchanged. Daemon half of V2-1630. Landing it before antd 0.16.0 means no released pointer client ever depends on the message. The SDKs then prefer the key and keep the suffix parser as a fallback for older daemons. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
APPROVE — no blocking findings
Reviewed WithAutonomi/ant-sdk #373 at 9eb7634664647eb469ccb257f664eafd00f35187, targeting main at ea389c79c2ac40601c574e93031a66a9791db1d2. Scope: one changed file, antd/src/error.rs (+72/-4).
Contract and compatibility
retryable()exposes known true/false forPartialUpload, preserves theOption<bool>onCloseGroupShortfall, and returnsNoneelsewhere.with_retryableemits literaltrue/falseonly for known values. Unknown or inapplicable retention omits the key; omission is notfalse.- The status conversion preserves codes, messages and existing
x-antd-error-codemetadata.PARTIAL_UPLOADdeliberately gains no error-code tag. REST JSON is unchanged. - This reports whether the same finalize can reuse the retained
upload_idand payment. It does not add a general automatic-retry policy. The existing finalize paths reinsert the paid session/resume handle before returning the retryable error. Those paths are unchanged.
Executed verification
Local macOS arm64, Rust/Cargo 1.96.0, dedicated exact-head worktree and revision-keyed target directory:
git diff --check ea389c79c2ac40601c574e93031a66a9791db1d2...HEAD: passed.cargo fmt --all -- --check: passed.cargo test --lockedinantd: 149 passed, 0 failed.cargo clippy --all-targets --all-features --locked -- -D warnings: passed.- 2 additional review-only tests passed, each with 19 variant/retention cases. One compares base/head Display strings, gRPC codes/messages/old metadata and REST status/body bytes. The other confirms metadata and UTF-8 messages survive a real tonic HTTP/2 loopback, including both boolean values, unknown retention and unrelated errors. These are separate from the 149 repository tests.
CI caveat — inherited dependency issue
Exact-head Check (antd), Check (antd-rust), Check (antd-js) and Third-party notices (antd) passed. Security audit remains red on zip 2.4.2: RUSTSEC-2026-0367, -0368 and -0369. Base and head antd/Cargo.lock are the same Git object (1f1e0acb2d947802bae11ae876b550f833276381), so the vulnerable dependency is inherited, not introduced here. This code approval does not waive the audit or branch protection.
Panel and limits
6/6 reviewed; 3/3 named models, both roles complete. GLM 5.3/OpenRouter, local Qwen 3.8 and local DS4 Flash each completed independent correctness and adversarial passes. All six recommend APPROVE; no verdict dissent or missing seat.
The loopback is a HealthService fixture using the actual error conversion, not a paid finalize end-to-end test. No live network/payment test or old-SDK binary test was run. SDK preference for this key plus legacy suffix fallback is the explicitly separate V2-1630 follow-up. Supplemental probes changed no tracked source. No merge performed.
Daemon half of V2-1630. Over gRPC, whether a failed paid write kept its payment now travels as structured metadata, not only in the message text.
Why
retryable.x-antd-error-code). The retryable bit lived in the message tail: "(paid attempt retained: call finalize again with the same upload_id ...)".Change (
antd/src/error.rsonly)x-antd-retryable: true|falseon the gRPC status wheneverretryableis known:Some(true)), next to its existingx-antd-error-code;upload_id, false = re-prepare).retryableout.AntdError::retryable()exposes the value for both transports.x-antd-error-codetag on PARTIAL_UPLOAD. It would be useful, but it changes what SDKs see on ABORTED, which CONFLICT shares, so it belongs in the SDK round where each mapper is checked.Next (SDK half of V2-1630)
x-antd-retryableand keeps the suffix parser only as a fallback for daemons ≤ 0.15, with a regression test on both paths.x-antd-error-code.Verified
cargo fmt --checkclean.cargo clippy --all-targets --all-features --locked -- -D warningsclean.cargo test --locked: 149 passed. New testgrpc_status_carries_retryable_as_metadata:Some(true)and with unknown retention;Part of V2-1630.
🤖 Generated with Claude Code