sipeed/picoclaw · error
failed to save store: %w
Error message
failed to save store: %w
What it means
After loading and recomputing next-run times, Start() persists the store via saveStoreUnsafe -> fileutil.WriteFileAtomic(storePath, data, 0600). Failure here aborts Start before the run loop goroutine launches. Causes: parent directory of storePath missing, not writable, disk full, or a read-only filesystem. Note load succeeded a moment earlier, so this is a write-path problem, not data corruption.
Source
Thrown at pkg/cron/service.go:97
cs.loadStore()
return cs
}
func (cs *CronService) Start() error {
cs.mu.Lock()
defer cs.mu.Unlock()
if cs.running {
return nil
}
if err := cs.loadStore(); err != nil {
return fmt.Errorf("failed to load store: %w", err)
}
cs.recomputeNextRuns()
if err := cs.saveStoreUnsafe(); err != nil {
return fmt.Errorf("failed to save store: %w", err)
}
cs.stopChan = make(chan struct{})
if cs.wakeChan == nil {
cs.wakeChan = make(chan struct{})
}
cs.running = true
go cs.runLoop(cs.stopChan)
return nil
}
func (cs *CronService) Stop() {
cs.mu.Lock()
defer cs.mu.Unlock()
if !cs.running {
returnView on GitHub (pinned to 49183d7e8d)
Solutions
- Ensure the parent directory exists and is writable by the service user: `install -d -m 700 -o <svcuser> $(dirname <storePath>)`
- Check space: `df -h $(dirname <storePath>)` and free space if full
- Check SELinux/AppArmor denials in the audit log if perms look fine
- Verify the storePath value passed to NewCronService matches an actually-mounted location (`mount | grep <dir>`)
Example fix
// before: Start on a path whose dir may not exist
svc := cron.NewCronService(storePath, handler)
if err := svc.Start(); err != nil { return err }
// after: pre-flight the write path
if err := os.MkdirAll(filepath.Dir(storePath), 0o700); err != nil {
return fmt.Errorf("cron store dir: %w", err)
}
if f, err := os.OpenFile(storePath, os.O_RDWR|os.O_CREATE, 0o600); err != nil {
return fmt.Errorf("cron store not writable: %w", err)
} else { f.Close() }
if err := svc.Start(); err != nil { return err } Defensive patterns
Strategy: try-catch
Validate before calling
func storeDirWritable(storePath string) error {
dir := filepath.Dir(storePath)
if err := os.MkdirAll(dir, 0o700); err != nil {
return err
}
f, err := os.OpenFile(storePath, os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
return fmt.Errorf("cron store not writable: %w", err)
}
return f.Close()
} Try / catch
if err := svc.Start(); err != nil {
if strings.Contains(err.Error(), "failed to save store") {
// write-path problem: surface fs diagnostics instead of retry-looping
return fmt.Errorf("cron store write failed (check dir perms/space for %s): %w", storePath, err)
}
return err
} Prevention
- Create the store directory (0700, service uid) during deployment, not at first Start
- Disk-space alerts on the data partition catch this before Start does
- Ensure the data volume is mounted read-write before the daemon starts (ordering in systemd units)
When it happens
Trigger: NewCronService(storePath, ...) where the directory of storePath does not exist (MkdirAll is not called by Start) or is owned by another user; disk filled between deploy and start; container with read-only data mount; storePath pointing into a tmpfs that was not mounted this boot.
Common situations: Data dir removed by a cleanup script between restarts; daemon runs as non-root but /var/lib/picoclaw is root-owned 0755; migration to a new host where the volume was not attached; SELinux denial on the directory.
Related errors
- error adding job: %w
- failed to load store: %w
- write result: %w
- failed to save config: %w
- failed to save config: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/85ca3e2eff265244.
Report an issue: GitHub.