-
Notifications
You must be signed in to change notification settings - Fork 23
Description
Summary
The Upstash Workflow SDK's recreateUserHeaders() function filters known CDN-specific headers (Cloudflare, Vercel, Render) before forwarding them via the Upstash-Forward-* mechanism. However, Fastly headers are not included in the exclusion list. When a workflow is hosted on Railway (which uses Fastly as its edge proxy since yesterday), Fastly tracking headers — most critically fastly-ff — are accumulated across workflow steps and eventually trigger Fastly's loop detection, returning a 503 error after ~4 steps.
Environment
- SDK:
@upstash/workflow - Hosting: Railway (Fastly edge proxy)
- QStash callbacks: each step callback passes through Railway/Fastly
- Triggered by: Railway introducing a Fastly CDN layer (previously worked fine)
Root Cause
recreateUserHeaders() forwards request headers to the next workflow step via Upstash-Forward-*. It filters some CDN headers but misses Fastly's:
fastly-ff
fastly-client
fastly-client-ip
fastly-ssl
fastly-temp-xff
cdn-loop
via
On each workflow step callback, Railway's Fastly edge appends a new entry to the existing Fastly-Ff header (rather than replacing it). Since the header is forwarded from step to step via Upstash-Forward-fastly-ff, the value grows with each step:
- Step 1:
Fastly-Ff: <pop1> - Step 2:
Fastly-Ff: <pop1>, <pop2> - Step 3:
Fastly-Ff: <pop1>, <pop2>, <pop3> - Step 4+: 503 — Pop visit count exceeded max threshold
This is why the failure is step-count dependent, not random — and why it only affects workflows with multiple steps.
Expected Behavior
recreateUserHeaders() should strip Fastly-specific headers the same way it currently strips Cloudflare/Vercel/Render headers, so they are never forwarded to subsequent steps via Upstash-Forward-*.
Suggested Fix
Add Fastly headers to the exclusion list in recreateUserHeaders():
const HEADERS_TO_STRIP = [
// existing entries...
// Fastly (Railway edge proxy)
'fastly-ff',
'fastly-client',
'fastly-client-ip',
'fastly-ssl',
'fastly-temp-xff',
'cdn-loop',
'via',
];Workaround (app-level)
For anyone hitting this today, strip the headers manually in your application before the request reaches the SDK:
// middleware.ts
const FASTLY_HEADERS_TO_STRIP = [
'fastly-ff',
'cdn-loop',
'fastly-client',
'fastly-client-ip',
'fastly-ssl',
'fastly-temp-xff',
'via',
];And optionally strip from the response too:
Impact
This affects all Railway users using Upstash Workflows with multi-step pipelines. The issue is silent until you have enough workflow steps to exceed Fastly's threshold — making it hard to diagnose without knowing the root cause.
Additional Context
We've reported this to Railway as well, suggesting they configure Fastly to strip/reset the Fastly-Ff header on incoming external requests. A fix in the SDK would be the most robust solution as it protects all Railway/Fastly users without requiring app-level workarounds.