Skip to content

fix(checkout-api): resolve checkout-race-condition#13

Open
Spkap wants to merge 1 commit intomainfrom
replayx/incident-checkout-race-001-mo64krrx
Open

fix(checkout-api): resolve checkout-race-condition#13
Spkap wants to merge 1 commit intomainfrom
replayx/incident-checkout-race-001-mo64krrx

Conversation

@Spkap
Copy link
Copy Markdown
Collaborator

@Spkap Spkap commented Apr 19, 2026

Summary

Resolve checkout-race-condition for checkout-api with a validated ReplayX patch candidate.

Changed Files

  • demo_app/src/inventory/reserve-stock.ts
  • demo_app/src/checkout/submit-order.ts

Validation

  • Concurrent checkout repro: exit 0
  • Sequential checkout sanity: exit 0

Rollback

Revert the live inventory guard and concurrent checkout settlement handling.

Copilot AI review requested due to automatic review settings April 19, 2026 18:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses the seeded checkout-race-condition in the demo checkout-api flow by preventing inventory from being driven negative under concurrent checkout attempts and by making the concurrent repro runner tolerant of expected OutOfStock outcomes.

Changes:

  • Add a post-delay “live inventory” availability guard in reserveStock to prevent overselling after a stale pre-check.
  • Return reservation tokens/snapshot versions based on the committed record version rather than the pre-check snapshot.
  • Update the concurrent checkout scenario to use Promise.allSettled and only fail on non-OutOfStock errors.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
demo_app/src/inventory/reserve-stock.ts Adds a live inventory check after the async delay and aligns returned reservation metadata to the committed record state.
demo_app/src/checkout/submit-order.ts Treats OutOfStock as an expected concurrent outcome via allSettled, while still surfacing unexpected failures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 18 to +24
// Intentional bug: this delay makes the pre-check stale under concurrent checkout attempts.
await delay(25);

const record = getInventoryRecord(sku);

if (record.available < quantity) {
throw new Error(`OutOfStock: ${sku}`);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

The inline comment says the delay is an “Intentional bug” that makes the pre-check stale, but the new post-delay record.available guard now prevents the stale-check oversell. Please update/remove this comment so it reflects the current behavior (e.g., that the delay simulates concurrency and the second check is the actual safety guard).

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +23
const hardFailure = settled.find(
(result) =>
result.status === "rejected" &&
!(result.reason instanceof Error && result.reason.message.startsWith("OutOfStock:"))
);
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

This treats OutOfStock as a non-fatal error by matching on error.message.startsWith("OutOfStock:"), which is brittle (any message change or non-Error throw will change behavior). Prefer a typed signal (e.g., an exported OutOfStockError class, error.name === "OutOfStockError", or a small isOutOfStockError() helper co-located with reserveStock) so the concurrent settlement logic isn’t coupled to a string prefix.

Copilot uses AI. Check for mistakes.
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.

2 participants