Skip to content

Serialize concurrent deliveries to one continuation token #624

Description

@ruiconti

What problem are you trying to solve?

eve does not serialize concurrent deliveries that target the same continuation token. The token is both the session resume address and an exclusive workflow hook. If two invocations race before either session is established, each tries to claim the same hook and one can fail with HookConflictError.

GitHub webhooks make this easy to hit. Creating a pull request with a label can deliver opened and labeled close together. If an application opts into both actions, the GitHub channel starts both dispatches independently with waitUntil. Both derive the same per-PR continuation token, then call send. The losing delivery is logged by sendGitHubTurn, while hook ownership rejects the conflicting run (source).

This race happens before there is a shared session. defineState cannot coordinate it because its state is scoped to one existing session. The current documentation therefore tells every channel or application to build an external per-session queue (docs).

Proposed solution

Provide framework-owned admission control for deliveries sharing a continuation token. The default policy should preserve accepted work rather than throwing or silently dropping it. A durable queue is the clearest contract, though an explicit configurable coalescing policy could also work for channels that want it.

Required behavior:

  • Two verified deliveries for the same continuation token can arrive concurrently before the session exists without producing HookConflictError for normal admission contention.
  • The framework creates or resumes one session and handles accepted deliveries in a documented order.
  • Coordination works across processes, regions, cold starts, and retries. An in-memory mutex is insufficient.
  • A crash after admission does not lose the delivery, and retrying the same delivery does not execute it twice when the caller supplies a stable delivery id or idempotency key.
  • The caller receives or can observe the disposition of every delivery: started, queued, coalesced, deduplicated, or rejected. Nothing disappears behind a swallowed log.
  • Different continuation tokens remain independently concurrent.
  • Authentication, channel state, message context, and attachment metadata stay attached to the correct queued delivery.
  • Admission contention does not become session.failed and does not post an unrecoverable error to the user.
  • Tests race two GitHub webhook deliveries for one PR from a cold state and prove deterministic completion. Add a separate test showing two PRs still run concurrently.

Alternatives considered

  • Use defineState as a lock. There is no session-scoped state to share during the creation race.
  • Require every application to provision Redis or another external queue. That can work, but it duplicates continuation-token ownership rules and retry semantics outside eve.
  • Catch and ignore HookConflictError. This avoids a crash by losing a valid delivery.

Related: #170 reported the same error class, but the author closed it after finding that their immediate reproduction came from a custom Telegram handler. The distributed creation race remains untracked.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions