AdguardTeam/AdGuardHome · error
starting transaction: %w
Error message
starting transaction: %w
What it means
bbolt could not begin a write transaction while loading sessions at startup. The wrapped error typically indicates the database is locked by another holder, was opened read-only, or is in an unusable state.
Source
Thrown at internal/aghuser/sessionstorage.go:152
// newBBoltLogger returns a new [*bbolt.DefaultLogger] that logs messages using
// the given [slog.Logger]. l must not be nil.
func newBBoltLogger(ctx context.Context, l *slog.Logger) (bl *bbolt.DefaultLogger) {
bl = &bbolt.DefaultLogger{
Logger: slog.NewLogLogger(l.Handler(), slog.LevelDebug),
}
if l.Enabled(ctx, slog.LevelDebug) {
bl.EnableDebug()
}
return bl
}
// loadSessions loads web user sessions from the bbolt database.
func (ds *DefaultSessionStorage) loadSessions(ctx context.Context) (err error) {
tx, err := ds.db.Begin(true)
if err != nil {
return fmt.Errorf("starting transaction: %w", err)
}
needRollback := true
defer func() {
if needRollback {
err = errors.WithDeferred(err, tx.Rollback())
}
}()
bkt := tx.Bucket([]byte(bboltBucketSessions))
if bkt == nil {
return nil
}
removed, err := ds.processSessions(ctx, bkt)
if err != nil {
return fmt.Errorf("processing sessions: %w", err)
}View on GitHub (pinned to b41aefbe51)
Solutions
- Ensure a single process owns the sessions database file
- Kill any stale process holding the file lock (lsof /var/lib/app/sessions.db)
- If the db was opened read-only intentionally, use a storage implementation that does not write on load
Example fix
# before instance1 --db /shared/sessions.db & instance2 --db /shared/sessions.db & # after # run one instance, or give each its own sessions.db
Defensive patterns
Strategy: validation
Validate before calling
// ensure exclusive open before constructing storage
if _, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600); err == nil { /* first opener */ } // or use flock Try / catch
if err := ds.loadSessions(ctx); err != nil {
if errors.Is(err, bbolt.ErrDatabaseNotOpen) || isLockErr(err) { retry after resolving the lock holder }
} Prevention
- Never share sessions.db across processes or instances
- Open bbolt exactly once and reuse the handle
- Clean up stale locks by ensuring PID 1 reaps crashed processes
When it happens
Trigger: ds.db.Begin(true) inside loadSessions fails: the bbolt file is flocked by another process, the database was opened with a read-only option, or the file handle is already closed.
Common situations: Two instances of the app sharing one sessions.db; a stale lock from a crashed process that has not been released; opening the same file path twice within the process.
Related errors
- loading sessions: %w
- opening a transaction: %w
- processing sessions: %w
- committing transaction: %w
- deleting sessions: %w
AI-assisted analysis of AdguardTeam/AdGuardHome@b41aefbe51 (2026-08-27).
Data as JSON: /api/errors/eed03244d6864186.
Report an issue: GitHub.