{"record":{"id":"25b0a46010781bde","repo":"sipeed/picoclaw","slug":"error-starting-heartbeat-service-w","errorCode":null,"errorMessage":"error starting heartbeat service: %w","messagePattern":"error starting heartbeat service: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gateway/gateway.go","lineNumber":441,"sourceCode":"\t\tcfg,\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error setting up cron service: %w\", err)\n\t}\n\tif err = runningServices.CronService.Start(); err != nil {\n\t\treturn nil, fmt.Errorf(\"error starting cron service: %w\", err)\n\t}\n\tfmt.Println(\"✓ Cron service started\")\n\n\trunningServices.HeartbeatService = heartbeat.NewHeartbeatService(\n\t\tcfg.WorkspacePath(),\n\t\tcfg.Heartbeat.Interval,\n\t\tcfg.Heartbeat.Enabled,\n\t)\n\trunningServices.HeartbeatService.SetBus(msgBus)\n\trunningServices.HeartbeatService.SetHandler(createHeartbeatHandler(agentLoop))\n\tif err = runningServices.HeartbeatService.Start(); err != nil {\n\t\treturn nil, fmt.Errorf(\"error starting heartbeat service: %w\", err)\n\t}\n\tfmt.Println(\"✓ Heartbeat service started\")\n\n\trunningServices.MediaStore = media.NewFileMediaStoreWithCleanup(media.MediaCleanerConfig{\n\t\tEnabled:  cfg.Tools.MediaCleanup.Enabled,\n\t\tMaxAge:   time.Duration(cfg.Tools.MediaCleanup.MaxAge) * time.Minute,\n\t\tInterval: time.Duration(cfg.Tools.MediaCleanup.Interval) * time.Minute,\n\t})\n\tif fms, ok := runningServices.MediaStore.(*media.FileMediaStore); ok {\n\t\tfms.Start()\n\t}\n\n\trunningServices.ChannelManager, err = channels.NewManager(\n\t\tcfg,\n\t\tmsgBus,\n\t\trunningServices.MediaStore,\n\t\tchannels.WithRuntimeEvents(agentLoop.RuntimeEventBus()),\n\t)","sourceCodeStart":423,"sourceCodeEnd":459,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/gateway/gateway.go#L423-L459","documentation":"The gateway failed while bringing up the heartbeat service during initial startup. HeartbeatService.Start() is wrapped with fmt.Errorf so any failure aborts gateway boot; in the current implementation Start() only returns non-nil on future internal changes (it returns nil when already running or disabled), so this is a defensive guard.","triggerScenarios":"Calling startServices when heartbeat.NewHeartbeatService(...).Start() returns an error after SetBus/SetHandler wiring; today Start()'s code paths (already running, disabled, spawn goroutine) all return nil, making the wrap nearly unreachable without a code change.","commonSituations":"A modified/older fork where Start validates the workspace or state manager; corrupted workspace/state directory used by state.NewManager(cfg.WorkspacePath()); heartbeat.enabled misconfigured alongside a read-only workspace.","solutions":["Verify workspace path exists and is writable (heartbeat builds state.NewManager(workspace))","Check config heartbeat.enabled and heartbeat.interval values in config.json","Inspect the wrapped error chain with errors.Unwrap to find the real cause","If running a fork, confirm HeartbeatService.Start() in pkg/heartbeat/service.go matches upstream"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if _, err := os.Stat(cfg.WorkspacePath()); err != nil {\n    log.Fatalf(\"workspace not ready before gateway start: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"if err = runningServices.HeartbeatService.Start(); err != nil {\n    return fmt.Errorf(\"error starting heartbeat service: %w\", err)\n}","preventionTips":["Pre-validate workspace existence and writability before boot","Keep heartbeat config keys (enabled, interval) within documented ranges"],"tags":["gateway","heartbeat","startup","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}