Fix "Current Transaction Is Aborted, Commands Ignored" in Django
Fix the PostgreSQL InternalError current transaction is aborted, commands ignored until end of transaction block in Django: find the first failing query, use atomic savepoints around expected errors, and clean up after exceptions in tests and workers.
Problem statement
The traceback in Sentry points at an innocent query:
django.db.utils.InternalError: current transaction is aborted,
commands ignored until end of transaction block
The query in the traceback is not the problem. In PostgreSQL, once any statement in a transaction fails, every later statement in the same transaction is rejected with this message until the transaction is rolled back. Somewhere earlier, a query failed, your code caught the exception and kept going, and the next query hit the aborted transaction.
- Look further up the logs or Sentry breadcrumbs for the first error in the same request or task.
- Wrap queries that are allowed to fail in their own
transaction.atomic()block, which creates a savepoint. - Do not catch
DatabaseErrororIntegrityErrorand continue without a savepoint. - In tests, use
TestCasewithatomicblocks around expected failures, orTransactionTestCase. - With
ATOMIC_REQUESTS = True, remember the whole view is one transaction.
Step-by-step solution
1. Find the first failure
Enable PostgreSQL's statement logging for errors, which is on by default:
grep -B2 "current transaction is aborted" /var/log/postgresql/postgresql-17-main.log | head
The line before the first "current transaction is aborted" in a session shows the original error, such as duplicate key value violates unique constraint or invalid input syntax for type uuid. In Sentry, breadcrumbs for the event list the earlier failing query.
2. Recognize the pattern in code
The classic version is "try to insert, fall back on conflict":
# broken inside any transaction
try:
Tag.objects.create(name=name)
except IntegrityError:
pass
tag = Tag.objects.get(name=name) # InternalError: current transaction is aborted
If this runs inside ATOMIC_REQUESTS, a Celery task wrapped in atomic(), or a test case, the failed INSERT aborted the outer transaction and the get() is rejected.
3. Use a savepoint around expected failures
from django.db import IntegrityError, transaction
try:
with transaction.atomic():
Tag.objects.create(name=name)
except IntegrityError:
pass
tag = Tag.objects.get(name=name)
The inner atomic() becomes a savepoint inside the outer transaction. When the insert fails, Django rolls back to the savepoint, and the outer transaction stays usable. Better still, avoid the exception: Tag.objects.get_or_create(name=name) already uses a savepoint, and PostgreSQL's ON CONFLICT via bulk_create(..., ignore_conflicts=True) never raises.
4. Check long-running workers
A Celery task or management command that catches all exceptions in a loop can poison the connection for every following iteration:
for row in rows:
try:
with transaction.atomic():
import_row(row)
except Exception:
logger.exception("row failed: %s", row["id"])
Putting the atomic() inside the loop means each row has its own transaction or savepoint, so one bad row does not abort the rest.
5. Fix tests
Django's TestCase wraps each test in a transaction. A test that expects an IntegrityError and then queries again fails with this message. Use the assertion inside its own atomic block:
def test_duplicate_email_rejected(self):
with self.assertRaises(IntegrityError):
with transaction.atomic():
User.objects.create(email="a@example.com")
self.assertEqual(User.objects.count(), 1)
Explanation
PostgreSQL treats a transaction as all-or-nothing. After an error, the server marks the transaction as failed and refuses further commands, so it cannot commit a half-done unit of work by accident. Only ROLLBACK, or ROLLBACK TO SAVEPOINT if a savepoint exists, clears the state. Django's atomic() creates a savepoint when nested and rolls back to it on exception, which is exactly the tool for expected failures. MySQL and SQLite do not abort the whole transaction on most errors, which is why code that "worked" there breaks on PostgreSQL.
Edge cases / notes
- Connection reuse. If a process catches the error and never rolls back outside any atomic block, Django's request cleanup resets the connection at the end of the request. Long-running scripts must handle it themselves.
- Raw SQL.
cursor.execute()failures abort the transaction the same way. Wrap them inatomic()too. - Duplicate keys from sequences. If the first error is a duplicate primary key, the sequence may be out of sync; see Fix PostgreSQL Sequence Out of Sync.
Next steps
- If transactions stay open after errors, see Fix PostgreSQL Idle in Transaction in Django.
- Lock-related first errors are covered in Fix PostgreSQL Deadlock Detected in Django.
- For a full approach to server errors, read Fix Django 500 Internal Server Error in Production.
FAQ
Why is the error on a query that looks correct?
Because the transaction was already aborted by an earlier failed query. The reported query is just the next one to run.
Does this happen with MySQL?
Mostly no. MySQL rolls back only the failed statement in most cases. PostgreSQL aborts the whole transaction, which is stricter.
Should I turn off ATOMIC_REQUESTS?
Not because of this error. Keep it if you rely on it, and use savepoints around statements that are expected to fail.
How do I recover the connection in a script?
Call transaction.rollback() when outside an atomic block, or structure the script so each unit of work uses its own atomic().