--- id: plugin-infrastructure/a-plugin-s-own-routes-lose-their-request-body title: "A plugin that has a sidecar proxy loses the request body on its OWN routes" theme: plugin-infrastructure status: done created: 2026-08-27 date_source: stated order: 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.