fix(env): put runtime config on <html> so client reads can't outrun it - #6923
Conversation
The inline script that assigns `window.__ENV` is rendered from the component tree, so it lands ~13KB after the `<script async>` bootstrap tags React emits in the preamble. `appBootstrap` calls `hydrate()` synchronously whenever `self.__next_s` is empty — which it always is now that the script is a plain tag rather than a `beforeInteractive` one, that queue having been the only thing sequencing the assignment ahead of hydration. So module bodies and the first commit could both read env before the assignment landed: the socket URL fell back to the page origin for the life of the document, `getBaseUrl()` threw, and every module-scope flag in `env-flags` froze on nothing. Carry the same snapshot on `<html>`, the document's first tag, and read it in `getEnv` when `window.__ENV` is not yet assigned. Parsing is memoized against the raw attribute rather than against having run once, so the cache can never serve a value the document no longer carries. `window.__ENV` stays the public global and the preferred read, and both transports are built from one function so they cannot drift. Alongside: guard the read-only webhook-URL field so a base URL it cannot resolve is a blank field rather than a dead canvas; report what the workflow error boundary catches, which it previously swallowed entirely; and enable PostHog's native exception capture, since error boundaries only ever see their own subtree and chunk-load failures, rejected promises and throws from event or socket callbacks reached nothing.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview
Separately, previously silent failures now report: PostHog Reviewed by Cursor Bugbot for commit 9e7772e. Configure here. |
Greptile SummaryThe PR makes runtime public configuration available from the initial
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/core/config/env.ts | Adds a cached, malformed-input-tolerant HTML-attribute fallback before the existing process environment fallback. |
| apps/sim/app/_shell/public-env-script.tsx | Generates the runtime public-environment payload shared by the initial HTML attribute and global assignment. |
| apps/sim/app/layout.tsx | Places the public runtime-environment payload on the root HTML element. |
| apps/sim/app/workspace/providers/socket-provider.tsx | Counts connection failures only from connect_error and emits one persistent-failure analytics event per successful-connection cycle. |
| apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx | Reports workflow canvas errors through structured logging and PostHog. |
| apps/sim/app/_shell/providers/posthog-provider.tsx | Enables PostHog capture for unhandled browser errors and rejected promises while excluding console errors. |
Reviews (3): Last reviewed commit: "fix(workflow): let an unresolvable webho..." | Re-trigger Greptile
`manager.reconnect()` calls `open()`, whose error path emits `error` — which the socket re-emits as `connect_error` — and then emits `reconnect_error` itself. A failed reconnect therefore reached both handlers and advanced the counter twice, so the outage report tripped on the second real attempt while claiming three. Count in `connect_error` alone: it is the only handler that fires exactly once for both the initial failure and every retry. `reconnect_error` keeps its log line and states why it deliberately does not count.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 99fee82. Configure here.
Reverts the guard added earlier in this PR. A blank row titled "Webhook URL" is a worse outcome than a crash: it explains nothing, and the value is one a user copies into a third-party provider, so any substitute — a guessed page origin, an empty string — is a URL that provider accepts and then never delivers to. The read can no longer come back empty from the hydration race this PR fixes, so reaching it at all means the deployment has no application base URL, which breaks webhook registration and callbacks regardless. The error boundary now reports what it caught, so the throw names its own cause instead of surfacing as an unexplained fallback.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9e7772e. Configure here.
Summary
NEXT_PUBLIC_*config now rides on the<html>element as well as the inline script.<html>is the document's first tag, so the values are readable by any client code that can run at all.getEnvreads that attribute whenwindow.__ENVhas not been assigned yet.window.__ENVstays the public global and the preferred read; both transports are built from one function so they cannot drift.window.__ENVlands ~13KB after the<script async>bootstrap tags, andappBootstraphydrates synchronously wheneverself.__next_sis empty — always, now that the script is a plain tag rather than abeforeInteractiveone. Reads that fell in that window resolved to nothing: the socket URL fell back to the page origin for the life of the document,getBaseUrl()threw, and every module-scope flag inenv-flagsfroze wrong.getDerivedStateFromError, so a canvas crash left no trace anywhere.capture_exceptionsfor unhandled errors and rejections. Error boundaries only see their own subtree, so chunk-load failures, rejected promises and throws from event or socket callbacks reached nothing.capture_console_errorsstays off — it would capture the hydration warnings we already filter as noise.realtime_connection_failing, once per socket after 3 consecutive failures, carrying the resolved origin. A socket that never connects raises no exception, so this class was previously invisible; the origin separates "realtime is unreachable" from "this client resolved the wrong host". Counted inconnect_erroronly — a failed reconnect also emits the manager'sreconnect_error, so counting both advanced twice per attempt.Type of Change
Testing
Tested manually. Added 6 tests for the browser env resolution order (attribute fallback,
window.__ENVprecedence, per-key fall-through, re-read on attribute change, malformed attribute, absent key) and 3 pinning that the<html>payload matches the script's. Tests pass acrosslib/core/config,app/_shell,app/workspace/providers,lib/posthogand the workflow editor tree (409 in the editor tree alone).type-check,lint, the block-registry audit and all 32check:auditspass.Checklist