{"record":{"id":"2936bbbbf4014ff4","repo":"chenhg5/cc-connect","slug":"scheduled-at-is-required","errorCode":null,"errorMessage":"scheduled_at is required","messagePattern":"scheduled_at is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/timer.go","lineNumber":65,"sourceCode":"\t\treturn defaultTimerJobTimeout\n\t}\n\tif *j.TimeoutMins <= 0 {\n\t\treturn 0\n\t}\n\treturn time.Duration(*j.TimeoutMins) * time.Minute\n}\n\n// UsesNewSessionPerRun reports whether the timer should use a new engine session.\nfunc (j *TimerJob) UsesNewSessionPerRun() bool {\n\treturn NormalizeCronSessionMode(j.SessionMode) == \"new_per_run\"\n}\n\nfunc validateTimerJob(j *TimerJob) error {\n\tif strings.TrimSpace(j.SessionKey) == \"\" {\n\t\treturn fmt.Errorf(\"session_key is required\")\n\t}\n\tif j.ScheduledAt.IsZero() {\n\t\treturn fmt.Errorf(\"scheduled_at is required\")\n\t}\n\tif j.Prompt == \"\" && j.Exec == \"\" {\n\t\treturn fmt.Errorf(\"either prompt or exec is required\")\n\t}\n\tif j.Prompt != \"\" && j.Exec != \"\" {\n\t\treturn fmt.Errorf(\"prompt and exec are mutually exclusive\")\n\t}\n\tmode := NormalizeCronSessionMode(j.SessionMode)\n\tif mode != \"\" && mode != \"new_per_run\" {\n\t\treturn fmt.Errorf(\"invalid session_mode %q (want reuse, new_per_run, or new-per-run)\", j.SessionMode)\n\t}\n\tif j.Mode != \"\" {\n\t\tswitch j.Mode {\n\t\tcase \"default\", \"bypassPermissions\", \"acceptEdits\", \"plan\", \"auto\", \"dontAsk\":\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid mode %q\", j.Mode)\n\t\t}\n\t}","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/timer.go#L47-L83","documentation":"validateTimerJob rejects a TimerJob whose ScheduledAt is the zero time.Time when it is registered via AddJob. Without a schedule time the scheduler cannot compute a firing delay, so such a job is meaningless and is rejected upfront.","triggerScenarios":"AddJob with a &TimerJob{...} where ScheduledAt was never assigned — e.g. time.Time{} zero value from struct literal, a failed time.Parse whose error was ignored, or an omitted field when deserializing the job.","commonSituations":"Parsing a user-supplied date string with time.Parse and ignoring the error, leaving ScheduledAt zero; building jobs from config where \"scheduled_at\" is absent; using `time.Now()` in one package but assigning to the wrong field during a refactor.","solutions":["Assign a concrete time to ScheduledAt (e.g. time.Now().Add(5*time.Minute) or a parsed time) before AddJob.","Check the error return of time.Parse/time.ParseInLocation; on failure the zero time will be stored and rejected.","If the field comes from JSON, verify the timestamp format is RFC3339-compatible so unmarshaling succeeds.","Validate ScheduledAt.IsZero() in caller code before AddJob to give a clearer error."],"exampleFix":"// before\nwhen, _ := time.Parse(time.RFC3339, cfg.At) // error swallowed -> zero time\nsched.AddJob(&core.TimerJob{SessionKey: k, ScheduledAt: when, Prompt: p})\n// after\nwhen, err := time.Parse(time.RFC3339, cfg.At)\nif err != nil { return fmt.Errorf(\"parse scheduled_at: %w\", err) }\nsched.AddJob(&core.TimerJob{SessionKey: k, ScheduledAt: when, Prompt: p})","handlingStrategy":"validation","validationCode":"if job.ScheduledAt.IsZero() { return errors.New(\"timer job needs scheduled_at\") }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never ignore the error from time.Parse — a failed parse yields the zero time","Default ScheduledAt to time.Now().Add(...) when omitted instead of leaving it zero","Store timestamps in RFC3339 so unmarshaling round-trips cleanly"],"tags":["timer","validation","scheduler"],"backgroundTag":"missing-required-argument","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}