{"record":{"id":"122b93f4460cc7e0","repo":"chenhg5/cc-connect","slug":"prompt-and-exec-are-mutually-exclusive-122b93","errorCode":null,"errorMessage":"prompt and exec are mutually exclusive","messagePattern":"prompt and exec are mutually exclusive","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"core/webhook.go","lineNumber":112,"sourceCode":"\t\treturn\n\t}\n\n\tvar req WebhookRequest\n\tif err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n\t\thttp.Error(w, \"invalid JSON: \"+err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif req.SessionKey == \"\" {\n\t\thttp.Error(w, \"session_key is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif req.Prompt == \"\" && req.Exec == \"\" {\n\t\thttp.Error(w, \"either prompt or exec is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif req.Prompt != \"\" && req.Exec != \"\" {\n\t\thttp.Error(w, \"prompt and exec are mutually exclusive\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tengine, err := ws.resolveEngine(req.Project)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\teventName := req.Event\n\tif eventName == \"\" {\n\t\teventName = \"webhook\"\n\t}\n\n\tslog.Info(\"webhook: received\",\n\t\t\"event\", eventName,\n\t\t\"project\", req.Project,\n\t\t\"session_key\", req.SessionKey,","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/webhook.go#L94-L130","documentation":"The request contained both `prompt` and `exec`, which are mutually exclusive actions; the handler returns 400 \"prompt and exec are mutually exclusive\". A single webhook call can either send a natural-language prompt or execute a command, not both.","triggerScenarios":"JSON body where both fields are non-empty strings, e.g. {\"prompt\":\"fix tests\",\"exec\":\"go test\"}; automation templates that always serialize both keys including defaults.","commonSituations":"Payload builders marshaling a struct with both fields set to defaults; merging of two configs/requests; user pasting a command into the prompt UI while the automation also fills exec.","solutions":["Remove one of the two fields — send prompt OR exec in a single request.","Issue two separate webhook calls if both actions are needed.","In the payload builder, zero out the unused field before marshaling.","Apply omitempty / explicit emptiness checks so defaults don't leak both fields."],"exampleFix":"// before\n{\"session_key\":\"s1\",\"prompt\":\"fix tests\",\"exec\":\"go test\"}\n// after\n{\"session_key\":\"s1\",\"prompt\":\"run go test and fix failures\"}","handlingStrategy":"validation","validationCode":"func checkWebhookReq(r WebhookRequest) error {\n    if r.Prompt != \"\" && r.Exec != \"\" { return errors.New(\"prompt and exec are mutually exclusive\") }\n    return nil\n}","typeGuard":"func exactlyOneAction(p map[string]any) bool {\n    pr, _ := p[\"prompt\"].(string); ex, _ := p[\"exec\"].(string)\n    return (pr != \"\") != (ex != \"\")\n}","tryCatchPattern":"resp, err := http.Post(url, \"application/json\", bytes.NewReader(payload))\nif err == nil && resp.StatusCode == 400 {\n    b, _ := io.ReadAll(resp.Body)\n    if strings.Contains(string(b), \"mutually exclusive\") {\n        return errors.New(\"drop either prompt or exec and resend\")\n    }\n    return fmt.Errorf(\"webhook rejected: %s\", b)\n}","preventionTips":["Model the payload as prompt XOR exec at the type level where possible","Zero out the unused field before marshaling","Combine both intents into one prompt instead of two fields","Add a unit test asserting builders never emit both fields"],"tags":["webhook","validation","conflicting-fields","http-400"],"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"}