← Context/ issues
Every remote hardcodes the workspace WebSocket to localhost — Org Workbench loads no data on augment.didi.sh
Sixteen remotes dial `ws://localhost:3001/ws` with no env read. On prod that points at the visitor's own laptop, so the socket shows `closed` and the roster never fills.
Why Care?
On https://augment.didi.sh, the Org Workbench surface for the
reach-edu workspace renders its chrome — title, SurrealDB · Organizations
badge, client: reach-edu, search box, + New organization — and then shows
nothing. No coverage roster, no organizations, no people. A red closed
pill sits in the top-right corner. The same surface, same workspace, same commit
works perfectly on localhost:3100.
That "works local, dead on prod" split is the tell, and it is not a data problem, a SurrealDB problem, or a tenancy problem. The remote is asking the visitor's own laptop for its data.
The root cause — one line, no env read
apps/org-workbench/src/App.svelte:20:
const WS_URL = 'ws://localhost:3001/ws';That is the whole bug. Compare the pattern the shell, chat, and
corpora-curator all use correctly — for example shell/src/App.svelte:41-43:
const WS_URL =
((import.meta as { env?: Record<string, string> }).env?.PUBLIC_WS_URL as string | undefined) ||
'ws://localhost:3001/ws';rsbuild inlines PUBLIC_-prefixed vars into import.meta.env at build time.
Org Workbench never performs that read, so no build-time value can reach it.
The deployment looks correctly configured, which is what makes this
expensive to spot. apps/org-workbench/Dockerfile:23-28 faithfully declares
and exports the variable:
ARG PUBLIC_WS_URL
ENV PUBLIC_WS_URL=$PUBLIC_WS_URLand Railway sets it per DEPLOYMENT. Every rung of the config chain is green. The value simply lands in a build environment that no source line ever consults, and is dropped on the floor.
Two independent reasons it can never work in production
Wrong host.
localhost:3001in a browser onaugment.didi.shresolves to the viewer's machine, not Railway'sworkspace-service. It works on the operator's laptop for the accidental reason that the laptop really is runningworkspace-serviceon:3001— the local stack masks the defect perfectly.Mixed content. Even if a viewer did run the backend locally, an insecure
ws://connection is blocked outright by every modern browser when the page origin ishttps://. Prod needswss://ws.augment.didi.sh/ws.
Blast radius — this is a family defect, not one surface
Sixteen remotes carry the identical hardcoded constant:
| App | Line | Deployed to prod? |
org-workbench | App.svelte:20 | yes — broken, this report |
search-and-add | App.svelte:22 | yes — same break |
search-results | App.svelte:16 | yes — same break |
corpora-curator | App.svelte:9 | yes — works anyway, see below |
chat | App.svelte:20 | yes — correct, reads env with localhost fallback |
record-collector, records-surface, pack-runner, sort-filter-lens, person-db-resolver, record-db-resolver, affiliation-rating-resolver, enhanced-records-list, prompt-template-manager, request-reviewer, response-reviewer | various | no — latent, will break on the day they deploy |
The entire Augment-from-DB flow is down on prod, not just Org Workbench —
org-workbench, search-and-add, and search-results are the three services
that flow comprises, and all three share the bug.
Two nuances worth recording:
corpora-curatorworks by luck of file layout. ItsApp.svelte:9has the same dead hardcoded constant, but its real client lives insrc/curation.svelte.ts:21-23, which does readPUBLIC_WS_URL. The unused constant inApp.svelteis a live trap for the next person who wires a socket there.chatis the reference implementation.apps/chat/src/App.svelte:16-20gets it exactly right, comment included.
This is a different axis from Move Remaining Remotes To Remote Hosting Prod Falls Back To Localhost.
That issue is about where the shell fetches each remote's remoteEntry.js
asset. This one is about where an already-loaded remote opens its data
socket. Org Workbench proves they are independent: its asset is properly
hosted on Railway and loads fine — then it dials localhost for data.
Why the symptom reads as "no data" rather than "error"
apps/org-workbench/src/App.svelte:38 models the socket as:
let status = $state<'connecting' | 'open' | 'closed' | 'error' | 'auth_required'>('connecting');The closed badge in the corner is that state, faithfully rendered. But the
main pane does not branch on it — it keeps showing the neutral instructional
copy, "Pick an organization from the coverage roster on the left (fewest corpus
items first), or search above…", inviting the operator to use a roster that can
never populate. The UI tells the truth in a 60px pill and lies in the 1200px
region next to it. See Live Not Live Indicator Tooling And Cross Service Error Surfacing
and No User Visibility Into State Needs A State Inspector.
The fix
Replace the constant in all sixteen apps with the env-reading form. This is mechanical and identical everywhere;
chatis the template to copy.Delete the dead constant in
corpora-curator/src/App.svelte:9so it cannot be picked up by accident.Set
PUBLIC_WS_URL=wss://ws.augment.didi.sh/wson theorg-workbench,search-and-add, andsearch-resultsRailway services, then rebuild —PUBLIC_*is baked at build time, sorailway redeploy --service <name> --from-sourceis required. A restart will not do it.Make the empty state honest — when
statusisclosed/error, the main pane should say the connection failed, not invite a roster pick.Guard it so it cannot regress. Two cheap options: extend
scripts/design-drift.mjs(or add a sibling lint) with a rule banning literalws://localhostoutside a fallback expression, or assert on it in the test harness named in No Test Coverage TDD Deferred Despite Agentic Fit. Without a guard this returns the next time a remote is scaffolded by copy-paste.
Suggested verification
Per the browser-drive discipline in CLAUDE.md, the click-path is: load
https://augment.didi.sh, sign in, switch workspace to Reach Edu, open the
Org Workbench flow, and assert the connection pill reads open and the
coverage roster renders ≥1 organization. That drive currently fails at the pill
and is the regression test for this fix.
Related
Move Remaining Remotes To Remote Hosting Prod Falls Back To Localhost — sibling deployment defect, different axis
Search And Add Invokes Never Reach The Workspace — same flow; worth re-checking whether its prod symptom is actually this
Domain Type Is Ambient State So A Failed Workspace Load Hides Every Corpus — the same failure-hidden-behind-a-neutral-empty-state shape
A Failed Deploy Is Silent Nothing Watches Production After Merge — why this survived undetected on prod
Live Not Live Indicator Tooling And Cross Service Error Surfacing