-
-
Notifications
You must be signed in to change notification settings - Fork 813
Description
Provide environment information
System:
OS: Windows 11 10.0.26100
CPU: (24) x64 Intel(R) Core(TM) i7-14650HX
Memory: 21.86 GB / 63.78 GB
Binaries:
Node: 22.11.0 - C:\Program Files\nodejs\node.EXE
npm: 10.9.0 - C:\Program Files\nodejs\npm.CMD
bun: 1.1.38 - C:\Program Files\nodejs\bun.CMD
Describe the bug
Summary
The retry.fetch
wrapper from @trigger.dev/sdk
doesn't preserve headers that are set by HTTP client libraries like openapi-fetch
. This causes API requests to fail with "Missing or incorrect Content-Type header" errors when using retry functionality.
Environment
- @trigger.dev/sdk version: [your version]
- Runtime: Bun 1.1.38
- OS: Windows
- HTTP Client: openapi-fetch
Expected Behavior
When using retry.fetch
as a custom fetch implementation with HTTP client libraries, headers set by the client (either in initial config or middleware) should be preserved across retry attempts.
Actual Behavior
Headers set by HTTP client libraries are stripped during retry attempts, causing requests to fail with missing headers.
Reproduction Steps
- Create an
openapi-fetch
client with headers in config:
import createClient from "openapi-fetch";
import { retry } from "@trigger.dev/sdk";
const client = createClient({
baseUrl: "https://api.example.com",
headers: {
"Authorization": "Bearer token",
"Content-Type": "application/json",
},
fetch: retry.fetch, // Using retry.fetch
});
- Make a request that triggers a retry (e.g., to an endpoint that returns 500):
const result = await client.POST("/api/endpoint", {
body: { data: "test" }
});
- Observe that the retry attempts are missing the
Authorization
andContent-Type
headers.
Root Cause Analysis
Looking at the retry.fetch
source code, the issue is in the doFetchRequest
function:
const response = await fetch(input, {
...init,
headers: {
...init?.headers, // Only preserves ORIGINAL headers from init
"x-retry-count": attemptCount.toString(),
},
});
The retry mechanism only spreads init?.headers
(the original headers), completely ignoring any headers that were modified by HTTP client middleware or merged by the client library.
Workaround
We had to create a custom fetch wrapper:
const retryFetchWithHeaders = async (
input: RequestInfo | URL,
init?: RequestInit
): Promise<Response> => {
const headers = new Headers(init?.headers);
// Ensure required headers are present
if (!headers.has('Authorization')) {
headers.set('Authorization', `Bearer ${API_TOKEN}`);
}
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
// Convert Headers to plain object for retry.fetch
const headersObj: Record<string, string> = {};
headers.forEach((value, key) => {
headersObj[key] = value;
});
return retry.fetch(input, {
...init,
headers: headersObj,
});
};
Suggested Fix
The doFetchRequest
function should preserve all headers from the incoming request, not just the original init?.headers
. Consider:
const response = await fetch(input, {
...init,
headers: {
...Object.fromEntries(new Headers(init?.headers).entries()),
"x-retry-count": attemptCount.toString(),
},
});
Or better yet, use the Headers API properly to merge headers without losing any that were set by middleware.
Impact
This affects any HTTP client library that:
- Sets headers in middleware (like
openapi-fetch
,axios
interceptors, etc.) - Merges headers from multiple sources
- Relies on dynamic header setting
Additional Context
This is particularly problematic for OpenAPI-based clients where type-safe headers and authentication are crucial for API functionality. The current behavior makes retry.fetch
incompatible with modern HTTP client libraries.
Reproduction repo
Included in description
To reproduce
Included in description
Additional information
No response