Skip to content

Commit ada42a7

Browse files
chore: send standard identity headers on cy-prompt/studio session requests (#34039)
The cy-prompt and studio session requests sent only Content-Type, x-os-name, and x-cypress-version, so cloud-side telemetry on these endpoints could not attribute traffic by machine (x-machine-id was absent from the spans). Add a shared getStandardHeaders() helper that returns the standard cloud identity headers (x-os-name, x-cypress-version, x-machine-id) and use it in both session helpers. Headers are resolved once before the retry loop so the machine id is not re-read on each attempt. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6017154 commit ada42a7

6 files changed

Lines changed: 88 additions & 22 deletions

File tree

packages/server/lib/cloud/api/cy-prompt/post_cy_prompt_session.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { asyncRetry, linearDelay } from '../../../util/async_retry'
22
import { isRetryableError } from '../../network/is_retryable_error'
3-
import os from 'os'
43
import { ParseKinds, postFetch } from '../../network/fetch'
4+
import { getStandardHeaders } from '../get_standard_headers'
55

6-
const pkg = require('@packages/root')
76
const routes = require('../../routes') as typeof import('../../routes')
87

98
interface PostCyPromptSessionOptions {
@@ -13,14 +12,15 @@ interface PostCyPromptSessionOptions {
1312
const _delay = linearDelay(500)
1413

1514
export const postCyPromptSession = async ({ projectId }: PostCyPromptSessionOptions) => {
15+
const headers = {
16+
'Content-Type': 'application/json',
17+
...(await getStandardHeaders()),
18+
}
19+
1620
return await (asyncRetry(() => {
1721
return postFetch<{ cyPromptUrl: string }>(routes.apiRoutes.cyPromptSession(), {
1822
parse: ParseKinds.JSON,
19-
headers: {
20-
'Content-Type': 'application/json',
21-
'x-os-name': os.platform(),
22-
'x-cypress-version': pkg.version,
23-
},
23+
headers,
2424
body: JSON.stringify({ projectSlug: projectId, cyPromptMountVersion: 2 }),
2525
})
2626
}, {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os from 'os'
2+
import { machineId } from '../machine_id'
3+
4+
const pkg = require('@packages/root')
5+
6+
/**
7+
* The standard identity headers sent on cloud requests so the backend can
8+
* attribute traffic by Cypress version, OS, and machine. The recording-service
9+
* records these on its Honeycomb request spans.
10+
*
11+
* `x-machine-id` resolves to an empty string when the machine id is
12+
* unavailable, matching the behavior of other cloud requests.
13+
*/
14+
export const getStandardHeaders = async (): Promise<Record<string, string>> => {
15+
return {
16+
'x-os-name': os.platform(),
17+
'x-cypress-version': pkg.version,
18+
'x-machine-id': await machineId() ?? '',
19+
}
20+
}

packages/server/lib/cloud/api/studio/post_studio_session.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { asyncRetry, linearDelay } from '../../../util/async_retry'
22
import { isRetryableError } from '../../network/is_retryable_error'
3-
import os from 'os'
43
import { ParseKinds, postFetch } from '../../network/fetch'
4+
import { getStandardHeaders } from '../get_standard_headers'
55

6-
const pkg = require('@packages/root')
76
const routes = require('../../routes') as typeof import('../../routes')
87

98
interface PostStudioSessionOptions {
@@ -13,14 +12,15 @@ interface PostStudioSessionOptions {
1312
const _delay = linearDelay(500)
1413

1514
export const postStudioSession = async ({ projectId }: PostStudioSessionOptions) => {
15+
const headers = {
16+
'Content-Type': 'application/json',
17+
...(await getStandardHeaders()),
18+
}
19+
1620
return await (asyncRetry(() => {
1721
return postFetch<{ studioUrl: string, protocolUrl: string }>(routes.apiRoutes.studioSession(), {
1822
parse: ParseKinds.JSON,
19-
headers: {
20-
'Content-Type': 'application/json',
21-
'x-os-name': os.platform(),
22-
'x-cypress-version': pkg.version,
23-
},
23+
headers,
2424
body: JSON.stringify({ projectSlug: projectId, studioMountVersion: 1, protocolMountVersion: 2 }),
2525
})
2626
}, {

packages/server/test/unit/cloud/api/cy-prompt/post_cy_prompt_session_spec.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { SystemError } from '../../../../../lib/cloud/network/system_error'
22
import { proxyquire } from '../../../../spec_helper'
3-
import os from 'os'
4-
import pkg from '@packages/root'
53
import { ParseKinds } from '../../../../../lib/cloud/network/fetch'
64
import sinon from 'sinon'
75

6+
const standardHeaders = {
7+
'x-os-name': 'test-os',
8+
'x-cypress-version': 'test-version',
9+
'x-machine-id': 'test-machine-id',
10+
}
11+
812
describe('postCyPromptSession', () => {
913
let postCyPromptSession: typeof import('@packages/server/lib/cloud/api/cy-prompt/post_cy_prompt_session').postCyPromptSession
1014
let postFetchStub: sinon.SinonStub = sinon.stub()
@@ -15,6 +19,9 @@ describe('postCyPromptSession', () => {
1519
'../../network/fetch': {
1620
postFetch: postFetchStub,
1721
},
22+
'../get_standard_headers': {
23+
getStandardHeaders: sinon.stub().resolves(standardHeaders),
24+
},
1825
}) as typeof import('@packages/server/lib/cloud/api/cy-prompt/post_cy_prompt_session')).postCyPromptSession
1926
})
2027

@@ -38,8 +45,7 @@ describe('postCyPromptSession', () => {
3845
parse: ParseKinds.JSON,
3946
headers: {
4047
'Content-Type': 'application/json',
41-
'x-os-name': os.platform(),
42-
'x-cypress-version': pkg.version,
48+
...standardHeaders,
4349
},
4450
body: JSON.stringify({ projectSlug: '12345', cyPromptMountVersion: 2 }),
4551
},
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { proxyquire } from '../../../spec_helper'
2+
import os from 'os'
3+
import pkg from '@packages/root'
4+
import sinon from 'sinon'
5+
6+
describe('getStandardHeaders', () => {
7+
const loadWithMachineId = (machineId: string | null) => {
8+
return (proxyquire('@packages/server/lib/cloud/api/get_standard_headers', {
9+
'../machine_id': {
10+
machineId: sinon.stub().resolves(machineId),
11+
},
12+
}) as typeof import('@packages/server/lib/cloud/api/get_standard_headers')).getStandardHeaders
13+
}
14+
15+
it('returns the standard identity headers', async () => {
16+
const getStandardHeaders = loadWithMachineId('test-machine-id')
17+
18+
expect(await getStandardHeaders()).to.deep.equal({
19+
'x-os-name': os.platform(),
20+
'x-cypress-version': pkg.version,
21+
'x-machine-id': 'test-machine-id',
22+
})
23+
})
24+
25+
it('falls back to an empty x-machine-id when the machine id is unavailable', async () => {
26+
const getStandardHeaders = loadWithMachineId(null)
27+
28+
expect(await getStandardHeaders()).to.deep.equal({
29+
'x-os-name': os.platform(),
30+
'x-cypress-version': pkg.version,
31+
'x-machine-id': '',
32+
})
33+
})
34+
})

packages/server/test/unit/cloud/api/studio/post_studio_session_spec.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { SystemError } from '../../../../../lib/cloud/network/system_error'
22
import { proxyquire } from '../../../../spec_helper'
3-
import os from 'os'
4-
import pkg from '@packages/root'
53
import { ParseKinds } from '../../../../../lib/cloud/network/fetch'
64
import sinon from 'sinon'
75

6+
const standardHeaders = {
7+
'x-os-name': 'test-os',
8+
'x-cypress-version': 'test-version',
9+
'x-machine-id': 'test-machine-id',
10+
}
11+
812
describe('postStudioSession', () => {
913
let postStudioSession: typeof import('@packages/server/lib/cloud/api/studio/post_studio_session').postStudioSession
1014
let postFetchStub: sinon.SinonStub = sinon.stub()
@@ -15,6 +19,9 @@ describe('postStudioSession', () => {
1519
'../../network/fetch': {
1620
postFetch: postFetchStub,
1721
},
22+
'../get_standard_headers': {
23+
getStandardHeaders: sinon.stub().resolves(standardHeaders),
24+
},
1825
}) as typeof import('@packages/server/lib/cloud/api/studio/post_studio_session')).postStudioSession
1926
})
2027

@@ -40,8 +47,7 @@ describe('postStudioSession', () => {
4047
parse: ParseKinds.JSON,
4148
headers: {
4249
'Content-Type': 'application/json',
43-
'x-os-name': os.platform(),
44-
'x-cypress-version': pkg.version,
50+
...standardHeaders,
4551
},
4652
body: JSON.stringify({ projectSlug: '12345', studioMountVersion: 1, protocolMountVersion: 2 }),
4753
},

0 commit comments

Comments
 (0)