Files
platform/todo/plugin-infrastructure/a-plugin-s-own-routes-lose-their-request-body.md
T

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.