sipeed/picoclaw · error
credential: credential file %q is empty
Error message
credential: credential file %q is empty
What it means
Returned by Resolver.Resolve when the credential file was found and read successfully but its content is empty after TrimSpace. A file:// reference exists to hold a secret, so an empty (or whitespace-only) file is treated as a configuration defect rather than silently resolving to an empty credential — that would produce baffling downstream 401s. The path is included in the error.
Source
Thrown at pkg/credential/credential.go:142
baseDir = r.configDir
}
keyPath := filepath.Join(baseDir, fileName)
// Resolve symlinks before enforcing containment to prevent escaping via symlinks.
realKeyPath, err := filepath.EvalSymlinks(keyPath)
if err != nil {
return "", fmt.Errorf("credential: failed to resolve credential file path %q: %w", keyPath, err)
}
if !isWithinDir(realKeyPath, baseDir) {
return "", fmt.Errorf("credential: file:// path escapes config directory")
}
data, err := os.ReadFile(realKeyPath)
if err != nil {
return "", fmt.Errorf("credential: failed to read credential file %q: %w", realKeyPath, err)
}
value := strings.TrimSpace(string(data))
if value == "" {
return "", fmt.Errorf("credential: credential file %q is empty", realKeyPath)
}
return value, nil
}
if strings.HasPrefix(raw, EncScheme) {
return resolveEncrypted(raw)
}
// Plaintext credential — return unchanged.
return raw, nil
}
// resolveEncrypted decrypts an enc:// credential using PassphraseProvider.
func resolveEncrypted(raw string) (string, error) {
passphrase := PassphraseProvider()
if passphrase == "" {
return "", ErrPassphraseRequiredView on GitHub (pinned to 49183d7e8d)
Solutions
- Write the actual secret into the exact path shown in the error: `printf '%s' 'sk-...' > <path>`
- If the file is a placeholder, remove it and use a real empty-string credential (omit the key) only if the provider genuinely needs no key
- Check your provisioning script for `echo $VAR > file` with unset/empty VAR — make it fail loudly instead
Example fix
# before : > config/openai.key # zero-byte placeholder # after printf '%s' 'sk-...' > config/openai.key && chmod 600 config/openai.key
Defensive patterns
Strategy: validation
Validate before calling
// Preflight: every file:// target must have non-blank content.
data, err := os.ReadFile(p)
if err != nil { return err }
if strings.TrimSpace(string(data)) == "" {
return fmt.Errorf("credential file %s is empty — fill it before boot", p)
} Try / catch
if _, err := resolver.Resolve(raw); err != nil {
if strings.Contains(err.Error(), "is empty") {
// provisioning wrote nothing; fail fast with the file path from the message
}
return err
} Prevention
- Make secret-injection scripts error on empty source values (set -u; test -n "$VAR")
- Avoid touch-placeholder patterns for credential files
- Add a boot-time preflight listing all empty credential files
When it happens
Trigger: `touch config/openai.key` created a zero-byte placeholder and config references file://openai.key; file contains only whitespace/newlines; a provisioning step wrote the secret to the wrong file and left this one blank.
Common situations: Setup checklists that create empty placeholder files to fill in later; secret-injection scripts that echo an empty env var into the file (`echo $API_KEY > openai.key` with API_KEY unset); truncated writes from a crashed provisioner.
Related errors
- credential: file:// reference has no filename
- credential: failed to resolve credential file path %q: %w
- deltachat: account %s is not configured in data_dir %s (%s)
- credential: file:// path escapes config directory
- feishu channel is not supported on 32-bit architectures (arm
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/da96fbf4b385533c.
Report an issue: GitHub.