sipeed/picoclaw · error
matrix message ID is empty
Error message
matrix message ID is empty
What it means
Thrown by MatrixChannel.EditMessage when messageID is empty after trimming (pkg/channels/matrix/matrix.go:649). The message ID must be a Matrix event ID (e.g. $abc:example.org) because it is passed to editContent.SetEdit to build the replacement event. Like the room-ID check, this is a plain error with no retry sentinel, so the manager will not retry it.
Source
Thrown at pkg/channels/matrix/matrix.go:649
resp, err := c.client.SendMessageEvent(ctx, roomID, event.EventMessage, &event.MessageEventContent{
MsgType: event.MsgNotice,
Body: text,
})
if err != nil {
return "", err
}
return resp.EventID.String(), nil
}
// EditMessage implements channels.MessageEditor.
func (c *MatrixChannel) EditMessage(ctx context.Context, chatID string, messageID string, content string) error {
roomID := id.RoomID(strings.TrimSpace(chatID))
if roomID == "" {
return fmt.Errorf("matrix room ID is empty")
}
if strings.TrimSpace(messageID) == "" {
return fmt.Errorf("matrix message ID is empty")
}
editContent := c.messageContent(content)
editContent.SetEdit(id.EventID(messageID))
_, err := c.client.SendMessageEvent(ctx, roomID, event.EventMessage, editContent)
return err
}
// DeleteMessage implements channels.MessageDeleter.
func (c *MatrixChannel) DeleteMessage(ctx context.Context, chatID string, messageID string) error {
roomID := id.RoomID(strings.TrimSpace(chatID))
if roomID == "" {
return fmt.Errorf("matrix room ID is empty")
}
eventID := id.EventID(strings.TrimSpace(messageID))
if eventID == "" {
return fmt.Errorf("matrix message ID is empty")View on GitHub (pinned to 49183d7e8d)
Solutions
- Store the Matrix event ID returned by Send/SendMedia (the []string result) keyed to your internal message ID, and translate before editing
- Reject edits targeting messages without a recorded Matrix event ID
- Do not feed external/foreign IDs into EditMessage — only event IDs this channel emitted
Example fix
// before
ch.EditMessage(ctx, roomID, internalMsgID, newText) // internal ID is not an event ID
// after
evID, ok := store.MatrixEventID(internalMsgID)
if !ok { return fmt.Errorf("message %s has no matrix event ID; not editable", internalMsgID) }
ch.EditMessage(ctx, roomID, evID, newText) Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(messageID) == "" {
return errors.New("edit rejected: message has no stored matrix event ID")
}
if err := matrixCh.EditMessage(ctx, chatID, messageID, newText); err != nil { /* handle */ } Type guard
func validMatrixEventID(messageID string) bool {
id := strings.TrimSpace(messageID)
return strings.HasPrefix(id, "$") && strings.Contains(id, ":")
} Try / catch
if err := matrixCh.EditMessage(ctx, chatID, messageID, newText); err != nil {
if strings.Contains(err.Error(), "message ID is empty") {
deadLetterEdit(messageID) // permanent data gap, not retryable
return nil
}
return err
} Prevention
- Store the event ID returned by Send/SendMedia keyed by your internal message ID
- Reject edits for messages you did not send through this channel
- Only pass Matrix event IDs ($...) — never external system IDs
When it happens
Trigger: Editing a message whose event ID was never recorded (message sent by another device/user, message predating persistence, or an external system's ID mistaken for a Matrix event ID); passing a provider-agnostic message ID from a multi-channel abstraction without per-channel ID translation.
Common situations: Multi-channel bots storing one canonical message ID instead of per-channel IDs; edits of messages the bot did not itself send through this channel; DB rows with empty event_id after partial writes.
Related errors
- matrix room ID is empty: %w
- no media store available: %w
- matrix room ID is empty
- empty matrix media URL
- invalid matrix media URL: %s
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/96150567cfdc2a0e.
Report an issue: GitHub.