April 22, 2026

[Nachricht wird nach Anthropic 529-Wiederholung in falsches Thema gesendet] - Telegram Forum: Message Sent to Wrong Topic After Anthropic 529 Retry

Wenn die Anthropic-API 529 (überlastet) zurückgibt und OpenClaw die Anfrage erneut versucht, wird die Antwortnachricht ohne die korrekte message_thread_id gesendet, was dazu führt, dass Nachrichten aus Forum-Themen verschwinden.

🔍 Symptome

Primäres Symptom

Nach einem Anthropic API 529-Wiederholungszyklus wird die Telegram-Antwortnachricht erfolgreich gesendet (Telegram API gibt ok und eine gültige message_id zurück), aber die Nachricht erscheint nicht im erwarteten Forenthema.

Protokollbeweise

2026-03-03T11:19:05.208Z [agent/embedded] embedded run agent end: runId=561c9fa1 isError=true error=The AI service is temporarily overloaded.
2026-03-03T11:19:05.685Z [agent/embedded] embedded run agent end: runId=81dab484 isError=true error=The AI service is temporarily overloaded.
2026-03-03T11:24:34.955Z [telegram] sendMessage ok chat=-1003885638534 message=13832

Diagnostische Symptome

  • Kein thread not found-Fehler — Telegram hat die Thread-ID nicht abgelehnt
  • Kein message_thread_id in Protokollen — Die Debug-Ausgabe enthält nicht den Thread-Parameter, was die Diagnose erschwert
  • 5-minütige Lücke zwischen dem letzten 529-Fehler und sendMessage (zeigt Wiederholung mit Backoff)
  • Fehlende Sitzungseinträge — Keine Sitzungsdatensätze für topic:562 am Tag des Vorfalls
  • Stale-Socket-Neustart ereignete sich 7 Minuten nach sendMessage, jedoch nachdem die Nachricht bereits verloren ging

Benutzerverhalten

  • Die ursprüngliche Nachricht in Thema 562 erhält keine Antwort
  • Die Antwortnachrichten-ID existiert in der Telegram-Datenbank (bestätigt durch API-Antwort)
  • Die Nachricht ist sowohl im Zielthema ALS auch im Allgemeinen Thema unsichtbar
  • Die Nachricht scheint „gesendet" worden zu sein, ist aber tatsächlich verwaist

🧠 Ursache

Hauptfehler: Verlust des Thread-Kontexts während der Wiederholung

Die Grundursache ist ein Kontext-Propagierungsfehler in der Wiederholungspipeline. Wenn Anthropic HTTP 529 zurückgibt, tritt die folgende Sequenz auf:

  1. Nachricht empfangen — OpenClaw empfängt ein Telegram-Update mit message.chat.id, message.message_thread_id: 562 und Konversationskontext
  2. API-Aufruf eingeleitet — OpenClaw ruft Anthropics Messages-API mit dem Konversationskontext auf
  3. 529-Fehler empfangen — Anthropic gibt HTTP 529: The AI service is temporarily overloaded zurück
  4. Wiederholung ausgelöst — OpenClaws Wiederholungsmechanismus (mit Backoff) versucht den API-Aufruf erneut
  5. Kontextbeschädigung — Während des Wiederholungszyklus wird die message_thread_id aus dem ursprünglichen Telegram-Update nicht an den sendMessage-Aufruf weitergegeben

Architektonisches Problem: Sitzungszustand vs. Inline-Kontext

OpenClaw verwendet eine sitzungsbasierte Architektur, bei der der Konversationskontext in einem Sitzungsspeicher gespeichert wird. Der kritische Fehler tritt auf, wenn:

// Vereinfachter Ablauf zeigt den Fehlerpunkt
async function handleUpdate(update) {
  const threadId = update.message.message_thread_id; // 562 - hier erfasst
  
  // Beim ersten Versuch wird Sitzung erstellt/geladen
  const session = await sessionStore.get(update.chat.id);
  session.threadId = threadId;
  await sessionStore.set(update.chat.id, session);
  
  // ... API-Aufruf gemacht, 529 empfangen ...
  
  // Bei Wiederholung kann Sitzungszustand veraltet oder überschrieben sein
  const retrySession = await sessionStore.get(update.chat.id);
  // retrySession.threadId könnte undefined, null oder falsch sein
  
  // sendMessage ohne korrekte thread_id aufgerufen
  await telegram.sendMessage({
    chat_id: update.chat.id,
    text: response,
    message_thread_id: retrySession.threadId // FEHLER: undefined!
  });
}

