gastownhall/beads · critical

failed to generate credential encryption key: %w

Error message

failed to generate credential encryption key: %w

What it means

initCredentialKey generates a fresh 32-byte AES-256 credential encryption key by reading from crypto/rand. This error wraps a failure of io.ReadFull on crypto/rand.Reader, meaning the OS entropy source could not deliver 32 random bytes. It is thrown so that bd refuses to fall back to a weak or partially-filled key, since a bad key would compromise all stored federation peer passwords.

Source

Thrown at internal/storage/dolt/credentials.go:88

	// Migration: try old location (.beads/dolt/) and move to new location
	if s.dbPath != "" {
		oldKeyPath := filepath.Join(s.dbPath, credentialKeyFile)
		oldKey, oldErr := os.ReadFile(oldKeyPath) //nolint:gosec // G304: oldKeyPath is derived from trusted dbPath
		if oldErr == nil && len(oldKey) == 32 {
			// Write to new location, then remove old file
			if writeErr := os.WriteFile(keyPath, oldKey, 0600); writeErr == nil {
				_ = os.Remove(oldKeyPath)
			}
			s.credentialKey = oldKey
			return nil
		}
	}

	// Generate new random 32-byte key (AES-256)
	key = make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return fmt.Errorf("failed to generate credential encryption key: %w", err)
	}

	// Migrate existing credentials from old dbPath-derived key to new random key
	if err := s.migrateCredentialKeys(ctx, key); err != nil {
		return fmt.Errorf("failed to migrate credential keys: %w", err)
	}

	// Write key file with owner-only permissions (0600).
	// Ensure the directory exists first — when connecting to an external
	// server without having run `bd init`, .beads/ may not exist yet (GH#2641).
	if err := os.MkdirAll(s.beadsDir, 0700); err != nil {
		return fmt.Errorf("failed to create beads directory %s: %w", s.beadsDir, err)
	}
	if err := os.WriteFile(keyPath, key, 0600); err != nil {
		return fmt.Errorf("failed to write credential key file: %w", err)
	}

	s.credentialKey = key

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run the command — crypto/rand failures are usually transient at boot; the system RNG typically becomes available within seconds
  2. Check that /dev/urandom exists and is readable in the environment (ls -l /dev/urandom; on Linux verify getrandom(2) is not blocked)
  3. Inspect container/seccomp/AppArmor policies for blocks on getrandom and allow the syscall
  4. If inside a container at early boot, add an entropy-availability wait or upgrade the kernel/host so getrandom never blocks
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: check the OS RNG is readable before running bd operations in constrained environments
f, err := os.Open("/dev/urandom")
if err != nil { log.Fatal("no entropy source available") }
f.Close()

Try / catch

if err := runBd(); err != nil {
    if strings.Contains(err.Error(), "failed to generate credential encryption key") {
        // transient entropy failure: wait and retry once
        time.Sleep(2 * time.Second)
        err = runBd()
    }
}

Prevention

When it happens

Trigger: io.ReadFull(rand.Reader, key) fails during initCredentialKey — i.e. crypto/rand.Reader returns an error or fewer than 32 bytes. In practice only when the OS CSPRNG is unavailable or returns an error (e.g. exhausted entropy at early boot, broken /dev/urandom, seccomp/container policy blocking getrandom(2)).

Common situations: Early-boot containers or VMs with insufficient entropy; hardened seccomp/AppArmor profiles that block the getrandom syscall; unusual minimal Linux environments where /dev/urandom is inaccessible; Go runtime reporting a rand failure is otherwise extremely rare on modern systems.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/83e5e51047858f17. Report an issue: GitHub.