cpatpa untangles the routing trap every Mike fork walks into

When your backend and your web app live at the same address, the proxy in front can't always tell a click from a data call - cpatpa worked out a fix.

infrastructure

In a Mike-style deployment, the data engine and the web interface often share a single address, and several paths - admin, projects, workflows - exist on both. That leaves the traffic cop out front guessing: is a request for one of those paths a person opening a page, or the app quietly fetching data? Guess wrong and people land on broken screens.

cpatpa's fix stops guessing and starts reading what each request is actually asking for - a web page, a data response, or a live chat stream - and sends it to the right place. It took three passes to cover the edge cases, including streaming chat and file downloads. The result is documented and reusable: a worked solution to a problem baked into this whole architecture, not a one-off patch.

So what Anyone standing up a Mike fork with a combined backend and web app should crib this rather than rediscover the same broken-routing headache.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

3 commits from cpatpa/PIP, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
489196a2 Caddy: route bare /me, /workspaces, /projects, etc. to backend Claude 2026-05-16 ↗ GitHub
commit body
The @backend path matcher only listed each prefix with /* (e.g.
/me/*), which in Caddy matches /me/onboarding-options but not the
bare /me. Bare-path requests were falling through to the Next.js
frontend, hitting the auth middleware, and 302-redirecting clients
back to /onboarding. This made the onboarding page hang at "Loading..."
after first sign-in: the form's /me fetch followed the redirect,
got HTML back, and the page never received an `onboarded` flag.

Express routers /me, /workspaces, /projects, /chat, /single-documents,
/tabular-review, and /workflows all expose list/create handlers on
the bare path, so each prefix is now listed both bare and with /*.

Operators who installed before this fix need to either re-run
install.sh (regenerates the Caddyfile) or edit
${DATA_ROOT}/caddy/Caddyfile by hand and `docker compose restart
caddy`.
1f08f72a Caddy: discriminate backend vs frontend by Accept header Claude 2026-05-16 ↗ GitHub
commit body
The previous fix tried to route bare /admin, /projects, /workflows
to the backend so the JS list-fetches would work, but those same
paths are also Next.js pages, so browser navigation started
returning JSON 401s.

The two surfaces share an origin and several path prefixes overlap.
The frontend's API client always sets `Accept: application/json`;
browser navigation sends `Accept: text/html,...`. Match on both path
and Accept: JSON requests go to the backend, page loads go to
Next.js.

/health and /ready stay path-only because docker healthchecks and
curl send `Accept: */*`.

Apply to a running deployment by editing
${DATA_ROOT}/caddy/Caddyfile to match this template and running
`docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile`,
or just re-run install.sh.
d8ae0d66 Caddy: route SSE and binary requests to backend, not just JSON Claude 2026-05-16 ↗ GitHub
commit body
The previous matcher required Accept to contain application/json,
which made browser-driven streaming chat requests (Accept:
text/event-stream) fall through to the Next.js frontend and return
the 404 HTML page as the chat reply.

Invert the matcher: route to backend when the path matches AND the
Accept header does NOT contain text/html. Browser navigation always
sends text/html; JSON fetches, SSE streams, and binary downloads
never do. One rule covers all three.

Operators apply this on a running deployment with:

  sudo bash bin/pip-write-caddyfile.sh

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-369.md from inside the repo you want the changes in.

⬇ Download capture-thread-369.md