gastownhall/beads · error

identity: generate proxy secret: %w

Error message

identity: generate proxy secret: %w

What it means

WriteSecret generates 32 random bytes via crypto/rand.Read and encodes them as the control-listener secret. This error wraps a failure of crypto/rand.Read, which on virtually all platforms only fails if the operating system's cryptographic random source is unavailable or broken. The library throws it because it cannot produce a cryptographically secure secret without the OS entropy source.

Source

Thrown at internal/storage/dbproxy/identity/identity.go:45

func RootID(rootDir string) (string, error) {
	abs, err := filepath.Abs(rootDir)
	if err != nil {
		return "", fmt.Errorf("identity: absolute root path: %w", err)
	}
	resolved, err := filepath.EvalSymlinks(abs)
	if err != nil {
		return "", fmt.Errorf("identity: resolve root path: %w", err)
	}
	sum := sha256.Sum256([]byte(resolved))
	return hex.EncodeToString(sum[:]), nil
}

// WriteSecret creates and atomically writes a new control-listener secret.
// Each proxy start intentionally rotates the previous secret.
func WriteSecret(rootDir string) (string, error) {
	raw := make([]byte, 32)
	if _, err := rand.Read(raw); err != nil {
		return "", fmt.Errorf("identity: generate proxy secret: %w", err)
	}
	secret := hex.EncodeToString(raw)
	if err := atomicfile.WriteFile(filepath.Join(rootDir, SecretFileName), []byte(secret+"\n"), 0o600); err != nil {
		return "", fmt.Errorf("identity: write proxy secret: %w", err)
	}
	return secret, nil
}

// ReadSecret reads and validates the control-listener secret.
func ReadSecret(rootDir string) (string, error) {
	data, err := os.ReadFile(filepath.Join(rootDir, SecretFileName)) // #nosec G304 - rootDir is the workspace proxy root, not user input
	if err != nil {
		return "", fmt.Errorf("identity: read proxy secret: %w", err)
	}
	secret := strings.TrimSpace(string(data))
	if len(secret) != 64 {
		return "", errors.New("identity: invalid proxy secret")
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error via errors.Unwrap to identify the OS-level cause
  2. Fix the environment: ensure /dev/urandom exists and seccomp/sandbox policy permits getrandom(2)
  3. Retry after the system entropy source is available (e.g. after boot completes)
  4. Check container runtime security profiles (Docker seccomp, gVisor) that may block getrandom
Defensive patterns

Strategy: retry

Try / catch

secret, err := identity.WriteSecret(rootDir)
if err != nil {
    // crypto/rand failures are environmental; retry once after a short delay,
    // then fail loudly with the wrapped cause
    time.Sleep(100 * time.Millisecond)
    secret, err = identity.WriteSecret(rootDir)
    if err != nil {
        return fmt.Errorf("entropy source unavailable: %w", err)
    }
}

Prevention

When it happens

Trigger: Calling identity.WriteSecret(rootDir) when crypto/rand.Read fails: on Linux this can happen if getrandom(2) is blocked and /dev/urandom is unavailable; on very early boot before the entropy pool is initialized on old kernels; or in exotic sandboxes/seccomp profiles that block getrandom. It is extremely rare on modern systems.

Common situations: Running inside a container or sandbox with a restricted seccomp filter that denies getrandom, extremely stripped-down environments lacking /dev/urandom, or legacy kernels during early boot with insufficient entropy.

Related errors


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