{"record":{"id":"e2fdc11ac7f52c12","repo":"nanocoai/nanoclaw","slug":"message-marked-as-failed-after-max-retries","errorCode":null,"errorMessage":"Message marked as failed after max retries","messagePattern":"Message marked as failed after max retries","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/host-sweep.ts","lineNumber":324,"sourceCode":"  inDb: InboundMailbox,\n  outDb: OutboundMailbox,\n  session: Session,\n  reason: string,\n): void {\n  const claims = outDb.getProcessingClaims();\n  const now = Date.now();\n  for (const { messageId } of claims) {\n    const msg = inDb.getMessageForRetry(messageId, 'pending');\n    if (!msg) continue;\n\n    // Already rescheduled for a future retry — don't bump tries again. The\n    // wake path (sweep step 2) will fire when process_after elapses and a\n    // fresh container will clean the orphan claim on startup.\n    if (msg.processAfter && Date.parse(msg.processAfter) > now) continue;\n\n    if (msg.tries >= MAX_TRIES) {\n      inDb.markMessageFailed(msg.id);\n      log.warn('Message marked as failed after max retries', {\n        messageId: msg.id,\n        sessionId: session.id,\n        reason,\n      });\n    } else {\n      const backoffMs = BACKOFF_BASE_MS * Math.pow(2, msg.tries);\n      const backoffSec = Math.floor(backoffMs / 1000);\n      inDb.retryWithBackoff(msg.id, backoffSec);\n      log.info('Reset stale message with backoff', {\n        messageId: msg.id,\n        tries: msg.tries,\n        backoffMs,\n        reason,\n      });\n    }\n  }\n\n  // Drop the orphan 'processing' rows. Without this, the next sweep tick","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/host-sweep.ts#L306-L342","documentation":"A message that kept getting stuck and retried has exhausted MAX_TRIES, so the sweep marks it failed in inbound.db instead of re-queueing again. After this warn, the message will not be delivered — the agent never processes it unless manually requeued.","triggerScenarios":"resetStuckProcessingRows sees msg.tries >= MAX_TRIES for a stuck/claimed message whose process_after has elapsed; markMessageFailed(msg.id) is called with the reset reason (e.g. 'claim-stuck', 'absolute-ceiling').","commonSituations":"Repeated container crashes on the same message (poison message); provider auth failure causing every retry to fail; persistent container instability making each attempt die mid-processing.","solutions":["Inspect the message content for something that crashes the agent (huge attachment, weird format) — a poison message","Fix the underlying per-attempt failure (container crash, provider error) seen in logs before the retries exhausted","Manually reset the message's failed status / tries in inbound.db to retry once fixed","If failures are environmental flakiness, raise MAX_TRIES or backoff tuning"],"exampleFix":"-- retry a failed message once root cause is fixed (session inbound.db)\nUPDATE messages_in SET status='pending', tries=0 WHERE id='<messageId>';","handlingStrategy":"validation","validationCode":"// inbound.db check before it hits MAX_TRIES\nconst tries = await inDb.getMessageTries(msgId);\nif (tries >= MAX_TRIES - 1) {\n  // pull the message aside for manual inspection instead of letting it fail silently\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Alert on retry counts climbing toward MAX_TRIES","Quarantine messages that repeatedly crash the agent rather than letting them loop","Fix per-attempt root causes (auth, crashes) quickly — retries burn fast under backoff"],"tags":["retries","max-retries","poison-message","sweep"],"backgroundTag":"message-max-retries-exceeded","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}