sipeed/picoclaw · critical
enable foreign keys: %w
Error message
enable foreign keys: %w
What it means
Returned by WhatsAppNativeChannel.Start when the 'PRAGMA foreign_keys = ON' statement fails on the freshly opened sqlite session database. Because db.SetMaxOpenConns(1) forces the first real connection here, this is where file-open problems surface: corrupt database file, unreadable/unwritable path, disk I/O errors, or a locked database held by another process.
Source
Thrown at pkg/channels/whatsapp_native/whatsapp_native.go:111
c.reconnecting = false
c.reconnectMu.Unlock()
if err := os.MkdirAll(c.storePath, 0o700); err != nil {
return fmt.Errorf("create session store dir: %w", err)
}
dbPath := filepath.Join(c.storePath, whatsappDBName)
connStr := "file:" + dbPath + "?_foreign_keys=on"
db, err := sql.Open(sqliteDriver, connStr)
if err != nil {
return fmt.Errorf("open whatsapp store: %w", err)
}
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
if _, err = db.ExecContext(ctx, "PRAGMA foreign_keys = ON"); err != nil {
_ = db.Close()
return fmt.Errorf("enable foreign keys: %w", err)
}
waLogger := waLog.Stdout("WhatsApp", "WARN", true)
container := sqlstore.NewWithDB(db, sqliteDriver, waLogger)
if err = container.Upgrade(ctx); err != nil {
_ = db.Close()
return fmt.Errorf("open whatsapp store: %w", err)
}
deviceStore, err := container.GetFirstDevice(ctx)
if err != nil {
_ = container.Close()
return fmt.Errorf("get device store: %w", err)
}
client := whatsmeow.NewClient(deviceStore, waLogger)
// Create runCtx/runCancel BEFORE registering event handler and startingView on GitHub (pinned to 49183d7e8d)
Solutions
- Ensure only one process uses this storePath; stop the other instance or give each its own path.
- Check file permissions/ownership of the store .db and its directory for the service user.
- Run sqlite3 PRAGMA integrity_check on the store file if corruption is suspected; back up and recreate if corrupt.
- Free disk space / remount read-write if the volume is full or ro.
Defensive patterns
Strategy: validation
Validate before calling
if _, err := os.Stat(dbPath); err == nil {
// pre-existing store: ensure exclusive access
lock, _ := os.OpenFile(dbPath+".probe", os.O_CREATE|os.O_EXCL, 0o600)
if lock != nil { _ = lock.Close(); _ = os.Remove(dbPath + ".probe") }
}
if fi, err := os.Stat(filepath.Dir(dbPath)); err != nil || !fi.IsDir() {
return errors.New("store directory missing or invalid")
} Try / catch
if err := ch.Start(ctx); err != nil {
if strings.Contains(err.Error(), "enable foreign keys") {
// first real sqlite touch failed: check locks, permissions, corruption, disk space
}
} Prevention
- Give each channel instance a dedicated storePath.
- Run sqlite3 PRAGMA integrity_check during maintenance windows.
- Monitor disk space and keep the store volume writable.
When it happens
Trigger: Start(ctx) reaches ExecContext('PRAGMA foreign_keys = ON') and sqlite cannot open/read the store .db file - e.g. the file is corrupt, another picoclaw/whatsmeow instance holds it, the directory disappeared, or the disk is full/read-only.
Common situations: Two processes configured with the same storePath (second one fails); store corrupted after a power loss; NFS-mounted store with locking problems; disk full; store file owned by root after running once as root, then as a normal user.
Related errors
- get device store: %w
- execute %s: %w
- open whatsapp store: %w
- create seahorse engine: %w
- seahorse: create engine: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/5bf04b04f8bb79bc.
Report an issue: GitHub.