{"record":{"id":"0202101ef2c5766e","repo":"bytebase/bytebase","slug":"100s","errorCode":null,"errorMessage":"%.100s","messagePattern":"%\\.100s","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/plugin/webhook/slack/slack.go","lineNumber":247,"sourceCode":"\t\tTimeout: webhook.Timeout,\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to POST webhook to %s\", context.URL)\n\t}\n\n\tb, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to read POST webhook response from %s\", context.URL)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn errors.Errorf(\"failed to POST webhook to %s, status code: %d, response body: %s\", context.URL, resp.StatusCode, b)\n\t}\n\n\tif string(b) != \"ok\" {\n\t\treturn errors.Errorf(\"%.100s\", string(b))\n\t}\n\n\treturn nil\n}\n\nfunc postDirectMessage(webhookCtx webhook.Context) bool {\n\tctx := context.Background()\n\tt := getSlackToken(webhookCtx.IMSetting)\n\tif t == \"\" {\n\t\tslog.Warn(\"failed to found valid slack im token\", slog.String(\"event\", webhookCtx.EventType))\n\t\treturn false\n\t}\n\tp := newProvider(t)\n\tsent := map[string]bool{}\n\tif err := common.Retry(ctx, func() error {\n\t\tvar errs error\n\t\tfor _, u := range webhookCtx.MentionEndUsers {\n\t\t\tif sent[u.Email] {","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/plugin/webhook/slack/slack.go#L229-L265","documentation":"Slack responded 200 OK but with a body other than the literal 'ok'. Classic Slack incoming webhooks return 'ok' on success; any other 200 body (often JSON with an error like 'channel_not_found' or 'user_not_found', or an HTML interstitial) is treated as failure and reported truncated to 100 chars via the %.100s verb.","triggerScenarios":"Calling Post where Slack returns 200 with an error payload — e.g. the webhook posts to a missing channel, or the URL is not an incoming-webhook endpoint and returns HTML/JSON instead of 'ok'.","commonSituations":"Channel configured for the webhook was deleted; URL points at a non-webhook Slack endpoint; Slack changes response format for newer webhook types; proxy returns a 200 HTML page (captive portal, SSO).","solutions":["Look at the truncated body in the error — it contains Slack's actual complaint (e.g. channel_not_found)","Recreate the incoming webhook and re-select a valid channel; update the configured URL","Confirm the URL is a genuine https://hooks.slack.com/services/... incoming webhook, not an API or SSO page","Check for proxies that rewrite responses (body would be HTML rather than JSON)"],"exampleFix":"// before\n// webhook channel deleted in Slack\n// after\n// edit the incoming webhook, choose an existing channel, save the new URL into settings","handlingStrategy":"try-catch","validationCode":"// preflight: post a test ping once at config time and require body == \"ok\"\nbody, code := tryPost(testURL, minimalPayload); if code != 200 || body != \"ok\" { return errors.New(\"webhook did not return ok: \" + body) }","typeGuard":null,"tryCatchPattern":"if err := webhook.Post(ctx); err != nil { body := err.Error() // <=100 chars of Slack's response\n  if strings.Contains(body, \"channel_not_found\") { /* reselect channel */ } }","preventionTips":["Validate the webhook with a test message immediately after configuration","Keep the webhook's subscribed channel valid; re-check after Slack workspace changes","Ensure no proxy rewrites 200 responses with HTML pages"],"tags":["slack","webhook","unexpected-response","api"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}