Skip to content

Commit a528bbb

Browse files
committed
fix(miner): handle CHALLENGE_UNAVAILABLE without burning LLM retries
When the LLM verifier is temporarily down, skill-inscribe returns CHALLENGE_UNAVAILABLE with a new challenge. Previously this code entered the standard challenge retry loop (maxChallengeRetries=5), which immediately called answerChallenge 5 times while the verifier was still unavailable — wasting LLM calls and leaving miners stuck. Fix: remove CHALLENGE_UNAVAILABLE from IsChallenge(). Instead, handle it explicitly before the retry loop: save the new challenge to state and return an error so the outer loop applies networkBackoff. On the next cycle, the saved challenge is answered and submitted normally. This prevents the cascading failure mode where all miners enter an infinite loop of requesting new challenges without submitting answers.
1 parent 1e16d55 commit a528bbb

File tree

2 files changed

+20
-1
lines changed

2 files changed

+20
-1
lines changed

internal/api/types.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,11 @@ func (r *InscribeResponse) ErrorMessage() string {
180180
func (r *InscribeResponse) IsChallenge() bool {
181181
switch r.ErrorCode() {
182182
case "CHALLENGE_REQUIRED", "CHALLENGE_FAILED", "CHALLENGE_EXPIRED",
183-
"CHALLENGE_INVALID", "CHALLENGE_USED", "CHALLENGE_UNAVAILABLE":
183+
"CHALLENGE_INVALID", "CHALLENGE_USED":
184+
// Note: CHALLENGE_UNAVAILABLE is intentionally excluded.
185+
// It means the LLM verifier is temporarily down — handled separately
186+
// in mineOnce to save the challenge and back off, rather than burning
187+
// LLM retries while the verifier is still unavailable.
184188
return true
185189
}
186190
return false

internal/miner/loop.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,21 @@ func (m *Miner) mineOnce(ctx context.Context) (*api.InscribeResponse, error) {
377377
return nil, err
378378
}
379379

380+
// CHALLENGE_UNAVAILABLE: verifier is temporarily down.
381+
// Save the new challenge for the next cycle and back off immediately.
382+
// Entering the retry loop would burn LLM calls while the verifier is still down.
383+
if resp.ErrorCode() == "CHALLENGE_UNAVAILABLE" {
384+
if ch := resp.GetChallenge(); ch != nil {
385+
m.State.LastChallenge = ch
386+
_ = m.State.Save()
387+
slog.Info("verifier unavailable, challenge saved for next cycle", "id", shortID(ch.ID))
388+
} else {
389+
m.State.LastChallenge = nil
390+
_ = m.State.Save()
391+
}
392+
return nil, fmt.Errorf("verification service temporarily unavailable, will retry next cycle")
393+
}
394+
380395
// Challenge retry loop
381396
for i := 0; resp.IsChallenge() && i < maxChallengeRetries; i++ {
382397
challenge := resp.GetChallenge()

0 commit comments

Comments
 (0)