Billionmail/BillionMail · warning

Chat not found or already stopped

Error message

Chat not found or already stopped

What it means

Stop() looks up the chat ID in the in-process ChatStatus map; if the ID is absent it means the chat was never started (or its entry was removed) or it was already stopped. The library throws this sentinel error to reject stopping a non-active chat instead of silently mutating the map.

Source

Thrown at core/internal/service/askai/chat.go:303

// RemoveChat removes a chat by its ID
// This function should handle the logic for removing a chat based on the provided chat ID
func RemoveChat(chatId string) error {
	chatPath := CHAT_CONFIG_PATH + "/" + chatId
	if !public.FileExists(chatPath) {
		return os.ErrNotExist
	}
	err := os.RemoveAll(chatPath)
	if err != nil {
		return err
	}
	return nil
}

func Stop(chatId string) error {
	// Implementation for stopping a chat
	// This function should handle the logic for stopping a chat based on the provided parameters
	if _, exists := ChatStatus[chatId]; !exists {
		return errors.New("Chat not found or already stopped")
	}
	ChatStatus[chatId] = false // Set chat status to inactive
	return nil
}

// GetLastUsage retrieves the last usage statistics for a chat by its ID
// This function should handle the logic for loading the last usage statistics of a chat based on the provided chat ID
func GetLastUsage(chatId string) ChatUsage {
	messages := GetMessages(chatId)
	if len(messages) == 0 {
		return ChatUsage{}
	}
	lastMessage := messages[len(messages)-1]
	return lastMessage.Usage
}

// GetHtml retrieves the HTML content of a chat by its ID
// This function should handle the logic for loading the HTML content of a chat based on the provided chat ID

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check chat existence first (e.g. a ChatExists helper or reading ChatStatus) before calling Stop
  2. Treat this error as an idempotent success in the caller: the desired end state (chat stopped) is already reached
  3. Ensure the same instance that started the chat handles Stop (sticky sessions/shared state) since ChatStatus is process-local
  4. Verify the chatId passed is the exact ID returned when the chat was created

Example fix

// before
if err := askai.Stop(chatId); err != nil {
    return err
}
// after
if err := askai.Stop(chatId); err != nil {
    if err.Error() == "Chat not found or already stopped" {
        return nil // already stopped; idempotent
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

func chatIsActive(chatId string) bool {
    askai.ChatStatusMu.Lock()
    defer askai.ChatStatusMu.Unlock()
    active, ok := askai.ChatStatus[chatId]
    return ok && active
}
// call Stop only if chatIsActive(chatId)

Try / catch

if err := askai.Stop(chatId); err != nil {
    if strings.Contains(err.Error(), "not found or already stopped") {
        return nil // idempotent: already in desired state
    }
    return err
}

Prevention

When it happens

Trigger: Calling askai.Stop(chatId) with an ID that was never registered in ChatStatus (chat never started), an ID that was already stopped (entry removed on completion), a stale ID after process restart (ChatStatus is in-memory only), or a typo'd/foreign chat ID.

Common situations: Double-clicking a 'stop' button so Stop fires twice; a frontend retrying Stop after the stream already finished; a server restart wiping the in-memory map while clients still hold old chat IDs; load-balanced deployments where the chat lives on another instance.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/c1df052eed4d8e14. Report an issue: GitHub.