sipeed/picoclaw · warning

matrix room ID is empty

Error message

matrix room ID is empty

What it means

Thrown by MatrixChannel.StartTyping when chatID is empty after trimming (pkg/channels/matrix/matrix.go:587). It returns a no-op stop function alongside the error, so callers that ignore the error still get a safe closure. Note the distinct behavior one branch above: when the channel is not running, StartTyping returns a no-op with NO error — only a blank room ID errors. Typing is cosmetic, so failures here should never break message flow.

Source

Thrown at pkg/channels/matrix/matrix.go:587

		}
	}

	if hasTrackedMsg {
		c.dismissTrackedToolFeedbackMessage(ctx, msg.ChatID, trackedMsgID)
	}

	return eventIDs, nil
}

// StartTyping implements channels.TypingCapable.
func (c *MatrixChannel) StartTyping(ctx context.Context, chatID string) (func(), error) {
	if !c.IsRunning() {
		return func() {}, nil
	}

	roomID := id.RoomID(strings.TrimSpace(chatID))
	if roomID == "" {
		return func() {}, fmt.Errorf("matrix room ID is empty")
	}

	session := newTypingSession()

	c.typingMu.Lock()
	if prev := c.typingSessions[chatID]; prev != nil {
		prev.stop()
	}
	c.typingSessions[chatID] = session
	c.typingMu.Unlock()

	parent := c.baseContext()
	go c.typingLoop(parent, roomID, session)

	var once sync.Once
	stop := func() {
		once.Do(func() {
			session.stop()

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Skip the typing call when chatID is blank — degrade silently rather than surfacing an error
  2. Fix the caller to pass the same trimmed room ID used for Send
  3. Treat any error from StartTyping as non-fatal: log at debug level and continue

Example fix

// before
 stop, err := ch.StartTyping(ctx, chatID)
 if err != nil { return err } // typing failure aborts the flow

// after
 stop, err := ch.StartTyping(ctx, chatID)
 if err != nil { logger.DebugC("matrix", "typing indicator skipped: " + err.Error()) }
 defer stop() // no-op closure is safe either way
Defensive patterns

Strategy: validation

Validate before calling

// typing is cosmetic: skip silently when unsendable
var stop func() = func() {}
if id := strings.TrimSpace(chatID); id != "" {
	s, err := matrixCh.StartTyping(ctx, id)
	if err == nil { stop = s }
}

Type guard

func canShowTyping(chatID string) bool {
	return strings.TrimSpace(chatID) != ""
}

Try / catch

stop, err := matrixCh.StartTyping(ctx, chatID)
if err != nil {
	log.Debug("typing indicator unavailable: ", err) // never propagate: UI degrades gracefully
}
defer stop() // safe: returns a no-op closure on every error path

Prevention

When it happens

Trigger: Calling StartTyping with an empty/whitespace chat ID from a UI/handler that lost its conversation binding; calling it before the chat ID is established (e.g. during onboarding when no inbound Matrix event exists yet).

Common situations: Frontend handlers invoking typing on selection of a not-yet-opened conversation; relays synthesizing typing notifications without a mapped room; races where typing is requested while a conversation is being created.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/d8a4edbc987d820f. Report an issue: GitHub.