{"record":{"id":"e79f42d0e20ea8f0","repo":"sipeed/picoclaw","slug":"chat-id-is-empty-w","errorCode":null,"errorMessage":"chat ID is empty: %w","messagePattern":"chat ID is empty: %w","errorType":"validation","errorClass":"ErrSendFailed","httpStatus":null,"severity":"error","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":161,"sourceCode":"\tc.mu.Unlock()\n\tif c.progress != nil {\n\t\tc.progress.StopAll()\n\t}\n\n\tc.SetRunning(false)\n\tlogger.InfoC(\"feishu\", \"Feishu channel stopped\")\n\treturn nil\n}\n\n// Send sends a message using Interactive Card format for markdown rendering.\n// Falls back to plain text message if card sending fails (e.g., table limit exceeded).\nfunc (c *FeishuChannel) Send(ctx context.Context, msg bus.OutboundMessage) ([]string, error) {\n\tif !c.IsRunning() {\n\t\treturn nil, channels.ErrNotRunning\n\t}\n\n\tif msg.ChatID == \"\" {\n\t\treturn nil, fmt.Errorf(\"chat ID is empty: %w\", channels.ErrSendFailed)\n\t}\n\n\tisToolFeedback := outboundMessageIsToolFeedback(msg)\n\tif isToolFeedback {\n\t\tif msgID, handled, err := c.progress.Update(ctx, msg.ChatID, msg.Content); handled {\n\t\t\tif err != nil {\n\t\t\t\t// Feishu can fall back to plain text for a previous progress\n\t\t\t\t// message, and those messages cannot be patched through the card\n\t\t\t\t// edit API. Drop the stale tracker and recreate the progress\n\t\t\t\t// message so later tool feedback is not blocked.\n\t\t\t\tc.resetTrackedToolFeedbackAfterEditFailure(ctx, msg.ChatID)\n\t\t\t} else {\n\t\t\t\treturn []string{msgID}, nil\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif msgIDs, handled := c.FinalizeToolFeedbackMessage(ctx, msg); handled {\n\t\t\treturn msgIDs, nil","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L143-L179","documentation":"FeishuChannel.Send rejects an OutboundMessage whose ChatID is empty, wrapping the permanent ErrSendFailed sentinel. Feishu's create-message API needs a receive_id (chat_id), so an empty one can never succeed - the guard prevents a wasted API round-trip. Not retryable.","triggerScenarios":"The bus delivers bus.OutboundMessage{ChatID: \"\"} to a running Feishu channel: upstream router failed to map the inbound event to a reply chat, or a programmatically built message skipped the ChatID field.","commonSituations":"Webhook/event handler did not extract chat_id from the Feishu event payload; reply routing table miss; test helper constructs OutboundMessage without a chat; broadcast fan-out path with an empty target.","solutions":["Find where the OutboundMessage was produced and ensure the inbound event's chat_id (event.message.chat_id) is copied into ChatID","Add a guard in the dispatcher/router: drop or log messages with an empty ChatID before they reach the channel","If it happens after adding a bot to a new chat type (p2p/group), verify the event parser handles that chat type"],"exampleFix":"// before\nbus.Publish(bus.OutboundMessage{Content: reply}) // ChatID forgotten\n\n// after\nchatID := ev.Message.ChatId\nif chatID == nil || *chatID == \"\" {\n\tlogger.Warn(\"no chat_id on event; dropping reply\")\n\treturn\n}\nbus.Publish(bus.OutboundMessage{ChatID: *chatID, Content: reply})","handlingStrategy":"validation","validationCode":"func outboundAddressable(msg bus.OutboundMessage) bool {\n\treturn strings.TrimSpace(msg.ChatID) != \"\"\n}","typeGuard":"null","tryCatchPattern":"if err := ch.Send(ctx, msg); err != nil {\n\tif errors.Is(err, channels.ErrSendFailed) && msg.ChatID == \"\" {\n\t\tlogger.Warn(\"dropped unaddressed outbound message\", \"origin\", msg.Origin)\n\t\treturn nil // routing bug upstream: do not retry\n\t}\n\treturn err\n}","preventionTips":["Validate ChatID at message construction, not at the channel boundary","Add a router invariant: every reply carries the inbound event's chat_id","Property-test the message builder so empty-ChatID messages cannot be published","Alert on this error - it always indicates an upstream bug, never transient conditions"],"tags":["feishu","validation","chat-id","send-failed","routing"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}