sipeed/picoclaw · error
failed to load store: %w
Error message
failed to load store: %w
What it means
CronService.Start() first calls loadStore, which reads the JSON store file at cs.storePath (a missing file is fine - fresh empty store) and json.Unmarshals it. Any other read error (permission denied, path is a directory, I/O error) or an Unmarshal failure (truncated/corrupt JSON, schema mismatch) is wrapped as "failed to load store". Start aborts and the cron loop never launches.
Source
Thrown at pkg/cron/service.go:92
onJob: onJob,
gronx: gronx.New(),
wakeChan: make(chan struct{}),
}
// Initialize and load store on creation
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() {View on GitHub (pinned to 49183d7e8d)
Solutions
- Validate the file: `jq . <storePath>` - if jq complains, restore from backup or delete the file (jobs are lost but the service starts fresh)
- Fix ownership/permissions so the service user can read it: `chown <svcuser> <storePath>` (file is written 0600)
- If a schema/version mismatch is suspected, downgrade to the version that wrote the file, export jobs, then re-import
- Check the exact underlying error in the wrapped chain - Unmarshal errors point at the byte offset of the corruption
Example fix
# before: start with a corrupt store, service refuses to boot picoclaw-daemon # Start() -> failed to load store # after: verify and repair before starting jq empty /var/lib/picoclaw/cron.json || cp /var/lib/picoclaw/cron.json.bak /var/lib/picoclaw/cron.json chown picoclaw:picoclaw /var/lib/picoclaw/cron.json picoclaw-daemon
Defensive patterns
Strategy: validation
Validate before calling
func storeFileSound(path string) error {
data, err := os.ReadFile(path)
if os.IsNotExist(err) {
return nil // fresh store is fine
}
if err != nil {
return fmt.Errorf("cron store unreadable: %w", err)
}
if !json.Valid(data) {
return fmt.Errorf("cron store %s is not valid JSON", path)
}
return nil
} Type guard
func isStoreCorruption(err error) bool {
return err != nil && strings.Contains(err.Error(), "failed to load store") &&
(strings.Contains(err.Error(), "invalid character") ||
strings.Contains(err.Error(), "unexpected end of JSON"))
} Try / catch
if err := svc.Start(); err != nil {
if isStoreCorruption(err) {
// quarantine the corrupt file and start fresh rather than crashing forever
os.Rename(storePath, storePath+".corrupt")
return svc.Start()
}
return err
} Prevention
- Back up the cron store on a schedule; the JSON format makes diffs/restores trivial
- Validate with `jq empty` after any manual edit or version upgrade
- Keep the store owned by the service user (written 0600 via WriteFileAtomic)
- Never hand-edit the store while the daemon is running
When it happens
Trigger: NewCronService(storePath, handler) then Start() where the store file was truncated by a crash during a non-atomic write (older version without WriteFileAtomic), a root-owned 0600 file being read by a non-root service, someone hand-edited the JSON, or a future schema version wrote fields this build cannot parse.
Common situations: Upgrading picoclaw across store-format changes; moving data dirs between users; disk-full events that previously truncated the JSON; manually editing cron.json and leaving a trailing comma; restoring a backup with wrong ownership.
Related errors
- failed to save store: %w
- error adding job: %w
- ${label} must be a JSON object.
- ${label}.${key} must be a string.
- Failed to save config
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/8d9c7ccb6c4c0c1f.
Report an issue: GitHub.