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 "", ErrPassphraseRequired

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Write the actual secret into the exact path shown in the error: `printf '%s' 'sk-...' > <path>`
  2. 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
  3. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/da96fbf4b385533c. Report an issue: GitHub.