Beitragende Faktoren

  • Wiederholungsverzögerung erzeugt Racebedingung — Das 5-minütige Backoff zwischen dem 529 und der Wiederholung ermöglicht, dass der Sitzungszustand gelöscht, beschädigt oder überschrieben wird
  • Keine thread_id in sendMessage-Protokollen — Die Debug-Anweisung enthält nicht message_thread_id, was eine frühzeitige Erkennung verhindert:
    // Aktuelles (fehlerhaftes) Protokollformat
    console.log(`sendMessage ok chat=${chatId} message=${messageId}`);
    

    // Fehlt: message_thread_id=${threadId || ‘undefined’}

  • Sitzungsspeicher TTL/Ablauf — Wenn Sitzungen während des Wiederholungsfensters ablaufen, geht der Thread-Kontext verloren
  • Gleichzeitige Nachrichtenverarbeitung — Wenn während der Wiederholung eine andere Nachricht in einem anderen Thema eingeht, kann der Sitzungszustand überschrieben werden

Warum Kein Fehler Ausgelöst Wird

Telegram akzeptiert die Nachricht ohne message_thread_id, weil sie standardmäßig an das „Hauptthema" (thread_id: 0) gesendet wird. Das Hauptthema-Verhalten in Forengruppen variiert jedoch je nach Client und Telegram-Version — einige Clients verbergen diese Nachrichten vollständig, wenn der ursprüngliche Kontext aus einem anderen Thread stammte.

🛠️ Schritt-für-Schritt-Lösung

Schritt 1: Sicherstellen, dass Thread-ID an sendMessage übergeben wird

Ändern Sie den Telegram-Adapter, um immer message_thread_id in das sendMessage-Payload aufzunehmen, wobei standardmäßig der Wert aus der eingehenden Nachricht verwendet wird, wenn er nicht aus dem Sitzungszustand verfügbar ist:

// VORHER (fehlerhafte Implementierung)
async sendMessage(chatId, text, options = {}) {
  const payload = {
    chat_id: chatId,
    text: text,
    // message_thread_id nicht eingeschlossen - Standard auf 0/undefined
    ...options
  };
  
  const result = await this.telegram.sendMessage(payload);
  console.log(`sendMessage ok chat=${chatId} message=${result.message_id}`);
  return result;
}

// NACHHER (korrigierte Implementierung)
async sendMessage(chatId, text, options = {}) {
  const payload = {
    chat_id: chatId,
    text: text,
    parse_mode: 'Markdown',
    ...options
    // message_thread_id MUSS explizit in options übergeben werden
    // Kein Standard auf undefined - Aufrufer ist verantwortlich
  };
  
  // Erweiterte Protokollierung mit thread_id
  console.log(`sendMessage ok chat=${chatId} thread=${payload.message_thread_id ?? 'main'} message=${result.message_id}`);
  return result;
}

Schritt 2: Thread-ID durch Wiederholungszyklen hindurch bewahren

Stellen Sie sicher, dass die thread_id aus der eingehenden Nachricht bis zum sendMessage-Aufruf weitergegeben wird, unabhängig vom Sitzungszustand:

// VORHER (sitzungsabhängig)
async handleMessage(ctx, messageText) {
  const session = await this.getSession(ctx.chat.id);
  const response = await this.callAIWithRetry(messageText, session.context);
  
  // Thread-ID aus Sitzung - kann nach Wiederholung veraltet sein
  await this.telegram.sendMessage(ctx.chat.id, response, {
    message_thread_id: session.threadId
  });
}

// NACHHER (eingehender Nachrichtkontext bewahrt)
async handleMessage(ctx, messageText) {
  // thread_id aus der TATSÄCHLICHEN eingehenden Nachricht erfassen, nicht aus Sitzung
  const originalThreadId = ctx.message.message_thread_id;
  
  const session = await this.getSession(ctx.chat.id);
  const response = await this.callAIWithRetry(messageText, session.context);
  
  // Immer die thread_id der Originalnachricht verwenden
  await this.telegram.sendMessage(ctx.chat.id, response, {
    message_thread_id: originalThreadId
  });
}

