AlistGo/alist · error
after %d attempts, last error: %s
Error message
after %d attempts, last error: %s
What it means
pkg/utils.Retry(attempts, sleep, f) runs f up to `attempts` times with exponential backoff (sleep doubling each retry). If every attempt errors, it returns 'after %d attempts, last error: %s' containing only the final error's text — the underlying error is not wrapped, just formatted.
Source
Thrown at pkg/utils/io.go:152
return closer.Close()
}
return nil
}
func Retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; i < attempts; i++ {
//fmt.Println("This is attempt number", i)
if i > 0 {
log.Println("retrying after error:", err)
time.Sleep(sleep)
sleep *= 2
}
err = f()
if err == nil {
return nil
}
}
return fmt.Errorf("after %d attempts, last error: %s", attempts, err)
}
type ClosersIF interface {
io.Closer
Add(closer io.Closer)
AddClosers(closers Closers)
GetClosers() Closers
}
type Closers struct {
closers []io.Closer
}
func (c *Closers) GetClosers() Closers {
return *c
}
var _ ClosersIF = (*Closers)(nil)View on GitHub (pinned to 843d9dc814)
Solutions
- Read the text after 'last error:' — that is the actual failure to fix
- Increase attempts / initial sleep for flaky dependencies (note: no context cancellation support)
- Fix the underlying error (network, credentials, etc.) rather than tuning retries
- If you need errors.Is/As on the cause, wrap it yourself since Retry only formats it as a string
Example fix
// before
err := utils.Retry(3, time.Second, fetch)
if err != nil { /* err is opaque text */ }
// after (capture the cause)
var lastErr error
err := utils.Retry(3, time.Second, func() error {
lastErr = fetch()
return lastErr
})
if err != nil { /* inspect lastErr typed value */ } Defensive patterns
Strategy: retry
Try / catch
var lastErr error
err := utils.Retry(3, time.Second, func() error {
lastErr = op()
return lastErr
})
if err != nil {
// err is formatted text; use lastErr for errors.Is/As and typed handling
if errors.Is(lastErr, io.EOF) { ... }
} Prevention
- Capture the last operation error in a closure since Retry only stringifies it
- Size attempts/sleep to the dependency's recovery time
- Fix root causes instead of raising attempt counts
When it happens
Trigger: Any operation wrapped in utils.Retry that keeps failing for all attempts: unreachable HTTP endpoints, failing conversions, or persistently erroring driver calls.
Common situations: Treating this as the root cause when it is only a wrapper — the real cause is inside 'last error:'. Search/log inspection for the last error string is needed. Also: fixed attempts give up too early on slow-recovering dependencies.
Related errors
- chunk_%d: giving up after %d retries while server is overloa
- open download file failed: %w
- http request failure,status: %d
- chunk download size incorrect, expected=%d, got=%d
- missing cookie or qrcode account
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/b87c686d0fa23964.
Report an issue: GitHub.