Explain a traceback: karni explain -¶
The expensive part of debugging is rebuilding context from a 60-line
traceback: which frame is yours, which line is the cause, what the framework
expected. karni explain - does that step. Paste a traceback, a test failure
or a log line into it, and it answers with the failure class, the specific
cause, the first project frame (file:line), the fix and the docs link:
karni explain - < traceback.txt
pbpaste | karni explain -
python manage.py migrate 2>&1 | karni explain -
karni explain "django.db.utils.OperationalError: no such table: shop_order"
karni explain - --json # for agents
With no code and stdin not a terminal, karni explain reads stdin too. An
argument that cannot be a code (it has a space or a colon) is read as a log
line. karni explain E104 still explains a code. Agents use the MCP tool
explain_text(text) (see agents), and rule K013 tells
them to run it before reading a traceback by hand.
It needs neither Django nor a project. The catalogue is
karni.errorcodes.failures, standard library only, and karni-cli carries a
copy. Inside a project, frames under the project root count as project frames
and are shown relative to the root.
What it prints¶
A real manage.py check traceback, from an Owned("organiser") typo in
EventHub's events/viewsets.py:
Class: E104 Owned() does not name a path to the user
Cause: EventViewSet: Owned('organiser') does not lead from Event to the user model (Event has no field 'organiser'). Name a relation path ending at the user, e.g. Owned("organizer") or Owned("event__organizer").
Where: events/viewsets.py:26 in <module>
Why: Owned('path') filters rows to those whose path leads to request.user, and stamps the owner on create. A path that does not end at the user model would match nothing or the wrong rows.
Fix: Name the owner relation as a string, following relations with __ and ending at the user model: Owned('organizer') or Owned('event__organizer').
Docs: https://cbsshekhawat18-lab.github.io/karni/errors/#e104
| Line | What it is |
|---|---|
Class |
the failure class: a registered code and its title, an agent rule, or a class from the catalogue below |
Cause |
the specific cause, read from your text (your class, field, table, host) |
Where |
the innermost project frame (file:line in function). For a query report, the line that issued the repeated query |
Why |
why Karni behaves this way (from the code's registry entry) |
Fix |
the fix |
Next |
the command that answers the rest: karni queries … for a query count, karni why … for an access outcome |
Docs |
the code's entry in errors, or the class's section below |
A pytest run whose request went over its query budget hands over to
karni queries, which names the cause and the hint:
Class: E117 A request ran more SQL queries than its budget
Cause: EventViewSet.list ran 9 queries against a budget of 8. Repeated: 5× events_venue from EventSerializer.venue_city (events/serializers.py:25 in get_venue_city).
Where: events/serializers.py:25 in get_venue_city
Why: Repeated statements are almost always an N+1: a relation loaded once per row. Budgets turn that into a failing test instead of a slow production page.
Fix: Run the `karni queries` command below: it attributes every repeated query to the serializer field, method or property that issued it and prints the exact hint (select_related / prefetch_related on the viewset, APIMeta.select / prefetch on the model, or an annotation for an aggregate). Raise the budget only for work that is real: query_budget = Budget(<action>=N, reason=...).
Next: karni queries EventViewSet.list --as <user>
Docs: https://cbsshekhawat18-lab.github.io/karni/errors/#e117
When it does not know¶
It never guesses. Text that no pattern recognises gets the exception line, the innermost project frame and the generic next step:
Not a failure Karni recognises: ZeroDivisionError: division by zero
Where: events/serializers.py:33 in get_capacity_ratio
Next: Karni does not know this failure, so it will not guess. Read the innermost project frame (Where) and the exception's own message. `karni doctor` checks the environment and `karni check` the declarations.
A log line printed beside a traceback, such as the dev query log, does not
explain the traceback. It is listed under Also in the text:.
The catalogue¶
Patterns are tried most specific first, and every match is reported (five at most). The patterns that identify a failure:
- A registered code in the text:
E104: …,(karni.E101)from a system check,error: C012: …from the CLI,PAY004: …from a module, or a…/errors/#e117link. The answer is the code's entry, with your message as the cause. The query budget codes (E117, E129) also list the repeated statements and thekarni queriescommand. - An agent rule or a lint id:
K101 [model-base](karni rules --check),[model-base] path:12:(karni check),karni/no-raw-elements(ESLint). The answer is the rule. - A Karni exception class without its code, such as
QueryBudgetExceeded: …from an older release. - Query counts, CSRF and CORS, access outcomes and framework-adjacent failures, the classes below.
A summary code (E702 "karni check found violations", C009 "a command in
the project failed") is listed after the failures it summarises.
Query counts¶
A query-count assertion failed¶
Django's assertNumQueries (AssertionError: 9 != 4 : 9 queries executed, 4
expected) or pytest-django's django_assert_num_queries (Expected to
perform 4 queries but 9 were done). The cause groups the captured queries
by statement (5× events_venue). Fix: run karni queries on the endpoint
the test requests.
The dev server's query-budget warning¶
EventViewSet.list ran 28 queries against a budget of 8., logged in warn mode (the
dev profile) without a code. It is E117. The answer lists the repeated
statements with the field that issued each and hands over to karni queries
EventViewSet.list.
Repeated statements in the dev query log¶
GET /api/events/ 200: 30 queries, 24 duplicated … from
karni.middleware.QueryLogMiddleware. Fix: karni queries /api/events/.
CSRF and CORS¶
A CSRF refusal (session mode)¶
CSRF Failed: CSRF token missing., Forbidden (CSRF cookie not set.) or
CSRF Failed: Origin checking failed - http://localhost:3000 does not match
any trusted origins. In session mode every unsafe request needs the
csrftoken cookie and the same value in X-CSRFToken, from an origin in
CSRF_TRUSTED_ORIGINS, which Karni derives from KARNI_FRONTEND_URL (see
session mode). The fix names your origin:
set KARNI_FRONTEND_URL to it, serve the SPA from that origin, or let
KarniClient bootstrap the cookie.
The browser blocked a cross-origin call¶
Chromium's has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
header is present, or Firefox's Cross-Origin Request Blocked. The dev
profile allows every origin. Outside dev, CORS is closed unless
CORS_ALLOWED_ORIGINS lists the origin. Fix: list the SPA's origin there, or
serve the SPA and the API from one origin.
Access outcomes¶
Each one hands over to karni why, which evaluates the access rule for the
user and row.
404 from a Karni viewset¶
No Event matches the given query. Rows are limited by EventViewSet.access
read=…. A row outside the requester's read scope answers 404, exactly like a
row that does not exist. The cause quotes the read rule.
403 from a Karni viewset¶
Access denied: this row is readable but the action requires Owned('organizer')
| HasRole('staff'). The user can read the row, but the write rule refuses
the action.
A request without credentials¶
Authentication credentials were not provided. (401 in jwt mode, 403 in
session mode). Fix: sign in first (KarniClient keeps the session or the
tokens), or authenticate the test client.
403 from a viewset's access rule¶
DRF's You do not have permission to perform this action. from a viewset.
A 401, 403 or 404 in the dev server's log¶
Not Found: /api/…, Forbidden: /api/… or the access log line, without a
body to say more. From a Karni viewset, a 404 can also mean a row outside the
read scope. karni routes lists every route.
Database and migrations¶
The database driver is not installed (C012)¶
ImproperlyConfigured: Error loading psycopg2 or psycopg module, or No
module named 'psycopg'. Fix: pip install "psycopg[binary]" into the
project interpreter, run in Docker, or use SQLite.
The database cannot be reached (C013)¶
OperationalError: connection failed: connection to server at "127.0.0.1",
port 5432 failed: Connection refused, failed to resolve host 'db',
password authentication failed. The cause quotes the driver's error and
points out the compose-only host db. Fix: start the database (karni serve
-d), or point DATABASE_URL at one that runs.
A table is missing, or migrations are not applied¶
no such table: events_event (SQLite), relation "events_event" does not
exist (PostgreSQL), or runserver's You have 1 unapplied migration(s). Fix:
karni migrate, after karni make migration when the model has none.
A model change has no migration¶
no such column: events_event.subtitle, column … does not exist, or
makemigrations --check listing Migrations for 'events':. Fix: karni make
migration && karni migrate, then karni types (rule K003).
Settings and services¶
Django code ran before the settings were loaded¶
Requested setting INSTALLED_APPS, but settings are not configured or Apps
aren't loaded yet. Fix: run it through karni shell or a management
command, or set DJANGO_SETTINGS_MODULE and call django.setup() first.
Karni's own settings validation fails with E504, a registered code.
An environment variable is not set¶
Set the SECRET_KEY environment variable (django-environ). Fix: cp
.env.example .env and set it.
The host is not in ALLOWED_HOSTS¶
Invalid HTTP_HOST header: 'evil.example'. You may need to add 'evil.example'
to ALLOWED_HOSTS. Fix: add it to ALLOWED_HOSTS in .env.
Redis is not reachable¶
redis.exceptions.ConnectionError: Error 61 connecting to 127.0.0.1:6379.
Connection refused. The default cache (throttle counters, the response
cache) and the Celery broker are Redis. Fix: start it (karni serve -d), or
for a host run without Redis set KARNI_CACHE=locmem and run Celery tasks
eagerly.
The OpenAPI schema¶
A SerializerMethodField has no return type¶
Warning [EventViewSet > EventSerializer]: unable to resolve type hint for
function "get_organizer_email" from manage.py spectacular. The field is
documented as a string, and karni types generates
the wrong TypeScript type from it. Fix: annotate the method's return type, or
use @extend_schema_field.
Another schema warning on a viewset¶
Any other Warning [<View> > <Serializer>]: … or Error [<View>]: … from
drf-spectacular, with the message as the cause.
JSON¶
--json (and nothing else on stdout) for agents:
{
"recognised": true,
"exception": "karni.performance.QueryBudgetExceeded: E117: EventViewSet.list ran 28 queries against a budget of 8.",
"where": "events/tests/test_scratch_listing.py:19",
"raised_in": "karni/api/viewsets.py:870",
"findings": [
{
"class": "code-e117",
"code": "E117",
"title": "A request ran more SQL queries than its budget",
"cause": "EventViewSet.list ran 28 queries against a budget of 8. Repeated: 12× events_venue from EventSerializer.venue_city (events/serializers.py:25 in get_venue_city), 12× events_booking from TicketTierSerializer.remaining (events/models.py:56 in remaining).",
"where": "events/serializers.py:25 in get_venue_city",
"fix": "Run the `karni queries` command below: …",
"next": "karni queries EventViewSet.list --as <user>",
"why": "Repeated statements are almost always an N+1: …",
"docs": "https://cbsshekhawat18-lab.github.io/karni/errors/#e117"
}
],
"also": [],
"next": ""
}
recognised is false (and findings empty) for a failure Karni does not
know. next then holds the generic next step.
Extending the catalogue¶
karni.errorcodes.failures.diagnose(text, patterns=…) takes the catalogue as
an argument. A pattern is any callable (Text, context) -> list[Finding].
Extend PATTERNS to add a class, for example in your own tooling.
lookup= (code to entry) and rules= (the agent rules) are the other two
escape hatches. A new class in Karni comes with a real captured failure as
its test fixture (packages/karni/tests/debugging/fixtures/), an anchor on
this page and an entry in CLASSES.