Schritt 3: Thread-ID zu allen Send-Operationen hinzufügen

Stellen Sie sicher, dass alle Telegram-Sendemethoden thread_id einschließen, wenn sie in einem Forum-Kontext arbeiten:

// Hilfsfunktion zum Erstellen von Send-Optionen mit Thread-Kontext
function buildSendOptions(originalMessage, overrides = {}) {
  const options = { ...overrides };
  
  // Immer thread_id einschließen, wenn Originalnachricht eine hatte
  if (originalMessage.message_thread_id) {
    options.message_thread_id = originalMessage.message_thread_id;
  }
  
  return options;
}

// Verwendung
const sendOptions = buildSendOptions(ctx.message);
await this.telegram.sendMessage(ctx.chat.id, text, sendOptions);
await this.telegram.editMessageReplyMarkup(ctx.chat.id, messageId, sendOptions);

Schritt 4: Wiederholungsprotokollierung verbessern

Protokollieren Sie die thread_id bei jedem Wiederholungsversuch zur Unterstützung der Fehlersuche:

async callAIWithRetry(message, context, threadId) {
  const maxRetries = 3;
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    console.log(`[retry] attempt=${attempt} thread=${threadId} maxRetries=${maxRetries}`);
    
    try {
      return await this.anthropic.messages.create({
        model: 'claude-3-5-sonnet-20241022',
        max_tokens: 1024,
        messages: [{ role: 'user', content: message }],
        extra_headers: { 'anthropic-dangerous-direct-browser-access': 'true' }
      });
    } catch (error) {
      lastError = error;
      
      if (error.status === 529) {
        console.log(`[retry] received 529 (overloaded) thread=${threadId}`);
        const backoffMs = Math.min(1000 * Math.pow(2, attempt), 30000);
        console.log(`[retry] backing off for ${backoffMs}ms thread=${threadId}`);
        await sleep(backoffMs);
      } else if (error.status === 529) {
        throw error; // Nicht wiederholbarer Fehler
      }
    }
  }
  
  throw lastError;
}

Schritt 5: Sitzungszustandssperrung (Erweitert)

Verhindern Sie Sitzungszustandsbeschädigung während langer Wiederholungszyklen:

// Optimistische Sperrung für Sitzungsaktualisierungen verwenden
async updateSession(chatId, updater, threadId) {
  const maxAttempts = 3;
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const session = await this.sessionStore.get(chatId);
    const updated = updater(session);
    
    // thread_id über Sitzungsaktualisierungen hinweg bewahren
    updated.threadId = session.threadId || threadId;
    
    try {
      await this.sessionStore.set(chatId, updated);
      return updated;
    } catch (conflictError) {
      if (attempt === maxAttempts) throw conflictError;
      await sleep(50 * attempt); // Kurzes Backoff
    }
  }
}

🧪 Verifizierung

Schritt 1: Das 529-Szenario reproduzieren

Simulieren Sie einen Anthropic 529-Fehler, um den Wiederholungspfad auszulösen:

# Using curl to simulate the Telegram update webhook
curl -X POST http://localhost:3000/webhook/telegram \
  -H "Content-Type: application/json" \
  -d '{
    "update_id": 123456789,
    "message": {
      "message_id": 100,
      "chat": { "id": -1003885638534, "type": "supergroup" },
      "message_thread_id": 562,
      "text": "Test message for 529 retry scenario"
    }
  }'

Schritt 2: sendMessage-Protokollausgabe verifizieren

Nach Anwendung der Korrektur bestätigen, dass das Protokoll thread enthält:

# Expected log output AFTER fix
2026-03-03T11:24:34.955Z [telegram] sendMessage ok chat=-1003885638534 thread=562 message=13832

# Should NOT see (before fix):
2026-03-03T11:24:34.955Z [telegram] sendMessage ok chat=-1003885638534 message=13832

Schritt 3: Überprüfen, dass Nachricht im korrekten Thema erscheint

# Use Telegram's getMessage to verify thread placement
curl "https://api.telegram.org/bot${BOT_TOKEN}/getMessage?chat_id=-1003885638534&message_id=13832"

# Expected response includes:
{
  "ok": true,
  "result": {
    "message_id": 13832,
    "chat": { "id": -1003885638534, "type": "supergroup" },
    "message_thread_id": 562,  // <-- Must match original
    "text": "..."
  }
}

