{"record":{"id":"92eca9813cc0f0ec","repo":"paperclipai/paperclip","slug":"dropping-batch-batch-batchid-on-terminal-respon","errorCode":null,"errorMessage":"dropping batch ${batch.batchId} on terminal response (HTTP ${outcome.status}); ${batch.events.length} event(s) lost","messagePattern":"dropping batch (.+?) on terminal response \\(HTTP (.+?)\\); (.+?) event\\(s\\) lost","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/shared/src/telemetry/client.ts","lineNumber":268,"sourceCode":"      // as effectively unbounded so `splitByBytes` routes it to the existing\n      // over-limit drop-and-log path instead of throwing out of the flush.\n      return Number.POSITIVE_INFINITY;\n    }\n  }\n\n  /**\n   * Sends one batch (its current `attempt`). On a retryable failure the EXACT\n   * same events + `batchId` are re-queued with capped, jittered backoff; on a\n   * terminal failure or after `maxAttempts` the batch is dropped-and-logged.\n   */\n  private async attemptSend(batch: PendingBatch): Promise<void> {\n    const body = JSON.stringify(this.buildEnvelope(batch.events, batch.batchId));\n    const outcome = this.classifyOutcome(await this.postEnvelope(body));\n\n    if (outcome.kind === \"ok\") return;\n\n    if (outcome.kind === \"terminal\") {\n      this.warn(\n        `dropping batch ${batch.batchId} on terminal response (HTTP ${outcome.status}); ${batch.events.length} event(s) lost`,\n      );\n      return;\n    }\n\n    // Retryable (429/502/503/504 or network/timeout).\n    if (batch.attempt >= this.caps.backoff.maxAttempts) {\n      this.warn(\n        `dropping batch ${batch.batchId} after ${batch.attempt} attempt(s); ${batch.events.length} event(s) lost`,\n      );\n      return;\n    }\n    // Cap the delay at maxDelayMs. `computeBackoffMs` is already capped, but a\n    // server `Retry-After` hint is not — an out-of-range value could otherwise\n    // overflow the runtime timer range and be clamped by Node to a near-immediate\n    // timeout, causing rapid retries. The cap keeps every retry within the\n    // configured backoff ceiling.\n    const delayMs = Math.min(","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/packages/shared/src/telemetry/client.ts#L250-L286","documentation":"attemptSend classifies the HTTP outcome for each telemetry batch. A 'terminal' status — a non-retryable client error such as 400/401/403/404/410 — means retrying cannot help, so the batch is dropped and logged. Dropped batches are never replayed; telemetry is deliberately best-effort so it never blocks the host app.","triggerScenarios":"The collector rejects the POST with a client error: wrong endpoint path (404), invalid/expired auth token (401/403), envelope rejected as malformed (400), or a decommissioned ingestion route (410).","commonSituations":"Rotated telemetry API keys deployed to only one side; proxy or gateway rewriting URLs; collector upgraded to a new envelope schema; misconfigured base URL after migration.","solutions":["Read the HTTP status from the warning and fix the root cause: correct URL, valid token, or matching envelope schema.","Reproduce with curl: POST a minimal sample envelope to the collector with the same auth and inspect the response.","Align the client envelope schema with the collector version you run.","After the fix, confirm drop warnings stop — events lost before the fix are gone."],"exampleFix":"// before\ntelemetry: { url: 'https://collector.example/ingest' }\n\n// after\ntelemetry: { url: 'https://collector.example/api/v2/telemetry' }","handlingStrategy":"validation","validationCode":"// Startup canary: one synthetic envelope must be accepted before telemetry matters.\nconst res = await fetch(collectorUrl, {\n  method: 'POST',\n  headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },\n  body: JSON.stringify({ batchId: 'canary', events: [{ name: 'canary', ts: Date.now(), dims: {} }] }),\n});\nif (!res.ok) throw new Error(`Telemetry collector rejected canary: HTTP ${res.status}`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Contract-test the envelope schema against the collector version in CI.","Rotate telemetry tokens with overlap, updating client and collector together.","Alert on any 'dropping batch ... on terminal response' warn — dropped batches never replay.","Keep collector URL and auth in one deploy unit so they cannot drift."],"tags":["telemetry","http-4xx","batch-drop","client-error"],"backgroundTag":"http-client-error-4xx","analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}