Skip to content

fix(networking): expose the workflow stream headers to cross-origin JS - #810

Open
sweetmantech wants to merge 1 commit into
mainfrom
fix/expose-workflow-stream-headers
Open

fix(networking): expose the workflow stream headers to cross-origin JS#810
sweetmantech wants to merge 1 commit into
mainfrom
fix/expose-workflow-stream-headers

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Row 5 of chat#1923. One line, but it fixes a contract we have not been honouring.

The gap

getCorsHeaders sets Access-Control-Allow-Headers — which governs request headers — but never Access-Control-Expose-Headers. Browsers hide every non-safelisted response header from cross-origin JS unless it is named there.

Two live consequences:

  1. x-workflow-run-id has never been readable by the chat client. It is documented as part of the 200 on POST /api/chat and GET /api/chat/{chatId}/stream, and has been since the workflow cutover. Every browser caller has been unable to see it.
  2. x-workflow-stream-tail-index reads as null. 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 WorkflowChatTransport reads that header to anchor relative resume positions; against us today it would fail silently, falling back to startIndex: 0 and 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.

  • Full api suite: 4,331 tests passing.
  • tsc --noEmit clean in the touched file; eslint clean.

Merge order

Independent of docs#287, which documents the header itself. Unblocks the negative-startIndex refresh 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-id and x-workflow-stream-tail-index and resume streams correctly.

  • Bug Fixes
    • Add Access-Control-Expose-Headers in getCorsHeaders for x-workflow-run-id and x-workflow-stream-tail-index (applies to all endpoints using it).
    • Prevents silent full replays by transports that rely on the tail index.
    • Tests cover unchanged allow-* headers and exposure of both workflow headers.

Written for commit c5106a4. Summary will update on new commits.

Review in cubic

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>
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview Aug 3, 2026 7:43pm

Request Review

@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

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.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 11d60569-a5c6-471c-83ed-0975c183f490

📥 Commits

Reviewing files that changed from the base of the PR and between 88ab640 and c5106a4.

⛔ Files ignored due to path filters (1)
  • lib/networking/__tests__/getCorsHeaders.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (1)
  • lib/networking/getCorsHeaders.ts

📝 Walkthrough

Walkthrough

The CORS response now exposes x-workflow-run-id and x-workflow-stream-tail-index to cross-origin clients through Access-Control-Expose-Headers.

Changes

CORS Header Exposure

Layer / File(s) Summary
Expose workflow headers
lib/networking/getCorsHeaders.ts
The CORS helper adds workflow run ID and stream tail index headers to Access-Control-Expose-Headers and documents the change.

Estimated code review effort: 1 (Trivial) | ~2 minutes

Poem

Two workflow headers step into the light,
Across the origin, clear and bright.
Run IDs flow, tail indexes too,
CORS now makes their values viewable anew.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Solid & Clean Code ✅ Passed getCorsHeaders remains one focused, 15-line function in its matching file; the change adds one direct header entry, preserves existing headers, and introduces no duplication or nesting.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/expose-workflow-stream-headers

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant