{"record":{"id":"8d9c7ccb6c4c0c1f","repo":"sipeed/picoclaw","slug":"failed-to-load-store-w","errorCode":null,"errorMessage":"failed to load store: %w","messagePattern":"failed to load store: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/cron/service.go","lineNumber":92,"sourceCode":"\t\tonJob:     onJob,\n\t\tgronx:     gronx.New(),\n\t\twakeChan:  make(chan struct{}),\n\t}\n\t// Initialize and load store on creation\n\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() {","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/cron/service.go#L74-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before: start with a corrupt store, service refuses to boot\npicoclaw-daemon  # Start() -> failed to load store\n\n# after: verify and repair before starting\njq empty /var/lib/picoclaw/cron.json || cp /var/lib/picoclaw/cron.json.bak /var/lib/picoclaw/cron.json\nchown picoclaw:picoclaw /var/lib/picoclaw/cron.json\npicoclaw-daemon","handlingStrategy":"validation","validationCode":"func storeFileSound(path string) error {\n    data, err := os.ReadFile(path)\n    if os.IsNotExist(err) {\n        return nil // fresh store is fine\n    }\n    if err != nil {\n        return fmt.Errorf(\"cron store unreadable: %w\", err)\n    }\n    if !json.Valid(data) {\n        return fmt.Errorf(\"cron store %s is not valid JSON\", path)\n    }\n    return nil\n}","typeGuard":"func isStoreCorruption(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"failed to load store\") &&\n        (strings.Contains(err.Error(), \"invalid character\") ||\n         strings.Contains(err.Error(), \"unexpected end of JSON\"))\n}","tryCatchPattern":"if err := svc.Start(); err != nil {\n    if isStoreCorruption(err) {\n        // quarantine the corrupt file and start fresh rather than crashing forever\n        os.Rename(storePath, storePath+\".corrupt\")\n        return svc.Start()\n    }\n    return err\n}","preventionTips":["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"],"tags":["persistence","json","startup","cron","corruption"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}