sipeed/picoclaw · error
failed to read security config: %w
Error message
failed to read security config: %w
What it means
Returned by loadSecurityConfig when os.ReadFile(securityPath) fails with an error other than not-exist (missing files are deliberately tolerated and return nil). This is an I/O-level failure reading security.yml: permission denied, the path is a directory, or another filesystem error. The underlying error is wrapped with %w so causes chain.
Source
Thrown at pkg/config/security.go:44
// securityPath returns the path to security.yml relative to the config file
func securityPath(configPath string) string {
configDir := filepath.Dir(configPath)
return filepath.Join(configDir, SecurityConfigFile)
}
// loadSecurityConfig loads the security configuration from security.yml
// and merges secure field values into the config.
func loadSecurityConfig(cfg *Config, securityPath string) error {
if cfg == nil {
return fmt.Errorf("config is nil")
}
data, err := os.ReadFile(securityPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("failed to read security config: %w", err)
}
// Save existing channels and ModelList before unmarshal
savedChannels := make(ChannelsConfig, len(cfg.Channels))
for name, bc := range cfg.Channels {
savedChannels[name] = bc
}
// savedModelList := cfg.ModelList
// Parse YAML into a yaml.Node tree to extract channels node
var rootNode yaml.Node
if err := yaml.Unmarshal(data, &rootNode); err != nil {
return fmt.Errorf("failed to parse security config: %w", err)
}
// Extract channels node (support both 'channels' and 'channel_list' keys)
var channelsNode *yaml.Node
if len(rootNode.Content) > 0 {View on GitHub (pinned to 49183d7e8d)
Solutions
- Check permissions: `ls -l <configdir>/security.yml` and `chmod 644` (or chown to the running user) so the process can read it
- If security.yml is a directory, remove/rename it and restore the real file
- Verify the path your app passes as securityPath actually points at the file you think it does (config dir env var, flag override)
- If the file is genuinely absent and you still get this, you are passing a path whose parent is unreadable — fix parent dir permissions
Example fix
# before (root-owned, mode 600) -rw------- 1 root root security.yml # after (readable by service user) chown appuser:appuser security.yml && chmod 600 security.yml
Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the file is readable as this process before loading config.
if info, err := os.Stat(securityPath); err == nil {
if info.IsDir() {
return fmt.Errorf("security config path %s is a directory", securityPath)
}
if f, err := os.Open(securityPath); err == nil {
f.Close()
} else {
return fmt.Errorf("security config %s not readable: %w", securityPath, err)
}
} Try / catch
if err := loadSecurityConfig(cfg, securityPath); err != nil {
if errors.Is(err, fs.ErrPermission) {
// guide user to chmod/chown rather than raw error
log.Fatalf("cannot read %s: fix ownership/permissions", securityPath)
}
return err
} Prevention
- Ship config dirs with correct ownership for the service user from day one
- Smoke-test readability (`test -r security.yml`) in container entrypoints
- Keep security.yml a regular file; never let tooling create a directory of that name
When it happens
Trigger: security.yml exists but the process lacks read permission (mode 0600 owned by another user), security.yml is a directory, or the file sits on a mount with I/O errors. os.IsNotExist(err) is false in all these cases, so the error is returned instead of ignored.
Common situations: Running the service as a different user than the one that created security.yml (common after switching to a systemd unit or container user), restoring configs with wrong ownership from backups/tarballs, or a stale directory named security.yml.
Related errors
- failed to save config: %w
- create output dir: %w
- failed to save config: %w
- local command %q is not executable
- ✗ failed to create skills directory: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/b59df23ed95bf90d.
Report an issue: GitHub.