Schritt 4: Überprüfen, dass Sitzung Thread-ID enthält

# Check session store for correct thread_id
# (depends on session store implementation)

# If using Redis:
redis-cli GET "session:-1003885638534"
# Should contain: {"threadId": 562, "..."}

# If using file-based:
cat sessions/-1003885638534.json
# Should contain: {"threadId": 562, "..."}

Schritt 5: Komponententest für Thread-Kontext-Bewahrung

describe('Telegram forum thread context', () => {
  it('should preserve message_thread_id through 529 retry', async () => {
    const ctx = createMockContext({
      chatId: -1003885638534,
      messageId: 100,
      threadId: 562,
      text: 'Test message'
    });
    
    // Mock Anthropic to return 529 twice, then success
    aiClient.messages.create
      .mockRejectedValueOnce({ status: 529, message: 'overloaded' })
      .mockRejectedValueOnce({ status: 529, message: 'overloaded' })
      .mockResolvedValueOnce({ content: [{ type: 'text', text: 'Response' }] });
    
    await handler.handleUpdate(ctx);
    
    // Verify sendMessage was called with correct thread_id
    expect(telegramAdapter.sendMessage).toHaveBeenCalledWith(
      -1003885638534,
      expect.any(String),
      expect.objectContaining({ message_thread_id: 562 })
    );
  });
});

Schritt 6: Integrationstest mit Telegram-Testumgebung

# Use Telegram's test environment or a private bot
# Send message in a forum topic, trigger 529 error, verify reply location

# 1. Set BOT_TOKEN to test bot
export BOT_TOKEN="test_bot_token"

# 2. Run openclaw with logging
OPENCLAW_LOG_LEVEL=debug npm start

# 3. Monitor for:
# - sendMessage logs with thread=562
# - Message appears in correct topic
# - No "lost" messages

⚠️ Häufige Fehler

Umgebungsspezifische Fallen

  • Docker-Container-Neustart löscht Sitzungszustand

    Wenn OpenClaw in Docker läuft und der Container während eines langen Wiederholungszyklus neu startet, geht der Sitzungszustand (einschließlich thread_id) verloren. Stellen Sie sicher, dass der Sitzungsspeicher externalisiert ist (Redis) anstatt im Speicher.

    # Docker Compose configuration - externalize session storage
    services:
      openclaw:
        image: openclaw:latest
        environment:
          - SESSION_STORE=redis
          - REDIS_URL=redis://redis:6379
      redis:
        image: redis:7-alpine
        volumes:
          - redis-data:/data
    volumes:
      redis-data:
    
  • macOS-Dateideskriptor-Limits

    Bei Verwendung sitzungsbasierter Dateien auf macOS kann das Standard-ulimit zu Sitzungsschreibfehlern unter hoher Last führen:

    # Check current limit
    ulimit -n
    # Increase if below 1024
    ulimit -n 65535
    
  • Windows-Pfadtrennzeichen in Sitzungsschlüsseln

    Sitzungsspeicher-Dateipfade können auf Windows Probleme mit Sonderzeichen in Chat-IDs haben (führendes Minuszeichen):

    # Use encodeURIComponent for chat IDs in file paths
    const sessionPath = path.join(
      sessionDir,
      `${encodeURIComponent(String(chatId))}.json`
    );
    

Konfigurationsfallen

  • Forum-Unterstützung in BotFather nicht aktiviert

    Telegram-Bots erfordern explizite Gruppenmitgliedschaftsberechtigung für Forenthemen:

    # Required BotFather commands:
    # /setprivacy -> Disable (for forum access)
    # /setjoingroup -> Yes
    # /setforums -> Enable (if available)
    
  • Missmatched Sitzungs-TTL vs. Wiederholungs-Backoff

    Wenn die Sitzungs-TTL kürzer ist als der Wiederholungs-Backoff-Zeitraum, läuft der Thread-Kontext ab:

    # Example: 5-minute TTL but 5-minute backoff = guaranteed context loss
    SESSION_TTL=300000  # 5 minutes in ms
    MAX_RETRY_BACKOFF=300000  # Should be less than TTL
    
  • reply_to_message_id ohne message_thread_id verwenden

    Selbst mit korrekt gesetztem reply_to_message_id führt das Weglassen von message_thread_id dazu, dass Forennachrichten verloren gehen:

    # BROKEN: reply without thread context
    {
      chat_id: -1003885638534,
      text: "Reply text",
      reply_to_message_id: 100
      // Missing: message_thread_id: 562
    }
    

    CORRECT: include both

    { chat_id: -1003885638534, text: “Reply text”, reply_to_message_id: 100, message_thread_id: 562 }

