plugins/backend-toolkit/skills/transaction-management/SKILL.md
Use transactions and isolation levels correctly — keep them short, no network calls inside, explicit isolation, retry on serialization conflicts, and choose optimistic vs pessimistic locking. Use when a write spans multiple tables, when concurrent updates corrupt data, or when designing money/inventory flows. Not for cross-service event delivery (use async-messaging Outbox) or schema-level constraints (use schema-design).
npx skillsauth add jaykim88/claude-ai-engineering transaction-managementInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Keep multi-step writes atomic and correct under concurrency — short transactions, explicit isolation, no I/O inside, and the right locking strategy — without creating deadlocks or contention.
Universal — ACID, isolation levels, optimistic vs pessimistic locking, and the "no network calls in a transaction" rule are RDBMS principles; the transaction API differs by ORM.
Keep transactions short — and never do I/O inside
Choose isolation level explicitly
Handle serialization conflicts with retry
40001)resilience-patterns backoff (ORM-specific error codes are in Implementation)Pick locking strategy
WHERE updated_at = ?) — low contention, retry on mismatch; default for user-edit flowsSELECT ... FOR UPDATE) — high contention on a hot row (inventory decrement, counter); serializes accessFOR UPDATE SKIP LOCKED — work-queue pattern; competing workers each claim a different row without blocking each otherpg_advisory_xact_lock(key)) — application-level mutual exclusion (one-leader job, cron coordination) that doesn't tie to a specific rowCo-locate the event with the state change (Outbox)
async-messaging)5b. Use savepoints for partial rollback inside a long transaction
| ❌ Anti-pattern | ✅ Correct |
|---|---|
| HTTP/queue call inside a transaction | Do I/O outside; emit events via Outbox |
| Read-modify-write without locking | Optimistic (version) or pessimistic (FOR UPDATE) |
| Ignoring serialization-failure errors | Bounded retry on serialization failure (SQLSTATE 40001) |
| Long-running transaction (batch loop) | Chunk into short transactions |
| Inconsistent lock acquisition order | Acquire locks in a consistent order to avoid deadlocks |
| Workers blocking on the same queue row | FOR UPDATE SKIP LOCKED for parallel queue consumption |
| App-level "only one runs" enforced with a flag column (race) | Advisory lock (pg_advisory_xact_lock) for coordination |
| Tier | Examples | Action SLA |
|---|---|---|
| Critical | Read-modify-write on money/inventory with no locking → lost update / double-spend; serialization failure swallowed on a financial invariant; outbox event written outside the state-change transaction (event ⟺ state divergence) | Block release; fix immediately |
| Major | Network/IO call inside a transaction (long-held locks → contention); serialization conflicts not retried (bounded); inconsistent lock-acquisition order producing deadlocks | Fix this sprint |
| Minor | Isolation level left implicit where Read Committed is acceptable; pessimistic FOR UPDATE used where optimistic versioning suffices (throughput cost) | Schedule within 2 sprints |
fix(tx): add FOR UPDATE lock to inventory decrement / fix(tx): retry on P2034 serialization conflictprisma.$transaction(async (tx) => { ... }, { isolationLevel: 'Serializable' })P2034 (write conflict / deadlock) with backoffSELECT ... FOR UPDATE (Prisma $queryRaw); optimistic: version column + updateMany({ where: { id, version } }) check affected counttimeoutSession.begin() + with_for_update(); retry on OperationalErrordatabase/sql Tx; SELECT ... FOR UPDATE; retry on serialization errorresilience-patterns — idempotency keys often stored in the same transactionschema-design — constraints + transactions enforce integrity togetherasync-messaging — the Outbox event is written in the same transaction as the state changeP2034.development
Design webhooks correctly on both sides — sending (HMAC signing, retries with backoff, at-least-once) and receiving (verify signature on raw body, enqueue + 200 fast, dedupe on event id). Use when adding webhook delivery or consuming a provider's webhooks. Not for internal service-to-service events (use async-messaging) or general outbound-call retry policy (use resilience-patterns).
development
Backend testing pyramid — unit for pure logic, integration against a real DB (Testcontainers), and consumer-driven contract testing (Pact) for service boundaries. Use before a feature, after a bug fix, or when services break each other on deploy. Not for load testing (use performance-profiling) or security testing (use backend-security-audit).
data-ai
Design a relational schema — normalize to 3NF then denormalize with justification, choose the right Postgres index type per data shape, enforce constraints at the DB. Use when modeling a new domain, when queries are slow, or before a migration. Not for diagnosing slow queries (use query-optimization) or shipping the change without downtime (use migration-strategy).
development
Apply reliability primitives — capped exponential backoff with jitter, circuit breakers, timeouts, and idempotency keys — to every outbound call and mutating endpoint. Use when integrating an external service, when retries cause duplicate effects, or before shipping a payment/order flow. Not for job-runner retry config specifically (use background-jobs) or webhook-delivery specifics (use webhook-design, which reuses these primitives).