{"record":{"id":"d13517f825441bcd","repo":"chenhg5/cc-connect","slug":"create-ai-card-status-d-body-s","errorCode":null,"errorMessage":"create AI card: status=%d, body=%s","messagePattern":"create AI card: status=(.+?), body=(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/dingtalk/card.go","lineNumber":149,"sourceCode":"\t\treturn nil, fmt.Errorf(\"do request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\trespBody, _ := io.ReadAll(resp.Body)\n\n\tslog.Debug(\"dingtalk: createAndDeliver response\",\n\t\t\"status\", resp.StatusCode,\n\t\t\"body\", string(respBody))\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tslog.Error(\"dingtalk: create AI card failed\",\n\t\t\t\"status\", resp.StatusCode,\n\t\t\t\"body\", string(respBody))\n\t\t// Check if we should trigger degrade\n\t\tif resp.StatusCode == 403 || resp.StatusCode == 429 || resp.StatusCode >= 500 {\n\t\t\tp.activateCardDegrade(fmt.Sprintf(\"card.create:%d\", resp.StatusCode))\n\t\t}\n\t\treturn nil, fmt.Errorf(\"create AI card: status=%d, body=%s\", resp.StatusCode, string(respBody))\n\t}\n\n\t// Parse response to get cardInstanceId\n\tvar result struct {\n\t\tResult struct {\n\t\t\tCardInstanceId  string `json:\"cardInstanceId\"`\n\t\t\tOutTrackId      string `json:\"outTrackId\"`\n\t\t\tProcessQueryKey string `json:\"processQueryKey\"`\n\t\t} `json:\"result\"`\n\t\tCardInstanceId string `json:\"cardInstanceId\"`\n\t\tOutTrackId     string `json:\"outTrackId\"`\n\t}\n\tif err := json.Unmarshal(respBody, &result); err != nil {\n\t\tslog.Warn(\"dingtalk: failed to parse createAndDeliver response\", \"error\", err, \"body\", string(respBody))\n\t}\n\n\tcardInstanceId := result.Result.CardInstanceId\n\tif cardInstanceId == \"\" {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/card.go#L131-L167","documentation":"DingTalk's createAndDeliver endpoint returned a non-200 HTTP status when creating the AI streaming card (platform/dingtalk/card.go:149, createAICard). The error message embeds the status code and raw response body for diagnosis. Statuses 403, 429, and 5xx additionally trigger activateCardDegrade, which switches the platform to plain-text fallback messages.","triggerScenarios":"POST /v1.0/card/instances/createAndDeliver returns 400/401/403/404/429/5xx: invalid or expired access token (401), robot/template permission denied (403), unknown or inaccessible cardTemplateID (400/404), rate limiting (429), or DingTalk server-side errors (5xx).","commonSituations":"Wrong or insufficiently scoped cardTemplateID in config.toml; robot lacks the AI-card/template permission in DingTalk admin console; expired access token credentials (Client Key/Secret); exceeding DingTalk API QPS limits; DingTalk service incident.","solutions":["Read the embedded body in the error — DingTalk includes a JSON code/message (e.g. INVALID_ACCESS_TOKEN, Forbidden.AccessDenied) naming the cause","On 401: fix the app's Client Key/Secret in config or the token fetch flow; on 403: grant the robot the card template permission in the DingTalk open platform console","On 400/404: verify cardTemplateID is a valid template ID published under the same app as the robotCode","On 429: back off — the platform already activates degrade; reduce streaming update frequency via cardThrottleMs","On 5xx: retry later; check DingTalk status/announcements","Confirm robotCode matches the robot bound to the target conversation"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// verify token and template before creating cards\ntok, err := p.getAccessToken(context.Background())\nif err != nil || tok == \"\" {\n\tlog.Printf(\"dingtalk auth/config problem, expect card creation to fail\")\n}\nif p.cardTemplateID == \"\" {\n\tlog.Printf(\"cardTemplateID not configured; card creation will 400\")\n}","typeGuard":null,"tryCatchPattern":"card, err := p.CreateStreamingCard(ctx, msg)\nif err != nil {\n\tvar httpErr *HTTPStatusError // or parse status= from err\n\tif m := statusRe.FindStringSubmatch(err.Error()); m != nil && (m[1] == \"429\" || m[1] >= \"500\") {\n\t\t// platform already degraded; fall back to plain text replies\n\t} else if strings.Contains(m[1], \"401\") || strings.Contains(err.Error(), \"403\") {\n\t\t// fix credentials/permissions before retry\n\t}\n}","preventionTips":["Publish the card template under the same DingTalk app as the robot and copy the exact template ID into config","Grant the robot card/AI-card permissions in the open platform console","Rotate appSecret promptly after change and restart the daemon","Respect QPS: keep cardThrottleMs >= 1000 for chatty streams","Subscribe to DingTalk status announcements for 5xx windows"],"tags":["http","api","dingtalk","http-error","card"],"backgroundTag":"http-error-response","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"}