From bcc6241fb3f725642e130ea90f590d8d273da42b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:39:33 +0000 Subject: [PATCH 1/6] Initial plan From d4d52a9288234e0528d0c277027d7e11e27608f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:48:48 +0000 Subject: [PATCH 2/6] Add orchestration ID support to git user-agent Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com> --- __test__/git-command-manager.test.ts | 90 ++++++++++++++++++++++++++++ dist/index.js | 10 +++- src/git-command-manager.ts | 12 +++- 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/__test__/git-command-manager.test.ts b/__test__/git-command-manager.test.ts index cea73d4dd..41bc31b0f 100644 --- a/__test__/git-command-manager.test.ts +++ b/__test__/git-command-manager.test.ts @@ -376,3 +376,93 @@ describe('Test fetchDepth and fetchTags options', () => { ) }) }) + +describe('git user-agent with orchestration ID', () => { + beforeEach(async () => { + jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn()) + jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn()) + }) + + afterEach(() => { + jest.restoreAllMocks() + // Clean up environment variable to prevent test pollution + delete process.env['ACTIONS_ORCHESTRATION_ID'] + }) + + it('should include orchestration ID in user-agent when ACTIONS_ORCHESTRATION_ID is set', async () => { + const orchId = 'test-orch-id-12345' + process.env['ACTIONS_ORCHESTRATION_ID'] = orchId + + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.18')) + } + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + // The user agent should be set with orchestration ID + // We can't directly inspect gitEnv, but we verify the git command was created successfully + expect(git).toBeDefined() + }) + + it('should sanitize invalid characters in orchestration ID', async () => { + const orchId = 'test (with) special/chars' + process.env['ACTIONS_ORCHESTRATION_ID'] = orchId + + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.18')) + } + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + // The user agent should be set with sanitized orchestration ID + // We can't directly inspect gitEnv, but we verify the git command was created successfully + expect(git).toBeDefined() + }) + + it('should not modify user-agent when ACTIONS_ORCHESTRATION_ID is not set', async () => { + delete process.env['ACTIONS_ORCHESTRATION_ID'] + + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.18')) + } + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + // The user agent should be set without orchestration ID + // We can't directly inspect gitEnv, but we verify the git command was created successfully + expect(git).toBeDefined() + }) +}) diff --git a/dist/index.js b/dist/index.js index b9b34d342..c913627bd 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1206,7 +1206,15 @@ class GitCommandManager { } } // Set the user agent - const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`; + let gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`; + // Append orchestration ID if set + const orchId = process.env['ACTIONS_ORCHESTRATION_ID']; + if (orchId) { + // Sanitize the orchestration ID to ensure it contains only valid characters + // Valid characters: 0-9, a-z, _, -, . + const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_'); + gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`; + } core.debug(`Set git useragent to: ${gitHttpUserAgent}`); this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent; }); diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index a45e15a86..8e457f5aa 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -730,7 +730,17 @@ class GitCommandManager { } } // Set the user agent - const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)` + let gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)` + + // Append orchestration ID if set + const orchId = process.env['ACTIONS_ORCHESTRATION_ID'] + if (orchId) { + // Sanitize the orchestration ID to ensure it contains only valid characters + // Valid characters: 0-9, a-z, _, -, . + const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_') + gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}` + } + core.debug(`Set git useragent to: ${gitHttpUserAgent}`) this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent } From dbf1fa69f17b18e65cdaf87ede2c8f0ba827f308 Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Thu, 8 Jan 2026 10:13:04 -0500 Subject: [PATCH 3/6] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/git-command-manager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 8e457f5aa..8d14d7b06 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -738,7 +738,9 @@ class GitCommandManager { // Sanitize the orchestration ID to ensure it contains only valid characters // Valid characters: 0-9, a-z, _, -, . const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_') - gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}` + if (sanitizedId.trim().length > 0) { + gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}` + } } core.debug(`Set git useragent to: ${gitHttpUserAgent}`) From 4b8779770e3f45b3258af89b68516010ba3b5776 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:39:33 +0000 Subject: [PATCH 4/6] Improve tests to verify user-agent content and handle empty sanitized IDs Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com> --- __test__/git-command-manager.test.ts | 74 +++++++++++++++++++++++++--- dist/index.js | 5 +- src/git-command-manager.ts | 3 +- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/__test__/git-command-manager.test.ts b/__test__/git-command-manager.test.ts index 41bc31b0f..5b21f318c 100644 --- a/__test__/git-command-manager.test.ts +++ b/__test__/git-command-manager.test.ts @@ -393,10 +393,13 @@ describe('git user-agent with orchestration ID', () => { const orchId = 'test-orch-id-12345' process.env['ACTIONS_ORCHESTRATION_ID'] = orchId + let capturedEnv: any = null mockExec.mockImplementation((path, args, options) => { if (args.includes('version')) { options.listeners.stdout(Buffer.from('2.18')) } + // Capture env on any command + capturedEnv = options.env return 0 }) jest.spyOn(exec, 'exec').mockImplementation(mockExec) @@ -410,19 +413,28 @@ describe('git user-agent with orchestration ID', () => { doSparseCheckout ) - // The user agent should be set with orchestration ID - // We can't directly inspect gitEnv, but we verify the git command was created successfully + // Call a git command to trigger env capture after user-agent is set + await git.init() + + // Verify the user agent includes the orchestration ID expect(git).toBeDefined() + expect(capturedEnv).toBeDefined() + expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( + `git/2.18 (github-actions-checkout) actions_orchestration_id/${orchId}` + ) }) it('should sanitize invalid characters in orchestration ID', async () => { const orchId = 'test (with) special/chars' process.env['ACTIONS_ORCHESTRATION_ID'] = orchId + let capturedEnv: any = null mockExec.mockImplementation((path, args, options) => { if (args.includes('version')) { options.listeners.stdout(Buffer.from('2.18')) } + // Capture env on any command + capturedEnv = options.env return 0 }) jest.spyOn(exec, 'exec').mockImplementation(mockExec) @@ -436,18 +448,62 @@ describe('git user-agent with orchestration ID', () => { doSparseCheckout ) - // The user agent should be set with sanitized orchestration ID - // We can't directly inspect gitEnv, but we verify the git command was created successfully + // Call a git command to trigger env capture after user-agent is set + await git.init() + + // Verify the user agent has sanitized orchestration ID (spaces, parentheses, slash replaced) expect(git).toBeDefined() + expect(capturedEnv).toBeDefined() + expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( + 'git/2.18 (github-actions-checkout) actions_orchestration_id/test__with__special_chars' + ) }) it('should not modify user-agent when ACTIONS_ORCHESTRATION_ID is not set', async () => { delete process.env['ACTIONS_ORCHESTRATION_ID'] + let capturedEnv: any = null + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.18')) + } + // Capture env on any command + capturedEnv = options.env + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + // Call a git command to trigger env capture after user-agent is set + await git.init() + + // Verify the user agent does NOT contain orchestration ID + expect(git).toBeDefined() + expect(capturedEnv).toBeDefined() + expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( + 'git/2.18 (github-actions-checkout)' + ) + }) + + it('should not append orchestration ID when it becomes empty after sanitization', async () => { + const orchId = '()///' + process.env['ACTIONS_ORCHESTRATION_ID'] = orchId + + let capturedEnv: any = null mockExec.mockImplementation((path, args, options) => { if (args.includes('version')) { options.listeners.stdout(Buffer.from('2.18')) } + // Capture env on any command + capturedEnv = options.env return 0 }) jest.spyOn(exec, 'exec').mockImplementation(mockExec) @@ -461,8 +517,14 @@ describe('git user-agent with orchestration ID', () => { doSparseCheckout ) - // The user agent should be set without orchestration ID - // We can't directly inspect gitEnv, but we verify the git command was created successfully + // Call a git command to trigger env capture after user-agent is set + await git.init() + + // Verify the user agent does NOT contain orchestration ID when sanitized ID is empty expect(git).toBeDefined() + expect(capturedEnv).toBeDefined() + expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( + 'git/2.18 (github-actions-checkout)' + ) }) }) diff --git a/dist/index.js b/dist/index.js index c913627bd..afcd3f82a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1213,7 +1213,10 @@ class GitCommandManager { // Sanitize the orchestration ID to ensure it contains only valid characters // Valid characters: 0-9, a-z, _, -, . const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_'); - gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`; + // Only append if sanitized ID contains at least one alphanumeric character + if (sanitizedId && /[a-z0-9]/i.test(sanitizedId)) { + gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`; + } } core.debug(`Set git useragent to: ${gitHttpUserAgent}`); this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent; diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 8d14d7b06..ea2302a9b 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -738,7 +738,8 @@ class GitCommandManager { // Sanitize the orchestration ID to ensure it contains only valid characters // Valid characters: 0-9, a-z, _, -, . const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_') - if (sanitizedId.trim().length > 0) { + // Only append if sanitized ID contains at least one alphanumeric character + if (sanitizedId && /[a-z0-9]/i.test(sanitizedId)) { gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}` } } From 818b5b307222770517d84ff07ab3ca8017472441 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:22:38 +0000 Subject: [PATCH 5/6] Simplify orchestration ID validation to accept any non-empty sanitized value Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com> --- __test__/git-command-manager.test.ts | 6 +++--- dist/index.js | 3 +-- src/git-command-manager.ts | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/__test__/git-command-manager.test.ts b/__test__/git-command-manager.test.ts index 5b21f318c..3d0afec3f 100644 --- a/__test__/git-command-manager.test.ts +++ b/__test__/git-command-manager.test.ts @@ -493,7 +493,7 @@ describe('git user-agent with orchestration ID', () => { ) }) - it('should not append orchestration ID when it becomes empty after sanitization', async () => { + it('should sanitize orchestration ID to underscores when it contains only invalid characters', async () => { const orchId = '()///' process.env['ACTIONS_ORCHESTRATION_ID'] = orchId @@ -520,11 +520,11 @@ describe('git user-agent with orchestration ID', () => { // Call a git command to trigger env capture after user-agent is set await git.init() - // Verify the user agent does NOT contain orchestration ID when sanitized ID is empty + // Verify the user agent contains orchestration ID with sanitized underscores expect(git).toBeDefined() expect(capturedEnv).toBeDefined() expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( - 'git/2.18 (github-actions-checkout)' + 'git/2.18 (github-actions-checkout) actions_orchestration_id/_____' ) }) }) diff --git a/dist/index.js b/dist/index.js index afcd3f82a..4eab86e75 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1213,8 +1213,7 @@ class GitCommandManager { // Sanitize the orchestration ID to ensure it contains only valid characters // Valid characters: 0-9, a-z, _, -, . const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_'); - // Only append if sanitized ID contains at least one alphanumeric character - if (sanitizedId && /[a-z0-9]/i.test(sanitizedId)) { + if (sanitizedId) { gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`; } } diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index ea2302a9b..eba285a97 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -738,8 +738,7 @@ class GitCommandManager { // Sanitize the orchestration ID to ensure it contains only valid characters // Valid characters: 0-9, a-z, _, -, . const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_') - // Only append if sanitized ID contains at least one alphanumeric character - if (sanitizedId && /[a-z0-9]/i.test(sanitizedId)) { + if (sanitizedId) { gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}` } } From 130335a4d96c0400355f31596d35eec14579530d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:30:31 +0000 Subject: [PATCH 6/6] Remove test for orchestration ID with only invalid characters Co-authored-by: TingluoHuang <1750815+TingluoHuang@users.noreply.github.com> --- __test__/git-command-manager.test.ts | 35 ---------------------------- 1 file changed, 35 deletions(-) diff --git a/__test__/git-command-manager.test.ts b/__test__/git-command-manager.test.ts index 3d0afec3f..23b88636e 100644 --- a/__test__/git-command-manager.test.ts +++ b/__test__/git-command-manager.test.ts @@ -492,39 +492,4 @@ describe('git user-agent with orchestration ID', () => { 'git/2.18 (github-actions-checkout)' ) }) - - it('should sanitize orchestration ID to underscores when it contains only invalid characters', async () => { - const orchId = '()///' - process.env['ACTIONS_ORCHESTRATION_ID'] = orchId - - let capturedEnv: any = null - mockExec.mockImplementation((path, args, options) => { - if (args.includes('version')) { - options.listeners.stdout(Buffer.from('2.18')) - } - // Capture env on any command - capturedEnv = options.env - return 0 - }) - jest.spyOn(exec, 'exec').mockImplementation(mockExec) - - const workingDirectory = 'test' - const lfs = false - const doSparseCheckout = false - git = await commandManager.createCommandManager( - workingDirectory, - lfs, - doSparseCheckout - ) - - // Call a git command to trigger env capture after user-agent is set - await git.init() - - // Verify the user agent contains orchestration ID with sanitized underscores - expect(git).toBeDefined() - expect(capturedEnv).toBeDefined() - expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( - 'git/2.18 (github-actions-checkout) actions_orchestration_id/_____' - ) - }) })