fix: stop replaying non-idempotent requests and expose the rotation grace

A retried POST /v1/auth/rotate loses the replacement token because the
presented token is already in grace, and a retried webhook registration
creates duplicates. Only idempotent methods now retry a 429 or 5xx. Add
previous_token_grace_until to RotateApiTokenResponse.

Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
This commit is contained in:
雷电芽衣 committed 2026-09-03 20:12:49 -04:00
1 parent 89bdd6e3b8
commit a63c360539
2 files changed
+9 -1

No files matched your search

+8 -1
View File
@@ -344,7 +344,7 @@ export class ElydoraClient {
body: body !== undefined ? JSON.stringify(body) : undefined,
});
if (attempt < this.maxRetries && (res.status === 429 || res.status >= 500)) {
if (attempt < this.maxRetries && isIdempotent(method) && (res.status === 429 || res.status >= 500)) {
const retryAfter = res.headers.get('Retry-After');
const delayMs = retryAfter
? parseInt(retryAfter, 10) * 1000
@@ -372,6 +372,13 @@ export class ElydoraClient {
}
}
const IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE']);
/** A lost response to a non-idempotent request must not be replayed. */
function isIdempotent(method: string): boolean {
return IDEMPOTENT_METHODS.has(method.toUpperCase());
}
async function handleResponse<T>(res: Response): Promise<T> {
if (res.ok) {
if (res.status === 204) {
+1
View File
@@ -384,6 +384,7 @@ export interface RotateApiTokenResponse {
readonly token: string;
readonly expires_at: number | null;
readonly token_id: string;
readonly previous_token_grace_until: number;
}
export interface HealthResponse {