{"record":{"id":"c35166c095a971cb","repo":"chenhg5/cc-connect","slug":"slack-finalize-streaming-card-w","errorCode":null,"errorMessage":"slack: finalize streaming card: %w","messagePattern":"slack: finalize streaming card: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/slack/streaming_card.go","lineNumber":147,"sourceCode":"\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif c.failed {\n\t\treturn nil\n\t}\n\trendered := core.MarkdownToSlackMrkdwn(content)\n\tif rendered == \"\" || rendered == c.lastSent {\n\t\treturn nil\n\t}\n\t// Long reply + card already posted: chat.update would 413 with msg_too_long;\n\t// post a fresh message with the full content instead. This is the in-house\n\t// equivalent of \"see full reply below\" — the partial streaming card stays\n\t// visible above the new full-content message.\n\tif c.ts != \"\" && len(rendered) > slackUpdateMaxText {\n\t\tslog.Debug(\"slack: streaming card finalize switching to fresh postMessage (payload exceeds chat.update limit)\",\n\t\t\t\"size\", len(rendered), \"limit\", slackUpdateMaxText)\n\t\tif _, err := c.postFresh(ctx, rendered); err != nil {\n\t\t\tc.failed = true\n\t\t\treturn fmt.Errorf(\"slack: finalize streaming card: %w\", err)\n\t\t}\n\t\tc.lastSent = rendered\n\t\treturn nil\n\t}\n\tif err := c.render(ctx, rendered); err != nil {\n\t\tc.failed = true\n\t\treturn fmt.Errorf(\"slack: finalize streaming card: %w\", err)\n\t}\n\tc.lastSent = rendered\n\treturn nil\n}\n\n// Failed reports whether the card has entered a terminal failed state.\nfunc (c *slackStreamingCard) Failed() bool {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\treturn c.failed\n}","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/slack/streaming_card.go#L129-L165","documentation":"Finalize renders the full final content of a streaming card. When the rendered text exceeds slackUpdateMaxText, chat.update cannot edit the existing message, so Finalize falls back to a fresh postMessage via postFresh. This error wraps a failure of that fallback post — the final content could not be delivered by either path.","triggerScenarios":"Rendered content length > slackUpdateMaxText AND the fallback postFresh PostMessageContext call fails (channel invalid, bot removed, rate limit, network error, context cancelled).","commonSituations":"Very long agent responses overflowing the chat.update limit exactly when the bot lost channel access or Slack rate limiting kicked in; token scope revoked mid-session; network blip at end of a long turn.","solutions":["Inspect the wrapped inner error from postFresh (channel_not_found, rate_limited, invalid_auth) and fix accordingly","Re-add the bot to the channel or restore chat:write scope if auth/membership is the cause","Retry the finalize/turn delivery — the card is marked failed, so a re-send posts a fresh card","Trim the response before rendering if content routinely exceeds the limit"],"exampleFix":"// before\nif _, err := c.postFresh(ctx, rendered); err != nil {\n    c.failed = true\n    return fmt.Errorf(\"slack: finalize streaming card: %w\", err)\n}\n// after\nif _, err := c.postFresh(ctx, rendered); err != nil {\n    c.failed = true\n    slog.Error(\"slack: streaming card finalize fresh-post failed\", \"channel\", c.channel, \"size\", len(rendered), \"err\", err)\n    return fmt.Errorf(\"slack: finalize streaming card: %w\", err)\n}","handlingStrategy":"fallback","validationCode":"if len(rendered) > slackUpdateMaxText {\n    // oversize: verify we can still post before dropping the old preview\n    if _, err := c.client.PostMessageContext(ctx, c.channel, slack.MsgOptionText(\"\", false)); err != nil {\n        return fmt.Errorf(\"slack: cannot post fallback: %w\", err)\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := card.Finalize(ctx, fullContent); err != nil {\n    // send final content as a normal message instead\n    p.client.PostMessageContext(ctx, channel, slack.MsgOptionText(core.MarkdownToSlackMrkdwn(fullContent), false))\n}","preventionTips":["Anticipate oversize finals: the fresh-post fallback path is expected for long responses","Ensure chat:write and channel membership so postFresh succeeds","Cap response length in agent config if Slack limits are routinely hit","Retry delivery as a plain message if the card finalize fails"],"tags":["slack","streaming-card","message-post","payload-size"],"backgroundTag":"payload-too-large","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"}