{"record":{"id":"eee50714e4219de0","repo":"sipeed/picoclaw","slug":"invalid-session-webhook-type-for-chat-s","errorCode":null,"errorMessage":"invalid session_webhook type for chat %s","messagePattern":"invalid session_webhook type for chat (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/dingtalk/dingtalk.go","lineNumber":124,"sourceCode":"\tlogger.InfoC(\"dingtalk\", \"DingTalk channel stopped\")\n\treturn nil\n}\n\n// Send sends a message to DingTalk via the chatbot reply API\nfunc (c *DingTalkChannel) Send(ctx context.Context, msg bus.OutboundMessage) ([]string, error) {\n\tif !c.IsRunning() {\n\t\treturn nil, channels.ErrNotRunning\n\t}\n\n\t// Get session webhook from storage\n\tsessionWebhookRaw, ok := c.sessionWebhooks.Load(msg.ChatID)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"no session_webhook found for chat %s, cannot send message\", msg.ChatID)\n\t}\n\n\tsessionWebhook, ok := sessionWebhookRaw.(string)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"invalid session_webhook type for chat %s\", msg.ChatID)\n\t}\n\n\tlogger.DebugCF(\"dingtalk\", \"Sending message\", map[string]any{\n\t\t\"chat_id\": msg.ChatID,\n\t\t\"preview\": utils.Truncate(msg.Content, 100),\n\t})\n\n\t// Use the session webhook to send the reply\n\treturn nil, c.SendDirectReply(ctx, sessionWebhook, msg.Content)\n}\n\n// onChatBotMessageReceived implements the IChatBotMessageHandler function signature\n// This is called by the Stream SDK when a new message arrives\n// IChatBotMessageHandler is: func(c context.Context, data *chatbot.BotCallbackDataModel) ([]byte, error)\nfunc (c *DingTalkChannel) onChatBotMessageReceived(\n\tctx context.Context,\n\tdata *chatbot.BotCallbackDataModel,\n) ([]byte, error) {","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/dingtalk/dingtalk.go#L106-L142","documentation":"The value stored in sessionWebhooks for this ChatID failed the string type assertion. The only writer in the shipped code stores data.SessionWebhook (a string), so hitting this error means some other code path stored a non-string into the map — it is an internal invariant guard, not an expected runtime failure.","triggerScenarios":"A fork or extension stores a non-string (struct, []byte, url.URL) into sessionWebhooks under a chatID key; only the type assertion in Send triggers it.","commonSituations":"Custom modifications reusing sessionWebhooks for extra per-chat data; practically unreachable in the stock code.","solutions":["Ensure only onChatBotMessageReceived writes to sessionWebhooks, and only with the string data.SessionWebhook","Replace the sync.Map with a typed map[string]string guarded by a mutex so non-strings cannot be stored"],"exampleFix":"// before\nvar sessionWebhooks sync.Map // chatID -> anything\n\n// after\ntype webhookTable struct {\n    mu sync.RWMutex\n    m  map[string]string // chatID -> sessionWebhook\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func sessionWebhookFor(m *sync.Map, chatID string) (string, bool) {\n    v, ok := m.Load(chatID)\n    if !ok {\n        return \"\", false\n    }\n    s, ok := v.(string)\n    return s, ok\n}","tryCatchPattern":"if webhook, ok := sessionWebhookFor(&ch.sessionWebhooks, msg.ChatID); !ok {\n    return fmt.Errorf(\"cannot reply to chat %s\", msg.ChatID)\n} else {\n    return ch.SendDirectReply(ctx, webhook, msg.Content)\n}","preventionTips":["Restrict writes to sessionWebhooks to the inbound callback handler only","Prefer a typed map[string]string with a mutex over sync.Map for string-only data","Never reuse the webhook map for unrelated per-chat state"],"tags":["dingtalk","type-assertion","invariant"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}