Fix mautrix-telegram Provisioning API Breakage on aiohttp 3.9+¶
Overlays
A nixpkgs overlay that patches mautrix-telegram's provisioning webserver so it survives aiohttp 3.9+.
The problem¶
Point a recent nixpkgs (one that builds mautrix-telegram against aiohttp 3.9
or newer — 3.13 in particular) at the Telegram bridge and every request to
/_matrix/provision/* returns HTTP 500 with:
This is not limited to error paths. Middleware runs before the route handler, so the handler is never reached — the whole provisioning API is dead.
The trap¶
mautrix-telegram's ProvisioningAPI.error_middleware was written in aiohttp's
old "middleware factory" style: a callable
async def error_middleware(app, handler): # (app, handler) -> coroutine
async def middleware_handler(request):
...
return middleware_handler # returns a *function*
aiohttp 3.9 removed the factory mode. Middlewares are now plain
(request, handler) -> Response coroutines decorated with @web.middleware.
The insidious part: aiohttp doesn't reject the stale factory. It treats it as
an ordinary middleware and calls it with (request, handler). The factory
then returns its inner middleware_handler function instead of a
Response. aiohttp passes that return value straight to finish_response,
which calls .prepare() on it — and a function object has no .prepare. Hence
the cryptic AttributeError on every request rather than a clear "bad
middleware signature" error.
The fix¶
Rewrite the middleware to the modern signature: decorate it with
@web.middleware, take (request, handler) directly, and return a
Response (or re-raise) inline instead of returning a nested handler
function. The patch in this recipe does exactly that.
Usage¶
Add the overlay to your nixpkgs config:
or apply it directly when you import nixpkgs:
import nixpkgs {
inherit system;
overlays = [ (import ./overlays/mautrix-telegram-aiohttp-middleware-fix) ];
}
The directory contains:
default.nix— the overlay (overrideAttrsappends the patch).mautrix-telegram-aiohttp-middleware.patch— the actual source fix.
Caveats¶
- Line offsets. The patch targets
mautrix_telegram/web/provisioning/__init__.pyaround the@staticmethoderror_middleware. If your mautrix-telegram version has shifted those lines, regenerate the patch against your pinned source — the transformation is trivial (add@web.middleware, flatten the factory into a(request, handler) -> Responsecoroutine). - The python3.13 override is optional.
default.nixpins the interpreter that pulls in an affected aiohttp so the bug is reproducible. Drop the.override { python3 = prev.python313; }if your nixpkgs already builds the bridge against aiohttp ≥ 3.9 on its default interpreter. - Upstream may fix this. Once mautrix-telegram ships the modern middleware signature, drop the overlay entirely.
Source¶
overlays/mautrix-telegram-aiohttp-middleware-fix/default.nix
# Nixpkgs overlay: patch mautrix-telegram's provisioning webserver so it works
# with aiohttp >= 3.9 (the @web.middleware signature change).
#
# See README.md for the full "why". In short: mautrix-telegram's
# ProvisioningAPI.error_middleware still uses the pre-3.9 aiohttp
# "middleware factory" pattern — a callable (app, handler) -> coroutine.
# aiohttp 3.9 dropped that mode, and under aiohttp 3.13 the stale factory is
# treated as a plain middleware whose *return value* (a function, not a
# Response) gets handed to finish_response, which then does `.prepare` on a
# function object and raises. Result: every /_matrix/provision/* request 500s.
#
# Usage: add to nixpkgs.overlays, e.g.
# nixpkgs.overlays = [ (import ./overlays/mautrix-telegram-aiohttp-middleware-fix) ];
#
# The python3.13 override is optional — it pins the interpreter that ships the
# aiohttp version where this surfaces. Drop the `.override { ... }` if your
# nixpkgs already builds mautrix-telegram against an affected aiohttp.
final: prev: {
mautrix-telegram =
(prev.mautrix-telegram.override { python3 = prev.python313; }).overrideAttrs
(old: {
patches = (old.patches or [ ]) ++ [
./mautrix-telegram-aiohttp-middleware.patch
];
});
}