{"record":{"id":"85ca3e2eff265244","repo":"sipeed/picoclaw","slug":"failed-to-save-store-w","errorCode":null,"errorMessage":"failed to save store: %w","messagePattern":"failed to save store: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/cron/service.go","lineNumber":97,"sourceCode":"\tcs.loadStore()\n\treturn cs\n}\n\nfunc (cs *CronService) Start() error {\n\tcs.mu.Lock()\n\tdefer cs.mu.Unlock()\n\n\tif cs.running {\n\t\treturn nil\n\t}\n\n\tif err := cs.loadStore(); err != nil {\n\t\treturn fmt.Errorf(\"failed to load store: %w\", err)\n\t}\n\n\tcs.recomputeNextRuns()\n\tif err := cs.saveStoreUnsafe(); err != nil {\n\t\treturn fmt.Errorf(\"failed to save store: %w\", err)\n\t}\n\n\tcs.stopChan = make(chan struct{})\n\tif cs.wakeChan == nil {\n\t\tcs.wakeChan = make(chan struct{})\n\t}\n\tcs.running = true\n\tgo cs.runLoop(cs.stopChan)\n\n\treturn nil\n}\n\nfunc (cs *CronService) Stop() {\n\tcs.mu.Lock()\n\tdefer cs.mu.Unlock()\n\n\tif !cs.running {\n\t\treturn","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/cron/service.go#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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>`)"],"exampleFix":"// before: Start on a path whose dir may not exist\nsvc := cron.NewCronService(storePath, handler)\nif err := svc.Start(); err != nil { return err }\n\n// after: pre-flight the write path\nif err := os.MkdirAll(filepath.Dir(storePath), 0o700); err != nil {\n    return fmt.Errorf(\"cron store dir: %w\", err)\n}\nif f, err := os.OpenFile(storePath, os.O_RDWR|os.O_CREATE, 0o600); err != nil {\n    return fmt.Errorf(\"cron store not writable: %w\", err)\n} else { f.Close() }\nif err := svc.Start(); err != nil { return err }","handlingStrategy":"try-catch","validationCode":"func storeDirWritable(storePath string) error {\n    dir := filepath.Dir(storePath)\n    if err := os.MkdirAll(dir, 0o700); err != nil {\n        return err\n    }\n    f, err := os.OpenFile(storePath, os.O_RDWR|os.O_CREATE, 0o600)\n    if err != nil {\n        return fmt.Errorf(\"cron store not writable: %w\", err)\n    }\n    return f.Close()\n}","typeGuard":null,"tryCatchPattern":"if err := svc.Start(); err != nil {\n    if strings.Contains(err.Error(), \"failed to save store\") {\n        // write-path problem: surface fs diagnostics instead of retry-looping\n        return fmt.Errorf(\"cron store write failed (check dir perms/space for %s): %w\", storePath, err)\n    }\n    return err\n}","preventionTips":["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)"],"tags":["persistence","filesystem","startup","cron","disk-full"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}