{"record":{"id":"253d6d7fc65d5a20","repo":"chenhg5/cc-connect","slug":"prompt-and-exec-are-mutually-exclusive","errorCode":null,"errorMessage":"prompt and exec are mutually exclusive","messagePattern":"prompt and exec are mutually exclusive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/timer.go","lineNumber":71,"sourceCode":"}\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}\n\tif j.TimeoutMins != nil && *j.TimeoutMins < 0 {\n\t\treturn fmt.Errorf(\"timeout_mins must be >= 0\")\n\t}\n\treturn nil\n}\n","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/timer.go#L53-L89","documentation":"validateTimerJob enforces that Prompt and Exec are mutually exclusive: a timer job either sends a prompt to the agent or runs a shell command, not both. Setting both is ambiguous about intent, so AddJob refuses the job.","triggerScenarios":"AddJob with a TimerJob where both Prompt != \"\" and Exec != \"\" — e.g. a config entry containing both `prompt` and `exec` keys, or builder code that assigns defaults to both fields.","commonSituations":"Users copying a timer template that included exec and then adding a prompt; programmatic construction that sets a default Exec and a caller-supplied Prompt; schema drift after adding the Exec feature to existing prompt-based configs.","solutions":["Remove Exec if the job should send a prompt to the agent.","Remove Prompt if the job should only run a shell command.","Wrap the shell work as an instruction inside Prompt if both behaviors are truly needed.","At config-load time, report which keys conflict so the user can delete one."],"exampleFix":"// before\njob := &core.TimerJob{SessionKey: k, ScheduledAt: when,\n    Prompt: \"deploy staging\", Exec: \"./deploy.sh\"}\nsched.AddJob(job) // mutually exclusive\n// after\njob := &core.TimerJob{SessionKey: k, ScheduledAt: when, Exec: \"./deploy.sh\"}\nsched.AddJob(job)","handlingStrategy":"validation","validationCode":"if job.Prompt != \"\" && job.Exec != \"\" { return errors.New(\"set only one of prompt or exec\") }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Decide the job kind (agent-prompt vs shell-exec) once and populate only that field","Strip conflicting keys when migrating old configs into the Exec-capable schema","Document that exec overrides/disallows prompt in config examples"],"tags":["timer","validation","conflict"],"backgroundTag":"mutually-exclusive-options","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"}