gastownhall/beads · error
identity: invalid proxy secret
Error message
identity: invalid proxy secret
What it means
ReadSecret loads the proxy secret file and requires the trimmed content to be exactly 64 characters — a 32-byte secret hex-encoded. This error means the file exists but its length is wrong: it was truncated, empty, or written in a different format (raw bytes, base64, extra content).
Source
Thrown at internal/storage/dbproxy/identity/identity.go:62
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")
}
if _, err := hex.DecodeString(secret); err != nil {
return "", errors.New("identity: invalid proxy secret")
}
return secret, nil
}
View on GitHub (pinned to 71377f2769)
Solutions
- Regenerate the secret with the package's WriteSecret/Rotate helper and restart the proxy
- Check file size: expect 64 bytes (+optional newline); restore from backup if truncated
- Ensure no two processes rotate the secret file concurrently (atomic rename, not in-place write)
- Verify no provisioning script writes the secret in base64 or raw-byte form
Example fix
// before // hand-written secret os.WriteFile(secretPath, []byte(base64.StdEncoding.EncodeToString(key)), 0600) // after // hex-encode to exactly 64 chars, write atomically hexSecret := hex.EncodeToString(key) // 64 chars os.WriteFile(secretPath+".tmp", []byte(hexSecret+"\n"), 0600) os.Rename(secretPath+".tmp", secretPath)
Defensive patterns
Strategy: validation
Validate before calling
info, err := os.Stat(secretPath); if err != nil || info.Size() < 64 { return ErrSecretMissing } Type guard
func isInvalidSecretLen(err error) bool { return strings.Contains(err.Error(), "invalid proxy secret") } Try / catch
secret, err := identity.ReadSecret(root)
if err != nil {
if isInvalidSecretLen(err) { return regenerateAndRotateSecret(root) }
return err
} Prevention
- Only write the secret with the package's WriteSecret/Rotate helper
- Use atomic writes (temp file + rename) to avoid truncated secrets
- Ensure provisioning writes hex, not base64 or raw bytes
- Check file size is 64 bytes (+newline) after any rotation
When it happens
Trigger: Calling ReadSecret when SecretFileName under rootDir contains content whose trimmed length is not 64 — empty file, partial write during creation/rotation, or a secret written by a different tool/version in raw or base64 form.
Common situations: Crash or concurrent rotation leaving a partially written secret file; manually editing the secret file; provisioning tooling writing base64 instead of hex; disk-full truncation.
Related errors
- identity: invalid request nonce
- backend must be set
- repo %q must use OWNER/REPO or HOST/OWNER/REPO
- repo %q contains an empty path component
- repo %q contains invalid character %q
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cdef28bc4ccd843c.
Report an issue: GitHub.