You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the failing request does not accept HTML, respond with the plain-text traceback plus a URL that opens the interactive debugger for that same crash, instead of a 13 KB HTML page a JSON client cannot use.
Done criteria:
DebuggerCore decides the crash response format by asking whether the client accepts HTML, rather than enumerating API content types. HTML stays the default: a missing Accept, or one containing text/html, text/*, or */*, keeps today's interactive page. Only a client that explicitly accepts something else (application/json, application/xml, application/*+json, ...) gets text/plain.
The plain-text body is the existing traceback.plaintext, followed by a URL that reopens the crash in the interactive debugger, anchored at the frame that raised (#frame-<id>; the page already emits those anchors).
A new debugger endpoint re-renders a stored traceback by id, e.g. ?__debugger__=yes&cmd=traceback&tb=<traceback_id>&s=<secret>, reusing self.tracebacks and Traceback.render_full(). This is what makes the link possible: today the interactive page exists only inline in the crash response, so a crashed POST or XHR can never be reopened. Guard it with the session secret, consistent with cmd=source.
Accept parsing stays a hand-rolled substring test. backlash declares no runtime dependencies, so no parsing library may be introduced for this, and pathological headers such as text/html;q=0 are not worth handling.
Both transports inherit the behavior from the core; the WSGI and ASGI shells only pass the request's Accept value down.
Tests cover: HTML client unchanged, Accept: application/json gets text/plain with the link, missing Accept gets HTML, */* gets HTML, the re-render endpoint returns the interactive page for a stored id, an unknown or evicted id degrades cleanly, and a wrong secret is rejected.
One decision to record: whether the link is a relative, query-only URL (?__debugger__=yes&cmd=traceback&...), which needs no new plumbing and stays correct behind a reverse-proxy prefix, or an absolute path, which reads better in a terminal but requires the shells to pass the request path (SCRIPT_NAME + PATH_INFO, or root_path + path) into the core.
Non-goals: no JSON or XML rendering of the traceback, no honouring of X-Requested-With, no q-value ranking, and no change to the status code or to the post-response-started paths.
Why
The debugger performs no content negotiation at all: both shells hardcode text/html; charset=utf-8, so a crashing JSON endpoint answers 500 with an HTML document. A client calling response.json() gets a parse error on top of the original failure, and the traceback is only readable by digging the raw response out of browser devtools.
The interactive part is lost entirely in that case. The console UI cannot be driven from a devtools response pane, and re-issuing the request in the address bar to obtain a clickable page only works for GET. For a failing POST or XHR, the captured frames are unreachable in practice even though they are still held in memory.
A plain-text body makes the failure legible where developers actually see it, and the link restores access to the console for exactly the frames that already exist. It also gives Werkzeug-divergence value in the direction users notice: ASGI deployments are predominantly APIs, which is where the HTML-only response hurts most.
References
backlash/debugger.py — DebuggerCore.debugger_response() (where a cmd=traceback branch fits) and save_traceback() (populates self.tracebacks).
backlash/wsgi.py and backlash/asgi.py — the two crash paths that currently hardcode text/html.
backlash/tbtools.py — Traceback.plaintext, Traceback.render_full(), and the id="frame-<id>" anchors used for deep-linking.
django.views.debug — prior art for choosing the format from request.accepts('text/html').
Goal
When the failing request does not accept HTML, respond with the plain-text traceback plus a URL that opens the interactive debugger for that same crash, instead of a 13 KB HTML page a JSON client cannot use.
Done criteria:
DebuggerCoredecides the crash response format by asking whether the client accepts HTML, rather than enumerating API content types. HTML stays the default: a missingAccept, or one containingtext/html,text/*, or*/*, keeps today's interactive page. Only a client that explicitly accepts something else (application/json,application/xml,application/*+json, ...) getstext/plain.traceback.plaintext, followed by a URL that reopens the crash in the interactive debugger, anchored at the frame that raised (#frame-<id>; the page already emits those anchors).?__debugger__=yes&cmd=traceback&tb=<traceback_id>&s=<secret>, reusingself.tracebacksandTraceback.render_full(). This is what makes the link possible: today the interactive page exists only inline in the crash response, so a crashedPOSTor XHR can never be reopened. Guard it with the session secret, consistent withcmd=source.text/html;q=0are not worth handling.Acceptvalue down.Accept: application/jsongetstext/plainwith the link, missingAcceptgets HTML,*/*gets HTML, the re-render endpoint returns the interactive page for a stored id, an unknown or evicted id degrades cleanly, and a wrong secret is rejected.One decision to record: whether the link is a relative, query-only URL (
?__debugger__=yes&cmd=traceback&...), which needs no new plumbing and stays correct behind a reverse-proxy prefix, or an absolute path, which reads better in a terminal but requires the shells to pass the request path (SCRIPT_NAME+PATH_INFO, orroot_path+path) into the core.Non-goals: no JSON or XML rendering of the traceback, no honouring of
X-Requested-With, no q-value ranking, and no change to the status code or to the post-response-started paths.Why
The debugger performs no content negotiation at all: both shells hardcode
text/html; charset=utf-8, so a crashing JSON endpoint answers500with an HTML document. A client callingresponse.json()gets a parse error on top of the original failure, and the traceback is only readable by digging the raw response out of browser devtools.The interactive part is lost entirely in that case. The console UI cannot be driven from a devtools response pane, and re-issuing the request in the address bar to obtain a clickable page only works for
GET. For a failingPOSTor XHR, the captured frames are unreachable in practice even though they are still held in memory.A plain-text body makes the failure legible where developers actually see it, and the link restores access to the console for exactly the frames that already exist. It also gives Werkzeug-divergence value in the direction users notice: ASGI deployments are predominantly APIs, which is where the HTML-only response hurts most.
References
backlash/debugger.py—DebuggerCore.debugger_response()(where acmd=tracebackbranch fits) andsave_traceback()(populatesself.tracebacks).backlash/wsgi.pyandbacklash/asgi.py— the two crash paths that currently hardcodetext/html.backlash/tbtools.py—Traceback.plaintext,Traceback.render_full(), and theid="frame-<id>"anchors used for deep-linking.django.views.debug— prior art for choosing the format fromrequest.accepts('text/html').