Code-Ebene-Fallen

  • thread_id als String vs. Zahl speichern

    Die Telegram-API akzeptiert beides, aber das Mischen von Typen verursacht Probleme:

    # Telegram API is flexible but some clients expect integer
    const threadId = parseInt(message.message_thread_id, 10);
    # Or ensure consistent type
    const threadId = String(message.message_thread_id);
    
  • Sitzung in gleichzeitigen Handlern überschreiben

    Wenn mehrere Nachrichten gleichzeitig für denselben Chat eingehen, können Sitzungsschreibvorgänge ein Rennen eingehen:

    // PROBLEMATIC: Read-modify-write without atomicity
    const session = await getSession(chatId);
    session.threadId = threadId;  // Read
    await saveSession(chatId, session);  // Write - another request may overwrite
    

    // FIXED: Use atomic operations or locking await updateSessionAtomic(chatId, (s) => { s.threadId = threadId; return s; });

  • Async/Await-Racebedingungen in Wiederholungshandlern

    Die Callback/Promise-Kette kann den Kontext verlieren:

    // PROBLEMATIC
    function handleMessage(ctx) {
      let threadId = ctx.message.message_thread_id;
    

    retry(3, () => ai.call()).then(response => { // ’this’ and ’threadId’ may be out of scope or stale sendMessage(ctx.chat.id, response, { threadId }); });

    // New message arrives, ’threadId’ is overwritten threadId = newMessage.message_thread_id; }

    // FIXED: Capture context in closure function handleMessage(ctx) { const threadId = ctx.message.message_thread_id; // Capture immediately

    retry(3, () => ai.call()).then(response => { sendMessage(ctx.chat.id, response, { message_thread_id: threadId }); }); }

🔗 Zugehörige Fehler

  • HTTP 529: The AI service is temporarily overloaded

    Der Rate-Limiting-Fehler von Anthropic, der die Wiederholungssequenz auslöst. Der 529-Fehler ist das auslösende Ereignis für den Fehler.

  • stale-socket

    Gateway-Gesundheitsmonitor-Neustart, der 7 Minuten nach der verlorenen Nachricht auftrat. Nicht direkt verwandt, deutet aber auf zugrunde liegende Verbindungsinstabilität hin, die Wiederholungsprobleme verschlimmern kann.

  • thread not found (in diesem Fall nicht gesehen)
    Telegram-API-Fehler, wenn message_thread_id auf ein nicht existierendes Thema verweist. Das Fehlen dieses Fehlers bestätigt, dass Telegram eine gültige Thread-ID erhalten hat (oder gar keine Thread-ID).

  • Sitzungsablauf während langer laufender Operationen

    Bezogen auf ein GitHub-Problem, bei dem die Sitzungs-TTL kürzer ist als die Operationsdauer, was zum Kontextverlust führt. Ähnliche Grundursache wie der thread_id-Verlust.

  • Webhook-Zustellungsfehler mit fehlendem Thread-Kontext

    Verwandtes Problem, bei dem Telegram-Webhook-Updates ohne message_thread_id für Forennachrichten eingehen, was Routing-Fehler verursacht.

  • context.lengthExceeded Anthropic-Fehler

    Wenn der Konversationskontext während der Wiederholungen zu groß wird, gibt Anthropic diesen Fehler zurück. Kann Thread-Kontext-Probleme verschlimmern, wenn die Fehlerbehandlung den Zustand verliert.

  • Racebedingung bei gleichzeitigen Telegram-Updates

    Wenn mehrere Updates gleichzeitig für denselben Chat eingehen, kann der Sitzungszustand überschrieben werden, wobei thread_id verloren geht. Dieselbe architektonische Anfälligkeit wie bei diesem Fehler.

  • Nachricht an falschen Chat nach Bot-Token-Aktualisierung gesendet

    Verwandtes Sitzungs-/Kontextverlust-Szenario, bei dem Bot-Konfigurationsänderungen während des Betriebs dazu führen, dass Nachrichten falsch weitergeleitet werden.

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.