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

  1. Read the text after 'last error:' — that is the actual failure to fix
  2. Increase attempts / initial sleep for flaky dependencies (note: no context cancellation support)
  3. Fix the underlying error (network, credentials, etc.) rather than tuning retries
  4. 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

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


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/b87c686d0fa23964. Report an issue: GitHub.