transaction-boundaries
Skill1 starsChanged 42 days agoMITView on GitHub
Use when writing or reviewing code that mixes database writes with external side effects such as HTTP calls, message publishing, emails, or cache updates
In this repository5 other files it gives its agents.
- snippets/CLAUDE.mdCLAUDE.md
- idempotencySkill
- money-handlingSkill
- safe-migrationsSkill
- timeouts-and-retriesSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 17
No network call inside an open transaction.
- Distinctive: line 22
Keep transactions short. No waiting on users or remote systems, except the outbox relay described below.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Transaction boundaries
A database transaction can roll back. An HTTP call, a published message, or a sent email cannot. Mixing them inside one method is how systems end up charged-but-not-ordered or announced-but-not-saved.
When to use
- A method writes to the database and also calls another service, publishes an event, sends a notification, or updates a cache.
- Reviewing any
@Transactionalmethod.
Checklist
- No network call inside an open transaction. It holds a connection and locks while it waits, and it cannot be undone if the commit fails.
- Publishing before commit can announce something that then rolls back. Publishing after commit can be lost if the process dies in between. Use a transactional outbox: write the event as a row in the same transaction, and let a relay publish it.
- Assume every consumer sees events at least once, and make handlers idempotent (see
idempotency). - With Spring's default proxy mode,
@Transactionalis applied by a proxy. A call from another method in the same class bypasses it, and so does a private method. - By default Spring rolls back on unchecked exceptions only. A checked exception commits unless
rollbackForsays otherwise. - Keep transactions short. No waiting on users or remote systems, except the outbox relay described below.
- A read followed by a write of the same row needs a lock or an optimistic version column, or two requests will overwrite each other.
Bad example
@Transactional
public void placeOrder(Order o) {
orders.save(o);
paymentClient.charge(o.total()); // remote call inside the transaction
broker.send("order-placed", o.id()); // published before commit
}What to flag
chargeruns even if the commit later fails: the customer is charged and there is no order.sendcan be consumed before the commit, so a consumer looks up an order that does not exist yet. If the commit fails, the event describes something that never happened.- The remote call keeps a database connection and row locks open for as long as the payment service takes to answer.
Good example
@Transactional
public void placeOrder(Order o) {
orders.save(o);
outbox.add("order-placed", o.id()); // same transaction as the order
}
// Separate component, runs after commit and retries on failure.
@Scheduled(fixedDelay = 500)
void relay() {
for (OutboxEvent e : outbox.pending(100)) {
broker.send(e.topic(), e.payload()); // must block until the broker acknowledges
outbox.markSent(e.id());
}
}The order and its event commit or roll back together. send must block until the broker acknowledges, otherwise markSent can record an event that was never delivered. The relay can still publish an event twice if it crashes between send and markSent, which is why consumers must be idempotent. If several relay instances run, either run a single relay, or have relay open a transaction, lock the rows inside it with FOR UPDATE SKIP LOCKED, and keep it open until the batch is marked sent. That transaction spans the broker call, the one accepted exception to item 1, so keep batches small and the send timeout short. Charge the customer in a consumer of order-placed, using an idempotency key derived from the order ID.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

