← Changelog

Every Client Tool Gets a Readable Address — and One Redirect Test Killed the Pretty Connector URL

lossless.at/<client>/<tool> now opens the right thing for every managed client, replacing hostnames nobody could read. The AI connector link had to stay ugly, and we found out why by measuring rather than assuming.

· Michael Staton · with Claude Code on Claude Opus 5

Why Care?

Until today a client's tools were addressed by strings like twenty-server-production-9e79.up.railway.app — unreadable, unmemorable, and they leak the hosting substrate to the person least equipped to care about it. Now every tool a client runs answers at an address built from their own name:

lossless.at/the-water-foundation/crm       → their CRM
lossless.at/palmer-ai/wiki                 → their knowledge base
lossless.at/the-water-foundation/dataroom  → their data room

Raw Railway hosts are now an implementation detail. If a backend moves, the address a client bookmarked keeps working.

What's New?

  • Role-based addresses for every client. crm, wiki, dataroom — named for the job, not the vendor, so the URL survives swapping the tool underneath. Live for all four clients to the extent each one has tools deployed: the-water-foundation (crm/wiki/dataroom), palmer-ai (crm/wiki), reach-edu (crm), lossless (crm).

  • Vendor nicknames as aliases. /palmer-ai/twenty redirects to /palmer-ai/crm. Resolved per-client, so a nickname only works for a client that actually runs that tool.

  • Everything below a service passes through — /<client>/crm/<anything> forwards to the same path upstream, query string intact.

  • clients.ts is data now. The four hardcoded *_url fields became a services[] list, and the homebase cards render from it. A new client with a new tool is a config entry, not a template edit.

The finding: a redirect can't carry a login

The obvious next step was to give the Claude connector the same treatment — lossless.at/<client>/crm/mcp instead of the Railway host. It doesn't work, for a reason worth writing down.

Authorization does not survive a cross-origin redirect. Clients are required to strip it when a redirect crosses an origin boundary, and they do. Measured against a header-echoing origin sitting behind the live redirect:

RequestWhat the upstream received
Direct to originBearer TESTTOKEN123
Through the redirect (default behavior)stripped
Through the redirect, curl --location-trustedBearer TESTTOKEN123

So a connector added at the pretty address would complete its login against the Railway host, store a token, and then get rejected forever — the token is discarded on every hop. The homebase therefore still hands out the Railway MCP URL, which is the address that actually works.

This sharpens the blocker recorded in Normalize-Paths-Everywhere.md. The previously-documented problem (OAuth discovery advertising absolute Railway URLs) is real but secondary; header stripping is the one that decides it. Consequence: the clean connector path can only come from proxying, never redirecting.

Gotchas

  1. Astro's CSRF guard silently broke the connector path. security.checkOrigin defaults on and returns 403 to any POST without a matching Origin — which is exactly what an agent client sends. Disabled, safe here because the portal has no forms and no writes. Re-evaluate the moment it accepts one.

  2. The passthrough was an open redirect on first write. /<client>/crm//evil.com resolves protocol-relative to evil.com. Leading / and \ are now stripped (the URL parser folds \ into / for http/https), and the built URL is re-checked against the intended origin before it is sent.

  3. 307, not 302. A 302 lets a client re-issue a POST as GET, which would break any API traffic through the path. 307 preserves method and body.

  4. cache-control: no-store on every redirect so moving a backend takes effect immediately instead of being pinned by an intermediary.

Postiz is the exception — it needs a host, not a path

The whole model assumes a tool only needs to be addressed by a nice URL, not served from one. Postiz breaks that.

It derives its auth cookie Domain= from FRONTEND_URL, and *.up.railway.app is a Public Suffix apex browsers refuse to set cookies against — so first-signup returns 200 and leaves the user logged out forever. A redirect lands the browser on that same rejected apex and changes nothing. Postiz stays parked until it gets a real custom host; the procedure is already written in client-stacks/the-water-foundation/postiz/stack.md. No Postiz entry was added to the portal, because a card linking to a login that can't succeed is worse than no card.

Naming: the handle is postiz, not social. Postiz is a cross-platform post planner — it publishes on a schedule; it is not where you read or monitor feeds. social is deliberately reserved for the social-perusing surface, which has no good open-source option and is expected to be built from scratch.

Files changed

  • hubs/lossless-at/src/config/clients.ts
  • hubs/lossless-at/src/lib/service-redirect.ts
  • hubs/lossless-at/src/pages/[client]/[service]/index.ts
  • hubs/lossless-at/src/pages/[client]/[service]/[...rest].ts
  • hubs/lossless-at/src/pages/[client]/index.astro
  • hubs/lossless-at/astro.config.mjs
  • context-v/issues/Normalize-Paths-Everywhere.md
  • context-v/decisions/lossless-at-path-based-homebase.md