April 13, 2026

When transactions go off the rails

I shipped a transaction bug, so I built a linter

Dev ships a sneaky data bug, builds a “code cop” — linter fans vs redesign purists

TLDR: A developer shipped a bug that let some database actions slip past a single “all-or-nothing” step, then built a linter to catch it early. Commenters are split between cheering the tool, warning about outages from blocked connections, and insisting the real fix is safer API design that prevents mix-ups.

A Go developer confessed to shipping a sneaky database bug: some actions ran outside the “all‑or‑nothing” safety box of a transaction. To stop repeats, he built a custom linter — think a code cop that flags risky calls before launch. The post doubles as a DIY guide; the comments turn it into a courtroom.

Ops veterans swarmed in with horror stories. One warns this bug doesn’t just scramble data — it can jam the app’s lifeline of database connections, freezing requests until clocks run out. The vibe: fix it or get paged at 3 a.m. Consequences > theory, say the on-call crowd.

Then the design purists pounced. “Humans are bad with modes,” sighed one, arguing the real fix is to split the “normal” database handle from the “transaction” one so you literally can’t mix them up. Another bristled at the phrase “outside a transaction,” reminding everyone that databases are always inside some transaction — often secretly started by fancy tools — so the API is the real villain.

Others went meta. One dev built the same linter but burned out trying to add it to a popular lint bundle; with today’s AI, maybe it’s worth another shot. Meanwhile, teachers-at-heart applauded the clear walk-through. Cue the memes: it feels like paying with two wallets at checkout or pouring coffee outside the cup.

Bottom line: Tooling vs. design is the fight of the day — and this linter just lit the fuse.

Key Points

  • A production bug occurred when database operations leaked outside a transaction due to using the outer repository (s.repo) instead of the transaction-scoped parameter (tx) in a Go callback.
  • This misuse is easy to introduce when wrapping existing code in transactions and can cause silent data corruption and race conditions.
  • The issue is difficult to catch with compilation, tests, and code reviews, often surfacing only under load.
  • The bug is structural (based on variable references) and well-suited to detection via static analysis.
  • The author built a custom linter using Go’s go/analysis framework, providing an analyzer skeleton to enforce correct use of the transaction-scoped repository.

Hottest takes

"then all stall holding the first open until timeouts occur." — mnahkies
"humans are bad with modes." — rowyourboat
"Everything in a relational database is done within a transaction" — branko_d
Made with <3 by @siedrix and @shesho from CDMX. Powered by Forge&Hive.