2.8 KiB
id, title, theme, status, created, date_source, order
| id | title | theme | status | created | date_source | order |
|---|---|---|---|---|---|---|
| plugin-infrastructure/a-plugin-s-own-routes-lose-their-request-body | A plugin that has a sidecar proxy loses the request body on its OWN routes | plugin-infrastructure | done | 2026-08-27 | stated | 9 |
createSidecarProxy registers the plugin's whole mount prefix with registerProxiedPrefix, and
bodyParser then skips every request under it — including routes the plugin serves itself. Those
routes get ctx.get('body') === {} and every field reads as missing.
Found on 2026-08-27 extracting caldav: POST /api/caldav/provision/ios answered
{"error":"deviceLabel is required"} for a request that carried one. It had never happened before
because app passwords lived at /api/dav — a second top-level prefix that was not proxied — and one
prefix per plugin is the rule that ended that arrangement.
The skip itself is correct and must not be removed. Parsing a body that is only passing through is
wasted for JSON and corrupting for multipart/form-data: Hono caches the parsed FormData and
re-serialises it with a NEW boundary while the proxy forwards the ORIGINAL content-type header naming
the old one, so every upload fails with "Multipart: Unexpected end of form". That is why the mechanism
exists.
The narrow fix is that the proxy should register only the sub-path it actually serves. Caldav's
sidecar answers _officer/* and nothing else, so /api/caldav/_officer is the honest claim; the rest of
the prefix is the plugin's own and should be parsed normally. That means createSidecarProxy taking the
proxied sub-path rather than assuming the whole mount.
Not done on the night it was found: it is shared machinery every sidecar depends on, and the change was
diagnosed while a phone was waiting. The caldav plugin works around it locally — jsonBody() in
api/app-passwords.ts reads ctx.req.json() directly, with the reasoning inline.
Who else is affected: every plugin that mixes its own routes with a sidecar proxy under one prefix, which is most of them. They have not hit it because their own routes are GETs, or because their writes go to the sidecar. A plugin adding its first non-proxied POST will hit it, and the symptom — a required field reported missing on a request that plainly has it — points at the caller rather than at this.
Done 2026-08-27 (f00abe81 in the platform, and caldav's own repo). createSidecarProxy takes
proxiedSubPath, so a proxy claims only what it forwards — caldav claims /api/caldav/_officer and its
own routes are parsed normally. Omitted still means the whole prefix, so no other plugin changed.
Caldav's local jsonBody() workaround is gone. Verified: a POST to the plugin's own route read its body,
and the proxied namespace still reaches the sidecar.