fix(networking): expose the workflow stream headers to cross-origin JS - #810
fix(networking): expose the workflow stream headers to cross-origin JS#810sweetmantech wants to merge 1 commit into
Conversation
getCorsHeaders set Access-Control-Allow-Headers but no Access-Control-Expose-Headers, so browsers hid every non-safelisted response header from cross-origin JS. Two consequences, both live: - x-workflow-run-id has been documented as part of the 200 on the chat endpoints since the workflow cutover and has NEVER been readable by chat.recoupable.dev. We have been shipping a header no browser caller could see. - x-workflow-stream-tail-index, added in #809, reads as null from the browser. Confirmed on preview 2026-08-03 during chat#1924 verification. The AI SDK's WorkflowChatTransport reads the tail header to anchor relative resume positions, so it would fail silently against us today — falling back to startIndex 0 and replaying the whole stream. Refs recoupable/chat#1923 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe CORS response now exposes ChangesCORS Header Exposure
Estimated code review effort: 1 (Trivial) | ~2 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
No issues found across 2 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant Browser as Browser (chat.recoupable.dev)
participant CORS as CORS Middleware
participant API as API Server
participant WF as Workflow Service
Note over Browser,WF: Cross-Origin Chat Stream Request
Browser->>CORS: POST /api/chat (preflight OPTIONS)
CORS->>CORS: getCorsHeaders()
CORS-->>Browser: Access-Control-Allow-Headers, Access-Control-Expose-Headers
Browser->>API: POST /api/chat (actual request)
API->>CORS: Apply CORS headers
CORS->>CORS: Include exposed headers in response
API->>WF: Start workflow stream
WF-->>API: Stream with x-workflow-run-id, x-workflow-stream-tail-index
API-->>Browser: 200 + CORS headers + SSE stream
Note over Browser: JS can now read x-workflow-run-id and x-workflow-stream-tail-index
Browser->>Browser: WorkflowChatTransport reads x-workflow-stream-tail-index
alt Header readable
Browser->>Browser: Calculate resume position from header
else Header hidden (previous behavior)
Browser->>Browser: Falls back to startIndex: 0, replays entire stream
end
Browser->>API: GET /api/chat/{chatId}/stream (resume)
API->>CORS: Apply expose headers
API->>WF: Resume from tail index
WF-->>API: Continue stream
API-->>Browser: Partial stream from resume point
Auto-approved: Adds a single CORS response header to expose existing workflow headers to browser JS. This is a configuration fix that only affects browser visibility; no behavioral, security, or operational tradeoffs.
Re-trigger cubic
Row 5 of chat#1923. One line, but it fixes a contract we have not been honouring.
The gap
getCorsHeaderssetsAccess-Control-Allow-Headers— which governs request headers — but neverAccess-Control-Expose-Headers. Browsers hide every non-safelisted response header from cross-origin JS unless it is named there.Two live consequences:
x-workflow-run-idhas never been readable by the chat client. It is documented as part of the 200 onPOST /api/chatandGET /api/chat/{chatId}/stream, and has been since the workflow cutover. Every browser caller has been unable to see it.x-workflow-stream-tail-indexreads asnull. Confirmed on preview 2026-08-03 while verifying chat#1924: the header was present on the wire, invisible to JS.The second is the sharper one going forward. The AI SDK's
WorkflowChatTransportreads that header to anchor relative resume positions; against us today it would fail silently, falling back tostartIndex: 0and replaying the entire stream rather than erroring.Why chat#1924 still worked
It counts SSE frames off the wire instead of trusting the header — a choice forced by a different constraint (headers are sent before the body, so no header can report where a stream ended). So this is not a regression in that PR; it unblocks the follow-up work that does need the header.
Scope
Applies to every endpoint using
getCorsHeaders, which is the intended blast radius: exposing these two response headers leaks nothing — both are already sent on the wire, and this only lets the browser's JS layer read what the network already delivered.Tests
RED before GREEN. 2 cases: the existing
allow-*headers are untouched, and both workflow headers are exposed.tsc --noEmitclean in the touched file;eslintclean.Merge order
Independent of docs#287, which documents the header itself. Unblocks the negative-
startIndexrefresh row in chat#1923.Refs chat#1923
🤖 Generated with Claude Code
Summary by cubic
Expose workflow stream response headers to cross-origin JS so browser clients can read
x-workflow-run-idandx-workflow-stream-tail-indexand resume streams correctly.Access-Control-Expose-HeadersingetCorsHeadersforx-workflow-run-idandx-workflow-stream-tail-index(applies to all endpoints using it).allow-*headers and exposure of both workflow headers.Written for commit c5106a4. Summary will update on new commits.