{"record":{"id":"16800e653e8189bc","repo":"chenhg5/cc-connect","slug":"enabled-must-be-a-boolean","errorCode":null,"errorMessage":"enabled must be a boolean","messagePattern":"enabled must be a boolean","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"core/cron.go","lineNumber":579,"sourceCode":"\n\t// Validate session_mode if updating session_mode field\n\tif field == \"session_mode\" {\n\t\tif v, ok := value.(string); ok && v != \"\" {\n\t\t\tmode := NormalizeCronSessionMode(v)\n\t\t\tif mode != \"\" && mode != \"new_per_run\" {\n\t\t\t\treturn fmt.Errorf(\"invalid session_mode %q (want reuse, new_per_run, or new-per-run)\", v)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate enabled type up-front. Without this, a non-bool value (e.g. a\n\t// JSON string \"true\" from a misbehaving API client) reaches updateJobField\n\t// only after we've already removed the cron entry below, and store.Update\n\t// then fails on the type mismatch — leaving the job marked Enabled in the\n\t// store but never firing again until the daemon restarts.\n\tif field == \"enabled\" {\n\t\tif _, ok := value.(bool); !ok {\n\t\t\treturn fmt.Errorf(\"enabled must be a boolean\")\n\t\t}\n\t}\n\n\t// Check if reschedule is needed\n\tneedsReschedule := field == \"cron_expr\" || field == \"enabled\"\n\n\tif needsReschedule {\n\t\t// Remove current schedule\n\t\tcs.mu.Lock()\n\t\tif entryID, ok := cs.entries[id]; ok {\n\t\t\tcs.cron.Remove(entryID)\n\t\t\tdelete(cs.entries, id)\n\t\t}\n\t\tcs.mu.Unlock()\n\t}\n\n\t// Update the field\n\tif !cs.store.Update(id, field, value) {","sourceCodeStart":561,"sourceCodeEnd":597,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/cron.go#L561-L597","documentation":"UpdateJob validates that the \"enabled\" field is a Go bool before applying it. This guard exists because JSON decoders may deliver the value as a string (e.g. \"true\"), and if the string reached store.Update, the store would reject it after the cron entry was already removed — leaving the job enabled in the store but never firing until daemon restart. The scheduler therefore fails fast before mutating any state.","triggerScenarios":"Calling UpdateJob(id, \"enabled\", value) where value is not a bool — typically a JSON-decoded string \"true\"/\"false\" from a misbehaving API client, or any other type passed via handleCronEdit or handleCronByID.","commonSituations":"A platform command handler unmarshals the edit payload into map[string]any, so JSON numbers/strings lose their static types; a client sends {\"enabled\": \"true\"} instead of {\"enabled\": true}.","solutions":["Pass a real bool: use value == \"true\" style coercion at the API boundary before calling UpdateJob","Decode the JSON field into a typed struct with a bool Enabled field instead of map[string]any","If the error is returned, surface it to the caller and do not retry — the job schedule is untouched"],"exampleFix":"// before\nerr := cs.UpdateJob(id, \"enabled\", rawValue) // rawValue is any from JSON\n// after\nenabled, ok := rawValue.(bool)\nif !ok {\n    if s, isStr := rawValue.(string); isStr {\n        enabled, err = strconv.ParseBool(s)\n        if err != nil { return err }\n    } else {\n        return fmt.Errorf(\"enabled must be a boolean\")\n    }\n}\nerr := cs.UpdateJob(id, \"enabled\", enabled)","handlingStrategy":"validation","validationCode":"func validEnabled(v any) bool { _, ok := v.(bool); return ok }","typeGuard":"en, ok := value.(bool)","tryCatchPattern":null,"preventionTips":["Decode edit payloads into typed structs, not map[string]any","Coerce JSON strings to bool at the API boundary","Add a unit test asserting UpdateJob rejects non-bool without touching the schedule"],"tags":["go","validation","type-mismatch","cron"],"backgroundTag":"invalid-argument-value","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}