From 983e8fc41a2f000ada9faaa3b6ba274538a91a10 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 30 Apr 2026 19:16:31 -0400 Subject: [PATCH 1/3] fix: set canonical User-Agent header format The User-Agent header is now always `workos-node/{VERSION}`, matching the canonical WorkOS Node SDK format used through the 6.x line. Three pieces of behavior added in later majors that broke conformance are removed: - Runtime detection no longer appended (e.g. `(node/v22.0.0)`) - The `appInfo` option is still accepted for backwards compatibility, but the value no longer affects the outgoing User-Agent header - The HTTP-client annotation injected by `addClientToUserAgent` (e.g. `/fetch`) is dropped Co-Authored-By: Claude Opus 4.7 (1M context) --- src/common/net/http-client.ts | 6 +----- src/sso/sso.spec.ts | 4 ++-- src/workos.spec.ts | 4 ++-- src/workos.ts | 15 ++------------- 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/src/common/net/http-client.ts b/src/common/net/http-client.ts index d251707b0..66aa2cb52 100644 --- a/src/common/net/http-client.ts +++ b/src/common/net/http-client.ts @@ -58,11 +58,7 @@ export abstract class HttpClient implements HttpClientInterface { ): Promise; addClientToUserAgent(userAgent: string): string { - if (userAgent.indexOf(' ') > -1) { - return userAgent.replace(/\b\s/, `/${this.getClientName()} `); - } else { - return `${userAgent}/${this.getClientName()}`; - } + return userAgent; } static getResourceURL( diff --git a/src/sso/sso.spec.ts b/src/sso/sso.spec.ts index cc7b3b4a0..a12e26467 100644 --- a/src/sso/sso.spec.ts +++ b/src/sso/sso.spec.ts @@ -379,7 +379,7 @@ describe('SSO', () => { 'application/x-www-form-urlencoded;charset=utf-8', ); expect(headers['User-Agent']).toMatch( - /^workos-node\/\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?\/fetch \(node\/v\d+\.\d+\.\d+\)$/, + /^workos-node\/\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/, ); expect(accessToken).toBe('01DMEK0J53CVMC32CK5SE0KZ8Q'); @@ -432,7 +432,7 @@ describe('SSO', () => { 'application/x-www-form-urlencoded;charset=utf-8', ); expect(headers['User-Agent']).toMatch( - /^workos-node\/\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?\/fetch \(node\/v\d+\.\d+\.\d+\)$/, + /^workos-node\/\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/, ); expect(accessToken).toBe('01DMEK0J53CVMC32CK5SE0KZ8Q'); diff --git a/src/workos.spec.ts b/src/workos.spec.ts index 83d07a97c..e33db5e96 100644 --- a/src/workos.spec.ts +++ b/src/workos.spec.ts @@ -165,7 +165,7 @@ describe('WorkOS', () => { const headers = fetchHeaders() as Record; expect(headers['User-Agent']).toMatch( - /^workos-node\/\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?\/fetch \(node\/v\d+\.\d+\.\d+\) fooApp: 1\.0\.0$/, + /^workos-node\/\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/, ); }); }); @@ -180,7 +180,7 @@ describe('WorkOS', () => { const headers = fetchHeaders() as Record; expect(headers['User-Agent']).toMatch( - /^workos-node\/\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?\/fetch \(node\/v\d+\.\d+\.\d+\)$/, + /^workos-node\/\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/, ); }); }); diff --git a/src/workos.ts b/src/workos.ts index d2ab92f17..d6506a523 100644 --- a/src/workos.ts +++ b/src/workos.ts @@ -48,7 +48,6 @@ import { ConflictException } from './common/exceptions/conflict.exception'; import { CryptoProvider } from './common/crypto/crypto-provider'; import { ParseError } from './common/exceptions/parse-error'; import { getEnv } from './common/utils/env'; -import { getRuntimeInfo } from './common/utils/runtime-info'; import { version as VERSION } from '../package.json' with { type: 'json' }; const DEFAULT_HOSTNAME = 'api.workos.com'; @@ -163,18 +162,8 @@ export class WorkOS { this.client = this.createHttpClient(this.options, userAgent); } - private createUserAgent(options: WorkOSOptions): string { - let userAgent: string = `workos-node/${VERSION}`; - - const { name: runtimeName, version: runtimeVersion } = getRuntimeInfo(); - userAgent += ` (${runtimeName}${runtimeVersion ? `/${runtimeVersion}` : ''})`; - - if (options.appInfo) { - const { name, version } = options.appInfo; - userAgent += ` ${name}: ${version}`; - } - - return userAgent; + private createUserAgent(_options: WorkOSOptions): string { + return `workos-node/${VERSION}`; } createWebhookClient() { From 5add2202fa3eb96dcce5966aa4b1e1bab7f2c444 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 22:03:04 +0000 Subject: [PATCH 2/3] Remove unused HTTP client name code --- src/common/interfaces/http-client.interface.ts | 1 - src/common/net/fetch-client.ts | 9 +-------- src/common/net/http-client.ts | 9 --------- src/workos.spec.ts | 2 +- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/common/interfaces/http-client.interface.ts b/src/common/interfaces/http-client.interface.ts index cc57c750b..d6f45c6d2 100644 --- a/src/common/interfaces/http-client.interface.ts +++ b/src/common/interfaces/http-client.interface.ts @@ -7,7 +7,6 @@ export type ResponseHeaderValue = string | string[]; export type ResponseHeaders = Record; export interface HttpClientInterface { - getClientName: () => string; get(path: string, options: RequestOptions): any; post( path: string, diff --git a/src/common/net/fetch-client.ts b/src/common/net/fetch-client.ts index 7cda1a249..cac7cd9bc 100644 --- a/src/common/net/fetch-client.ts +++ b/src/common/net/fetch-client.ts @@ -37,11 +37,6 @@ export class FetchHttpClient extends HttpClient implements HttpClientInterface { this._fetchFn = fetchFn.bind(globalThis); } - /** @override */ - getClientName(): string { - return 'fetch'; - } - async get( path: string, options: RequestOptions, @@ -259,9 +254,7 @@ export class FetchHttpClient extends HttpClient implements HttpClientInterface { 'Content-Type': 'application/json', ...this.options?.headers, ...headers, - 'User-Agent': this.addClientToUserAgent( - (userAgent || 'workos-node').toString(), - ), + 'User-Agent': (userAgent || 'workos-node').toString(), }, body: requestBody, signal: abortController?.signal, diff --git a/src/common/net/http-client.ts b/src/common/net/http-client.ts index 66aa2cb52..f5f2f81ed 100644 --- a/src/common/net/http-client.ts +++ b/src/common/net/http-client.ts @@ -18,11 +18,6 @@ export abstract class HttpClient implements HttpClientInterface { readonly options?: RequestInit, ) {} - /** The HTTP client name used for diagnostics */ - getClientName(): string { - throw new Error('getClientName not implemented'); - } - abstract get( path: string, options: RequestOptions, @@ -57,10 +52,6 @@ export abstract class HttpClient implements HttpClientInterface { options: RequestOptions, ): Promise; - addClientToUserAgent(userAgent: string): string { - return userAgent; - } - static getResourceURL( baseURL: string, path: string, diff --git a/src/workos.spec.ts b/src/workos.spec.ts index e33db5e96..89daded6f 100644 --- a/src/workos.spec.ts +++ b/src/workos.spec.ts @@ -171,7 +171,7 @@ describe('WorkOS', () => { }); describe('when no `appInfo` option is provided', () => { - it('adds the HTTP client name to the user-agent', async () => { + it('sets the canonical user-agent', async () => { fetchOnce('{}'); const workos = new WorkOS('sk_test'); From 5027a297a656026e88b5276176e6192db751254e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 22:09:54 +0000 Subject: [PATCH 3/3] Clarify app info user-agent test --- src/workos.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workos.spec.ts b/src/workos.spec.ts index 89daded6f..6fbd5e131 100644 --- a/src/workos.spec.ts +++ b/src/workos.spec.ts @@ -151,7 +151,7 @@ describe('WorkOS', () => { }); describe('when the `appInfo` option is provided', () => { - it('applies the configuration to the fetch client user-agent', async () => { + it('sets the canonical user-agent', async () => { fetchOnce('{}'); const workos = new WorkOS('sk_test', {