{"record":{"id":"8ebb7e72e7660bd9","repo":"chenhg5/cc-connect","slug":"session-key-is-required-8ebb7e","errorCode":null,"errorMessage":"session_key is required","messagePattern":"session_key is required","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"core/webhook.go","lineNumber":104,"sourceCode":"func (ws *WebhookServer) handleHook(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"POST only\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\tif !ws.authenticate(r) {\n\t\thttp.Error(w, \"unauthorized\", http.StatusUnauthorized)\n\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","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/webhook.go#L86-L122","documentation":"HTTP 400 validation response from the webhook handler: the authenticated WebhookRequest JSON decoded successfully but its session_key field is empty, so the engine has no session to route the prompt or exec payload to.","triggerScenarios":"POSTing a JSON object without the session_key key, or with \"session_key\": \"\"; sending a differently-named field (e.g. sessionId or sessionID) that the struct ignores.","commonSituations":"Client payloads written against an older/renamed API schema; builders omitting optional-looking fields; Go/JS clients using camelCase while the server expects snake_case.","solutions":["Include a non-empty session_key in the JSON body (snake_case).","Use the exact session key shown by the engine/session listing (e.g. /history or /sessions output).","If key naming is the issue, rename sessionId → session_key in the payload.","Client-side, reject payloads with empty session_key before dispatching the request."],"exampleFix":"// before\n{\"prompt\": \"run tests\"}\n// after\n{\"session_key\": \"my-project:s1\", \"prompt\": \"run tests\"}","handlingStrategy":"validation","validationCode":"func checkWebhookReq(r WebhookRequest) error {\n    if strings.TrimSpace(r.SessionKey) == \"\" { return errors.New(\"session_key is required\") }\n    return nil\n}","typeGuard":"func hasSessionKey(p map[string]any) bool { k, ok := p[\"session_key\"].(string); return ok && strings.TrimSpace(k) != \"\" }","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), \"session_key is required\") {\n        return errors.New(\"add non-empty session_key (snake_case) to payload\")\n    }\n    return fmt.Errorf(\"webhook rejected: %s\", b)\n}","preventionTips":["Use snake_case field names exactly as WebhookRequest defines them","Validate session_key client-side before every send","Derive session keys from a single helper, not ad-hoc strings","Reuse keys returned by session listing APIs"],"tags":["webhook","validation","required-field","http-400"],"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"}