fix(pairing): retry get_key_tokens past a transient empty result
Some checks failed
ci.yml / fix(pairing): retry get_key_tokens past a transient empty result (pull_request) Failing after 0s

pair_spire failed with "bunker returned no tokens after create_new_token" when
the nsecbunkerd was briefly slow — get_key_tokens listed nothing in the few ms
after create_new_token before the write landed. Observed live: a nip44_decrypt
timeout, then a 502 on pair, then the identical call 25s later succeeding.

Retry get_key_tokens up to 5x with a 0.4s backoff before giving up, so pairing
survives a sluggish bunker instead of flaking. Tests cover the transient-empty
recovery and the exhausted-attempts failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-07-02 00:29:08 +02:00
commit 765b07737b
2 changed files with 68 additions and 1 deletions

View file

@ -42,6 +42,7 @@ with nothing else provisioned. See bitspire packages/nostr-client/src/seed.ts.
from __future__ import annotations
import asyncio
import base64
import json
import re
@ -143,6 +144,28 @@ def _recover_token(tokens: list[dict], client_name: str) -> str:
return token
async def _get_key_tokens_with_retry(
admin_client: NsecBunkerAdminClient,
key_name: str,
*,
attempts: int = 5,
delay: float = 0.4,
) -> list[dict]:
"""`get_key_tokens` can race a just-issued token: when the bunker is briefly
slow (a `nip44_decrypt`/`create_new_token` under load), listing tokens
milliseconds later can return an empty set before the write lands, and
pairing fails with 'no tokens'. Retry a few times with a short backoff
before giving up (bitspire#70)."""
tokens: list[dict] = []
for i in range(attempts):
tokens = await admin_client.get_key_tokens(key_name)
if tokens:
return tokens
if i < attempts - 1:
await asyncio.sleep(delay)
return tokens
def build_seed_url(
*,
spire_npub: str,
@ -299,7 +322,7 @@ async def pair_spire(
await admin_client.create_new_token(
key_name, client_name, policy_id, duration_hours=duration_hours
)
tokens = await admin_client.get_key_tokens(key_name)
tokens = await _get_key_tokens_with_retry(admin_client, key_name)
except NsecBunkerNotConfiguredError as exc:
raise PairingError(f"nsecbunkerd is not configured: {exc}") from exc
except NsecBunkerError as exc: