paperclipai/paperclip · warning
dropping batch ${batch.batchId} on terminal response (HTTP $
Error message
dropping batch ${batch.batchId} on terminal response (HTTP ${outcome.status}); ${batch.events.length} event(s) lost What it means
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.
Source
Thrown at packages/shared/src/telemetry/client.ts:268
// as effectively unbounded so `splitByBytes` routes it to the existing
// over-limit drop-and-log path instead of throwing out of the flush.
return Number.POSITIVE_INFINITY;
}
}
/**
* Sends one batch (its current `attempt`). On a retryable failure the EXACT
* same events + `batchId` are re-queued with capped, jittered backoff; on a
* terminal failure or after `maxAttempts` the batch is dropped-and-logged.
*/
private async attemptSend(batch: PendingBatch): Promise<void> {
const body = JSON.stringify(this.buildEnvelope(batch.events, batch.batchId));
const outcome = this.classifyOutcome(await this.postEnvelope(body));
if (outcome.kind === "ok") return;
if (outcome.kind === "terminal") {
this.warn(
`dropping batch ${batch.batchId} on terminal response (HTTP ${outcome.status}); ${batch.events.length} event(s) lost`,
);
return;
}
// Retryable (429/502/503/504 or network/timeout).
if (batch.attempt >= this.caps.backoff.maxAttempts) {
this.warn(
`dropping batch ${batch.batchId} after ${batch.attempt} attempt(s); ${batch.events.length} event(s) lost`,
);
return;
}
// Cap the delay at maxDelayMs. `computeBackoffMs` is already capped, but a
// server `Retry-After` hint is not — an out-of-range value could otherwise
// overflow the runtime timer range and be clamped by Node to a near-immediate
// timeout, causing rapid retries. The cap keeps every retry within the
// configured backoff ceiling.
const delayMs = Math.min(View on GitHub (pinned to 120ae5428f)
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.
Example fix
// before
telemetry: { url: 'https://collector.example/ingest' }
// after
telemetry: { url: 'https://collector.example/api/v2/telemetry' } Defensive patterns
Strategy: validation
Validate before calling
// Startup canary: one synthetic envelope must be accepted before telemetry matters.
const res = await fetch(collectorUrl, {
method: 'POST',
headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
body: JSON.stringify({ batchId: 'canary', events: [{ name: 'canary', ts: Date.now(), dims: {} }] }),
});
if (!res.ok) throw new Error(`Telemetry collector rejected canary: HTTP ${res.status}`); Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- dropping batch ${batch.batchId} after ${batch.attempt} attem
- dropping 1 event whose serialized envelope exceeds maxBodyBy
- dropping 1 event with a non-serializable dimension (circular
- pending-retry store full (bound=${bound}); evicted oldest ba
AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18).
Data as JSON: /api/errors/92eca9813cc0f0ec.
Report an issue: GitHub.