sipeed/picoclaw · error

execute %s: %w

Error message

execute %s: %w

What it means

Thrown by MatrixChannel.initCrypto (pkg/channels/matrix/matrix.go:355) while configuring the SQLite database that stores Matrix end-to-encryption state. Four PRAGMAs are executed in order (foreign_keys=ON, journal_mode=WAL, synchronous=NORMAL, busy_timeout=5000) via modernc.org/sqlite's database/sql driver; the message names the exact PRAGMA that failed. Any failure closes the DB and aborts channel startup, so encrypted Matrix messaging cannot run.

Source

Thrown at pkg/channels/matrix/matrix.go:355

	db, err := sql.Open(sqliteDriver, connStr)
	if err != nil {
		return fmt.Errorf("open crypto database: %w", err)
	}
	db.SetMaxOpenConns(1)
	db.SetMaxIdleConns(1)

	// Execute PRAGMA statements
	// This is equivalent to the "sqlite3-fk-wal" dialect used by cryptohelper
	pragmaStmts := []string{
		"PRAGMA foreign_keys = ON",
		"PRAGMA journal_mode = WAL",
		"PRAGMA synchronous = NORMAL",
		"PRAGMA busy_timeout = 5000",
	}
	for _, pragma := range pragmaStmts {
		if _, err = db.ExecContext(ctx, pragma); err != nil {
			_ = db.Close()
			return fmt.Errorf("execute %s: %w", pragma, err)
		}
	}

	// Wrap with dbutil for dialect support
	wrappedDB, err := dbutil.NewWithDB(db, sqliteDriver)
	if err != nil {
		_ = db.Close()
		return fmt.Errorf("wrap database: %w", err)
	}

	cryptoHelper, err := cryptohelper.NewCryptoHelper(c.client, []byte(c.config.CryptoPassphrase), wrappedDB)
	if err != nil {
		return fmt.Errorf("create crypto helper: %w", err)
	}

	if c.client.DeviceID == "" {
		resp, whoamiErr := c.client.Whoami(ctx)
		if whoamiErr != nil {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Identify the failing PRAGMA from the message: 'journal_mode' points to filesystem/lock issues, 'foreign_keys' to a corrupt or non-SQLite file
  2. Verify the DB file is valid SQLite: sqlite3 <cryptodb>/crypto.db 'PRAGMA integrity_check;' — if corrupt, delete the crypto DB directory and restart (the bot re-registers device keys; users must re-verify the new device)
  3. Check the crypto DB directory is writable by the bot user and sits on a local filesystem, not NFS; move it if necessary
  4. Ensure only one bot instance uses the crypto DB path at a time
  5. Retry startup once environmental issues are fixed — the error is emitted at startup, not per-message

Example fix

# before: crypto db on a network volume
MATRIX_CRYPTO_DB=/mnt/nfs/bot/crypto

# after: crypto db on local writable storage
MATRIX_CRYPTO_DB=/var/lib/bot/crypto
mkdir -p /var/lib/bot/crypto && chown bot:bot /var/lib/bot/crypto
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight before starting the Matrix channel
func cryptoDbUsable(dir string) error {
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return fmt.Errorf("crypto db dir: %w", err)
	}
	probe := filepath.Join(dir, ".write-probe")
	if err := os.WriteFile(probe, []byte("ok"), 0o600); err != nil {
		return fmt.Errorf("crypto db dir not writable: %w", err)
	}
	_ = os.Remove(probe)
	if dbFile := filepath.Join(dir, dbName); fileExists(dbFile) {
		db, err := sql.Open("sqlite", "file:"+dbFile+"?mode=ro")
		if err != nil { return err }
		defer db.Close()
		if err := db.Ping(); err != nil {
			return fmt.Errorf("crypto db not a valid sqlite file: %w", err)
		}
	}
	return nil
}

Try / catch

// in Go, handle the startup error and distinguish transient vs permanent
if err := matrixCh.Start(ctx); err != nil {
	var pragmaErr *pragmaExecError // wrap and classify at your boundary
	if errors.As(err, &pragmaErr) && isLockError(pragmaErr.Unwrap()) {
		backoff.Retry(startMatrix, 3) // transient SQLITE_BUSY
	} else {
		log.Fatalf("matrix crypto db unusable: %v", err) // corrupt/ro fs: fix environment
	}
}

Prevention

When it happens

Trigger: Executing 'PRAGMA journal_mode = WAL' on a filesystem that does not support it (NFS/network volumes, some container overlayfs) causing SQLITE_IOERR; the file at the crypto DB path existing but not being a valid SQLite database ('file is not a database'); the crypto DB directory being read-only or owned by another user; a second process holding an exclusive lock on the DB file (busy_timeout=5000 is set later in the same loop, so it cannot rescue the earlier statements); the startup context being canceled mid-loop.

Common situations: Bot deployed in Docker/Kubernetes with the crypto DB path on a read-only or network-attached volume; a leftover corrupt crypto.db from a previous crashed run or from software that wrote a different format at the same path; running two bot instances against the same crypto DB path; permission changes (dir was created 0700 by root, bot now runs as non-root).

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/9e845743b1327838. Report an issue: GitHub.