sipeed/picoclaw · critical

create session store dir: %w

Error message

create session store dir: %w

What it means

Returned by WhatsAppNativeChannel.Start when os.MkdirAll fails to create the whatsmeow session store directory (c.storePath) with mode 0700. The %w chain keeps the OS error (e.g. permission denied, path component is a file, read-only filesystem). Start aborts before opening the sqlite session database.

Source

Thrown at pkg/channels/whatsapp_native/whatsapp_native.go:97

		config:      cfg,
		storePath:   storePath,
	}
	return c, nil
}

func (c *WhatsAppNativeChannel) Start(ctx context.Context) error {
	logger.InfoCF("whatsapp", "Starting WhatsApp native channel (whatsmeow)", map[string]any{"store": c.storePath})

	// Reset lifecycle state from any previous Stop() so a restarted channel
	// behaves correctly.  Use reconnectMu to be consistent with eventHandler
	// and Stop() which coordinate under the same lock.
	c.reconnectMu.Lock()
	c.stopping.Store(false)
	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)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Check the store path in channel config and ensure every parent directory exists and is writable by the service user.
  2. Create the directory manually with correct ownership: install -d -o <user> -m 700 <storePath>.
  3. If running in Docker, confirm the volume is mounted rw at the configured path.
  4. Look for a regular file shadowing a directory component (e.g. a file named like the dir) and remove/rename it.
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(storePath)
switch {
case err == nil && !info.IsDir():
    return fmt.Errorf("store path %s exists and is not a directory", storePath)
case os.IsNotExist(err):
    if err := os.MkdirAll(storePath, 0o700); err != nil { return err }
case err != nil:
    return err
}

Try / catch

if err := ch.Start(ctx); err != nil {
    if errors.Is(err, fs.ErrPermission) || strings.Contains(err.Error(), "create session store dir") {
        // fix directory ownership/permissions, then retry Start
    }
}

Prevention

When it happens

Trigger: Start(ctx) with storePath whose parent lacks write permission, a path where an existing regular file occupies a needed directory component, a read-only volume, or SELinux/AppArmor denial.

Common situations: Running the binary as an unprivileged user against /var/lib/... or another root-owned path; container with a read-only or unmounted volume at the store path; store path misconfigured to something like /proc/... or a path containing a stray file; macOS/linux permission mismatch after moving the data dir.

Related errors


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