sipeed/picoclaw · error
credential: failed to resolve credential file path %q: %w
Error message
credential: failed to resolve credential file path %q: %w
What it means
Returned by Resolver.Resolve when filepath.EvalSymlinks fails on the joined path configDir/filename — overwhelmingly because the target file does not exist (ENOENT), but also for unreadable path components or I/O errors. Symlinks are resolved deliberately before containment enforcement, so a dangling symlink also lands here. The message includes the unresolved keyPath for debugging.
Source
Thrown at pkg/credential/credential.go:130
if raw == "" {
return "", nil
}
if strings.HasPrefix(raw, FileScheme) {
fileName := strings.TrimSpace(strings.TrimPrefix(raw, FileScheme))
if fileName == "" {
return "", fmt.Errorf("credential: file:// reference has no filename")
}
baseDir := r.resolvedConfigDir
if baseDir == "" {
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) {View on GitHub (pinned to 49183d7e8d)
Solutions
- Check the path printed in the error: does <configDir>/<filename> exist? Create it with the secret content if missing
- If the file exists elsewhere, either move/copy it into the config dir or fix the filename in the file:// reference
- Fix or remove dangling symlinks inside the config dir (they fail EvalSymlinks even when the final name is right)
- Verify which config dir the resolver uses (resolvedConfigDir overrides configDir when set) before assuming the location
Example fix
# before: config references file://openai.key, file absent ls config/openai.key # -> No such file # after: create the file with the secret echo -n 'sk-...' > config/openai.key && chmod 600 config/openai.key
Defensive patterns
Strategy: validation
Validate before calling
// Pre-check existence inside the resolver's base dir.
base := resolver.BaseDir() // resolvedConfigDir, falling back to configDir
p := filepath.Join(base, filename)
if _, err := os.Stat(p); err != nil {
return fmt.Errorf("credential file %s missing: create it before boot", p)
} Try / catch
val, err := resolver.Resolve(raw)
if err != nil {
var pathErr *fs.PathError
if errors.As(err, &pathErr) && errors.Is(pathErr.Err, fs.ErrNotExist) {
// missing/dangling file: actionable message with the printed keyPath
}
return "", err
} Prevention
- Add a startup preflight that stats every file:// target and lists all missing ones at once
- Keep credential filenames stable; update config when renaming
- Include credential file creation in setup docs/scripts for fresh machines
When it happens
Trigger: `file://openai.key` in config but openai.key was never created in the resolved config dir; filename typo; file lives in a different directory than the resolver's baseDir; a symlink inside configDir pointing to a deleted target (EvalSymlinks returns ENOENT).
Common situations: Fresh clones/machines where credential files aren't checked in (they're secrets) and the setup step that writes them was skipped; running with a different --config dir or env var than the one where keys were placed; renaming a key file without updating the config.
Related errors
- credential: credential file %q is empty
- credential: file:// reference has no filename
- credential: file:// path escapes config directory
- credential: failed to read credential file %q: %w
- create output dir: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/ce81b6b8f0fc1712.
Report an issue: GitHub.