Skip to content

fix(basket): preserve retries after interrupted admission - #586

Merged
izadoesdev merged 4 commits into
stagingfrom
codex/basket-recovery-staging
Aug 2, 2026
Merged

fix(basket): preserve retries after interrupted admission#586
izadoesdev merged 4 commits into
stagingfrom
codex/basket-recovery-staging

Conversation

@izadoesdev

@izadoesdev izadoesdev commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Shorten the Redis pending-dedup lease to 30 seconds while preserving 1d/2d delivered-event retention.
  • Return a retryable 503 when an unexpected /batch item-preparation failure occurs, before any batch is acknowledged.
  • Centralize the delivery-unavailable error so all Basket admission paths use the same retry contract.

Why

A process crash or failed release could leave a normal pending reservation for two minutes and suppress client retries. Separately, a transient preparation error could yield an HTTP 200 partial response even though the event was never admitted. Both behaviors risked silent event loss.

Validation

  • cd apps/basket && bun run test — 598 passed
  • bun run check-types --filter=@databuddy/basket
  • bun run lint
  • bun run format

No migrations or PostgreSQL delivery state are included.


Summary by cubic

Prevents silent event loss by preserving client retries after interrupted admission, reserving events only at delivery handoff, and returning a structured 503 when preparation fails to avoid partial acknowledgements.

  • Bug Fixes
    • Shortened Redis pending-dedup lease from 120s to 30s while keeping delivered-event retention at 1–2 days.
    • Reserved events only after enrichment completes (track and outgoing link) to avoid suppressing retries on preparation errors.
    • Centralized deliveryUnavailable and returned a retryable 503 for unexpected /batch preparation failures before any item is acknowledged.
    • Kept malformed batch items as schema failures without dropping valid events; extended tests for stale-pending recovery, 503 behavior, and schema edge cases.

Written for commit 49ca094. Summary will update on new commits.

Review in cubic

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard Ready Ready Preview Aug 2, 2026 2:01pm
databuddy-status Ready Ready Preview Aug 2, 2026 2:01pm
documentation Ready Ready Preview Aug 2, 2026 2:01pm

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a3208b6f-3993-4233-8913-4a97628456e1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor

cursor Bot commented Aug 2, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@dosubot

dosubot Bot commented Aug 2, 2026

Copy link
Copy Markdown

📄 Knowledge review

Dosu skipped reviewing this PR because your organization has used its 200 included credits for the month. Your usage will reset on 2026-09-01. To have Dosu review this PR before then, ask your organization admin to upgrade to a pro account.


Leave Feedback Ask Dosu about Databuddy Add Dosu to your team

@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR centralizes Basket's retryable delivery error, changes unexpected batch-preparation failures to return 503 before publishing, and reduces pending Redis dedup reservations from 120 to 30 seconds.

  • Preserves one- and two-day retention for successfully delivered event IDs.
  • Adds integration and unit coverage for batch preparation failures, structured errors, and stale pending leases.
  • The shortened lease can expire before an in-flight admission finishes, allowing concurrent retries to publish duplicates.

Confidence Score: 4/5

The pending lease race should be fixed before merging because a slow admission followed by a retry can publish the same event twice.

The new 30-second Redis TTL is not coupled to or renewed for the full reservation-to-commit interval, so ownership can expire while enrichment or delivery remains active and a second owner can publish the same event.

Files Needing Attention: apps/basket/src/lib/security.ts and apps/basket/src/lib/event-service.ts

Important Files Changed

Filename Overview
apps/basket/src/lib/security.ts Reduces pending reservation lifetime to 30 seconds, exposing in-flight admissions to lease expiry and duplicate publication.
apps/basket/src/lib/event-service.ts Moves the shared delivery-unavailable error import while retaining reservation, publication, and settlement behavior.
apps/basket/src/lib/structured-errors.ts Adds a shared retryable 503 constructor and a correctly tested classifier.
apps/basket/src/routes/basket.ts Converts unexpected preparation failures into a whole-request 503 before batch reservations or publishing begin.
apps/basket/src/routes/integration.test.ts Verifies that a preparation failure returns the retryable structured error without invoking batch insertion.

Sequence Diagram

sequenceDiagram
  participant A as Original admission
  participant R as Redis dedup
  participant P as Producer
  participant B as Client retry
  A->>R: Reserve event ID (30s pending lease)
  A->>A: Enrich and prepare event
  A->>P: Begin delivery
  R-->>R: Pending lease expires
  B->>R: Reserve same event ID
  B->>P: Deliver retry
  P-->>B: Retry published
  P-->>A: Original published
  A->>R: Mark delivered with old token
  R-->>A: Token mismatch
  Note over P: Same event may be published twice
Loading

Reviews (1): Last reviewed commit: "fix(basket): preserve retries after inte..." | Re-trigger Greptile

// Pending ownership only guards an in-flight admission. It must outlive the
// 20-second Railway shutdown budget while still letting a crashed owner expire
// before client retries are suppressed for minutes.
const PENDING_DEDUP_TTL = 30;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pending lease expires during admission

If enrichment or producer delivery remains in flight for more than 30 seconds, the pending Redis key expires before the original owner marks it delivered. A retry can then reserve the same event ID and publish it again, causing duplicate analytics rows while the original owner's token no longer matches.

Knowledge Base Used: Basket Ingestion Flow

@unkey-deploy

unkey-deploy Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Unkey Deploy

Name Status Preview Inspect Updated (UTC)
links (preview) Ready Visit Preview Inspect Aug 2, 2026 2:00pm

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant