Cap event capture requests at 100 events - #98
Merged
Conversation
The capture service rejects any batch over 100 events with
`400 {"error": "batch too large", "max_size": 100}`, but _flush drained
the whole backlog into a single send_batch call.
max_events already defaults to 100, so the single-producer path stays
under the cap. It is not a hard guarantee though: push() appends
unconditionally after its own flush,
if should_flush:
self._flush()
with self.lock:
self.events.append(event)
so producers piling up behind an in-flight send can drive the backlog
past max_events, and a caller may also raise max_events themselves.
Chunk each flush into requests of at most 100 events in both the sync
and async buffers, so the cap holds regardless of max_events or producer
concurrency. This matches schematic-go, schematic-java, and
schematic-csharp, which all bound the drain rather than the buffer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cbrady
approved these changes
Aug 19, 2026
bpapillon
approved these changes
Aug 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The capture service rejects any batch over 100 events with
400 {"error": "batch too large", "max_size": 100}(capture.go:73), but_flushdrained the whole backlog into a singlesend_batchcall.Severity, honestly
This is hardening rather than a live outage fix. Unlike
schematic-node(where the default buffer size was 1000, see schematic-node#162),max_eventshere already defaults to 100, so the ordinary single-producer path stays under the cap.It is not a hard guarantee though.
pushappends unconditionally after its own flush:That tail has no length check, so producers piling up behind an in-flight send can drive the backlog past
max_events. Exceeding 100 in a single request needs more than 100 concurrent pushers, which is narrow but reachable for a threaded web app. A caller who raisesmax_eventsthemselves is exposed directly.I tried to write a threaded regression test for the concurrent case and could not make it fail deterministically, so I did not ship a timing-dependent test that would flake in CI. The two tests here cover the deterministic property: a backlog larger than the cap must go out as multiple requests.
Changes
Chunk each flush into requests of at most 100 events, in both
EventBufferandAsyncEventBuffer, so the cap holds regardless ofmax_eventsor producer concurrency. Each chunk retries on its own, since retrying the whole drained set would resend chunks that had already been delivered.This matches
schematic-go,schematic-java, andschematic-csharp, which all bound the drain rather than relying on the buffer size to stay small.Testing
Two new tests in
tests/custom/test_event_buffer.py, both confirmed failing without the source change (a 250-event backlog goes out as one request instead of[100, 100, 50]):TestEventBufferBatchSizeCap::test_flush_splits_backlog_into_capped_requestsTestAsyncEventBufferBatchSizeCap::test_flush_splits_backlog_into_capped_requestsFull
tests/custom/suite passes: 161 passed.Context
Found while fixing a daily cron that tracked 113 companies through
schematic-nodeand lost the whole batch to a 400. I checked every SDK with an event buffer:schematic-go,schematic-java, andschematic-csharpalready hard-cap the drain and need no change;schematic-nodeandschematic-rubyhave their own PRs;schematic-jssends events one at a time and is unaffected.🤖 Generated with Claude Code