gastownhall/beads · error

failed to write credential key file: %w

Error message

failed to write credential key file: %w

What it means

After generating (or migrating) the credential encryption key, initCredentialKey persists it to .beads/.beads-credential-key with 0600 permissions via os.WriteFile. This error wraps a failure of that write, meaning the key could not be saved. If it is not persisted, credentials would be encrypted with a key that is lost on process exit, so bd fails fast instead.

Source

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

	// 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
	return nil
}

// ensureCredentialKey lazily initializes the credential key when federation
// operations actually need password encryption or decryption.
func (s *DoltStore) ensureCredentialKey(ctx context.Context) error {
	s.mu.RLock()
	if s.credentialKey != nil {
		s.mu.RUnlock()
		return nil
	}
	s.mu.RUnlock()

	s.mu.Lock()
	defer s.mu.Unlock()

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check ownership/permissions of .beads/.beads-credential-key and the .beads directory (ls -l .beads); fix with chown/chmod so the current user can write (0600 on the file, 0700 on the dir)
  2. If no peer credentials need preserving, delete the stale key file so a fresh one can be written (note: existing stored passwords become undecryptable — re-add peers afterwards)
  3. Free disk space or address quota/inode exhaustion if that is the wrapped cause
  4. Run bd as the workspace owner rather than root/sudo, which would leave root-owned files behind

Example fix

// before
$ ls -l .beads/.beads-credential-key
-rw------- 1 root root ... (owned by root, bd runs as dev)
// after
$ sudo chown dev:dev .beads/.beads-credential-key && chmod 600 .beads/.beads-credential-key
Defensive patterns

Strategy: validation

Validate before calling

keyPath := filepath.Join(".beads", ".beads-credential-key")
if info, err := os.Stat(keyPath); err == nil {
    if st, _ := info.Sys().(*syscall.Stat_t); st != nil && st.Uid != uint32(os.Getuid()) {
        return fmt.Errorf("key file owned by uid %d, current uid %d", st.Uid, os.Getuid())
    }
}
if err := os.WriteFile(".beads/.probe", []byte("x"), 0600); err != nil {
    return fmt.Errorf(".beads not writable: %w", err)
}
os.Remove(".beads/.probe")

Prevention

When it happens

Trigger: os.WriteFile(keyPath, key, 0600) fails during initCredentialKey: the .beads directory exists but is not writable by the current user, the disk is full, the key file exists with restrictive ownership (written by another user), an immutable attribute is set, or the filesystem is read-only.

Common situations: Workspace previously used by another user so .beads-credential-key is owned by root; read-only mounts or disk-quota exhaustion; running bd via cron/CI under a service account that lacks write access to the repo's .beads directory; SELinux/AppArmor denials on the key path.

Related errors


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