Files
platform/todo/plugin-infrastructure/a-plugin-s-own-routes-lose-their-request-body.md
T
pastilhas 185aa3168e two todos from the caldav extraction
setting up a phone should be one link inside officer — open Calendar, the screen
already knows this phone is not set up, click, done. the endpoint for it exists
(POST /api/caldav/provision/ios) and nothing in the UI calls it, so the smooth
path is built and hidden while the path a new person walks has four fields.

it also does not currently work: the profile is refused by iOS 26.6 as "Invalid
Profile". the todo records what was RULED OUT with evidence rather than
reasoning, because that is the expensive part to redo — delivery works, the
phone really received it (the claim is logged now), the server side is correct,
and removing PayloadScope did not fix it. signing is untested and is the first
experiment.

second: a plugin with a sidecar proxy loses the request body on its own routes.
createSidecarProxy registers the whole mount prefix and bodyParser skips it,
which is right for forwarded traffic and load-bearing for uploads — but it
covers the plugin's own routes too. caldav works around it locally; the real fix
is the proxy claiming only the sub-path it serves, which is shared machinery and
not a thing to change with a phone waiting.
2026-08-27 05:46:57 +00:00

2.3 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 open 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.