From 29b5c665f0e6a97d173f2b01bd652bde5fe44eba Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Fri, 31 Jul 2026 11:33:03 +0530 Subject: [PATCH] feat(webapp): add RUNTIME_API_ORIGIN to decouple runner traffic from external origin --- .server-changes/runtime-api-origin.md | 6 ++++++ apps/webapp/app/env.server.ts | 15 +++++++++++++++ .../environmentVariablesRepository.server.ts | 6 +++++- docs/self-hosting/env/webapp.mdx | 1 + hosting/docker/.env.example | 10 ++++++++++ hosting/docker/webapp/docker-compose.yml | 1 + hosting/k8s/helm/templates/webapp.yaml | 4 ++++ hosting/k8s/helm/values.yaml | 6 ++++++ 8 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .server-changes/runtime-api-origin.md diff --git a/.server-changes/runtime-api-origin.md b/.server-changes/runtime-api-origin.md new file mode 100644 index 00000000000..54edd511634 --- /dev/null +++ b/.server-changes/runtime-api-origin.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: feature +--- + +Add `RUNTIME_API_ORIGIN` env var to route managed runner traffic through an in-cluster URL, bypassing tracing gateways that rewrite the W3C `traceparent` header and break parent→child run links. diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 24ed0833d8e..660ea013a6e 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -239,6 +239,21 @@ const EnvironmentSchema = z // Global default for internalApiOriginEnabled when an org hasn't set it. INTERNAL_API_ORIGIN_ENABLED: z.string().default("0"), STREAM_ORIGIN: z.string().optional(), + // Origin that the webapp publishes to MANAGED (deployed) runner pods as + // both `TRIGGER_API_URL` and (as the first fallback) `TRIGGER_STREAM_URL`. + // When self-hosting behind a tracing-enabled gateway (Envoy/Istio/etc.) + // that rewrites the W3C `traceparent` on egress, point this at an + // in-cluster service URL so runner-to-webapp traffic stays inside the + // cluster and the parent->child run link in the trace tree is preserved. + // Intentionally NOT used for dev (CLI) task runs, which usually run on a + // developer's machine outside the cluster and would lose connectivity if + // forced onto an in-cluster URL. Empty string is normalized to unset so + // blank `${RUNTIME_API_ORIGIN:-}` passthroughs from caller environments + // don't short-circuit the `??` fallback chain. + RUNTIME_API_ORIGIN: z + .string() + .optional() + .transform((v) => v || undefined), ELECTRIC_ORIGIN: z.string().default("http://localhost:3060"), // A comma separated list of electric origins to shard into different electric instances by environmentId // example: "http://localhost:3060,http://localhost:3061,http://localhost:3062" diff --git a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts index c4dd631470d..e3ebef1424f 100644 --- a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts +++ b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts @@ -1157,6 +1157,10 @@ async function resolveOverridableOtelDevVariables( function resolveProdApiOrigin(runtimeEnvironment: RuntimeEnvironmentForEnvRepo): string { const publicOrigin = env.API_ORIGIN ?? env.APP_ORIGIN; + if (env.RUNTIME_API_ORIGIN) { + return env.RUNTIME_API_ORIGIN; + } + if (!env.INTERNAL_API_ORIGIN) { return publicOrigin; } @@ -1190,7 +1194,7 @@ async function resolveBuiltInProdVariables( // Deliberately not switched by internalApiOriginEnabled: streams are // long-lived connections served on their own path. key: "TRIGGER_STREAM_URL", - value: env.STREAM_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN, + value: env.RUNTIME_API_ORIGIN ?? env.STREAM_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN, }, { key: "TRIGGER_RUNTIME_WAIT_THRESHOLD_IN_MS", diff --git a/docs/self-hosting/env/webapp.mdx b/docs/self-hosting/env/webapp.mdx index 09416f0f38f..f6bd54d5573 100644 --- a/docs/self-hosting/env/webapp.mdx +++ b/docs/self-hosting/env/webapp.mdx @@ -19,6 +19,7 @@ mode: "wide" | `LOGIN_ORIGIN` | Yes | http://localhost:3030 | Login origin URL. Most likely the same as `APP_ORIGIN`. | | `API_ORIGIN` | No | `APP_ORIGIN` | API origin URL. | | `STREAM_ORIGIN` | No | `APP_ORIGIN` | Realtime stream origin URL. | +| `RUNTIME_API_ORIGIN` | No | — | In-cluster origin used by managed (deployed) runners for `TRIGGER_API_URL`/`TRIGGER_STREAM_URL`. Set this to keep runner-to-webapp traffic on an internal hop that bypasses tracing-enabled gateways (which rewrite the W3C `traceparent` header and break the run-detail tree). Leave unset to keep using `API_ORIGIN`/`STREAM_ORIGIN`. | | `ELECTRIC_ORIGIN` | No | http://localhost:3060 | Electric origin URL. | | **Postgres** | | | | | `DATABASE_URL` | Yes | — | PostgreSQL connection string. | diff --git a/hosting/docker/.env.example b/hosting/docker/.env.example index 4c7cf11bc70..a636dc39cfc 100644 --- a/hosting/docker/.env.example +++ b/hosting/docker/.env.example @@ -50,6 +50,16 @@ API_ORIGIN=http://localhost:8030 DEV_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8030/otel # You may need to set this when testing locally or when using the combined setup # API_ORIGIN=http://webapp:3000 +# Optional: origin advertised to MANAGED (deployed) runner pods as both +# TRIGGER_API_URL and TRIGGER_STREAM_URL (intentional: keeps all managed +# runner traffic on the same bypass hop). Dev (CLI) task runs are NOT +# affected -- they keep using API_ORIGIN/APP_ORIGIN so a developer running +# `trigger.dev dev` from outside the cluster doesn't lose connectivity. +# Set this to an in-cluster service URL when running behind a tracing-enabled +# gateway that rewrites the W3C `traceparent` header on egress (e.g. Envoy/ +# Istio with tracing on). If you need streams on a dedicated endpoint (CDN, +# etc.), keep RUNTIME_API_ORIGIN unset and use STREAM_ORIGIN instead. +# RUNTIME_API_ORIGIN=http://webapp:3000 # Webapp - memory management # - This sets the maximum memory allocation for Node.js heap in MiB (e.g. "4096" for 4GB) diff --git a/hosting/docker/webapp/docker-compose.yml b/hosting/docker/webapp/docker-compose.yml index 8d133cbeea5..3db565e7259 100644 --- a/hosting/docker/webapp/docker-compose.yml +++ b/hosting/docker/webapp/docker-compose.yml @@ -44,6 +44,7 @@ services: APP_ORIGIN: ${APP_ORIGIN:-http://localhost:8030} LOGIN_ORIGIN: ${LOGIN_ORIGIN:-http://localhost:8030} API_ORIGIN: ${API_ORIGIN:-http://localhost:8030} + RUNTIME_API_ORIGIN: ${RUNTIME_API_ORIGIN:-} ELECTRIC_ORIGIN: http://electric:3000 # Realtime streams v2, backed by the bundled s2-lite service. This powers # AI-agent token streaming and run streams. Point the endpoint at a hosted diff --git a/hosting/k8s/helm/templates/webapp.yaml b/hosting/k8s/helm/templates/webapp.yaml index 49acee98f4a..a46f440b85f 100644 --- a/hosting/k8s/helm/templates/webapp.yaml +++ b/hosting/k8s/helm/templates/webapp.yaml @@ -186,6 +186,10 @@ spec: value: {{ .Values.webapp.loginOrigin | quote }} - name: API_ORIGIN value: {{ .Values.webapp.apiOrigin | quote }} + {{- with .Values.webapp.runtimeApiOrigin }} + - name: RUNTIME_API_ORIGIN + value: {{ . | quote }} + {{- end }} - name: ELECTRIC_ORIGIN value: {{ include "trigger-v4.electric.url" . | quote }} {{- if include "trigger-v4.s2.enabled" . }} diff --git a/hosting/k8s/helm/values.yaml b/hosting/k8s/helm/values.yaml index e76a921279c..82187031f1b 100644 --- a/hosting/k8s/helm/values.yaml +++ b/hosting/k8s/helm/values.yaml @@ -73,6 +73,12 @@ webapp: appOrigin: "http://localhost:3040" loginOrigin: "http://localhost:3040" apiOrigin: "http://localhost:3040" + # Origin advertised to runner pods as TRIGGER_API_URL. + # When unset (default), runners use apiOrigin/appOrigin. Set this to an + # in-cluster service URL to keep runner->webapp traffic inside the cluster, + # bypassing gateways/proxies (e.g. Envoy with tracing enabled) that rewrite + # the W3C `traceparent` header on egress and break the parent->child run link. + runtimeApiOrigin: "" replicaCount: 1