Skip to content

fix(events): GCS rate limits no longer fail the webhook - #1367

Draft
aivong-openhands wants to merge 4 commits into
mainfrom
fix/event-index-invalidation-rate-limit
Draft

aivong-openhands wants to merge 4 commits into
mainfrom
fix/event-index-invalidation-rate-limit

Conversation

@aivong-openhands

@aivong-openhands aivong-openhands commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

HUMAN:

  • A human has tested these changes.

AGENT:


Why

Each event that reached the event webhook renamed the conversation's index.json to index_stale.json. A busy conversation did this several times a second. Google Cloud Storage (GCS) allows about one change per second to one object, so it returned 429. The 429 failed the webhook. The agent-server then dropped events when its queue filled.

Summary

  • The webhook saves a batch of events and then invalidates the index once, not once per event.
  • A 429 from GCS, or SlowDown from S3, during invalidation no longer fails the webhook. The app retries the invalidation in the background, with exponential backoff and jitter for up to 30 seconds. While that retry is pending, saves for the same conversation skip invalidation, because the retry covers their events.

Issue Number

Fixes #1344

How to Test

tests/unit/app_server/test_filesystem_event_service.py checks that saving three events invalidates the index once. It also checks that a rate-limited invalidation is retried in the background, and that saves skip invalidation while the retry is pending. test_google_cloud_event_service.py and test_aws_event_service.py check that each store reports a rate limit.

test_google_cloud_event_service_rate_limit.py runs the GCS event service against fake-gcs-server in Docker. Its HTTP session answers 429 to a second change to one object within 0.2 seconds, as GCS does within one second. The test saves 10 batches while searching, and then finds every saved event.

Video/Screenshots

Rate-limited event storage integration test: Ten batches save 30 events while searches run. An injected rate limit triggers a background retry. The final search finds every saved event, with no missing or unexpected IDs. The test uses a local GCS emulator with a 0.2-second rate-limit window. It does not test production GCS, S3, or HTTP webhook responses.

rate-limited-event-storage-1367.mp4

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

While a retry is pending, index.json is missing the newest events, so a search can miss them for up to 30 seconds. If the retry still fails after 30 seconds, the app logs an error, and the index stays stale until the next save to that conversation. The retry state is per app process. A restart drops a pending retry, and several replicas can each rename the same index.

A search rebuilds index.json, and the next batch renames it again. So an open conversation page can still change the index more than once a second.

This PR was drafted by an AI agent on behalf of the user.


Enterprise server image for this PR:

ghcr.io/openhands/enterprise-server:sha-45a8441

…lerate rate limits

A busy conversation renamed index.json for every saved event, past the
per-object mutation limit of GCS. The 429 failed the webhook, and the
agent-server dropped events when its queue filled.

Fixes #1344
@github-actions github-actions Bot added the type: fix A bug fix label Nov 12, 2026
A rate-limited invalidation left index.json without the batch's events
until another event arrived. Retry it off the request path with
exponential backoff and jitter, one retry per conversation, and skip
invalidations while it is pending.
@datadog-official

This comment has been minimized.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  openhands/app_server/event
  aws_event_service.py 182
  event_service.py 67
  event_service_base.py 498-499
  google_cloud_event_service.py
  openhands/app_server/event_callback
  webhook_router.py
Project Total  

This report was generated by python-coverage-comment-action

Runs the GCS event service against fake-gcs-server, with a session that
answers 429 to a second change to one object within a short window, as GCS
does. Every saved event must still be found once the retries finish.
@aivong-openhands aivong-openhands changed the title fix(events): invalidate the event index once per webhook batch fix(events): fix(events): stop GCS rate limits from failing the event webhook Nov 12, 2026
@aivong-openhands aivong-openhands changed the title fix(events): fix(events): stop GCS rate limits from failing the event webhook fix(events): stop GCS rate limits from failing the event webhook Nov 12, 2026
@aivong-openhands aivong-openhands changed the title fix(events): stop GCS rate limits from failing the event webhook fix(events): GCS rate limits no longer fail the webhook Nov 12, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Event index invalidation hits GCS per-object rate limit, slowing the event webhook until the agent-server drops events

2 participants