Skip to content

Ensure api and auth commands record agentic invocations#13046

Merged
babakks merged 4 commits intotrunkfrom
wm/gh-api-agent
Mar 27, 2026
Merged

Ensure api and auth commands record agentic invocations#13046
babakks merged 4 commits intotrunkfrom
wm/gh-api-agent

Conversation

@williammartin
Copy link
Copy Markdown
Member

@williammartin williammartin commented Mar 26, 2026

Description

When we added the invoking agent to the User-Agent header in API requests, we missed two spots.

Firsty, gh api constructs its own http client inline, so we needed to thread that down through the factory like we do with appVersion.

Secondly, the gh auth login starts using the plainHTTPClient (which does have the InvokingAgent set) but after obtaining a token, it constructed a new client to getViewer that dropped configuration on the existing client. This is why we had a lot of empty versions when looking at request logs. Now we copy it and wrap the transport to include the newly obtained token.

The gh api command builds its own HTTP client inline without
forwarding InvokingAgent, so the User-Agent header was missing
the Agent/<name> suffix when invoked by AI coding agents.

Thread InvokingAgent through Factory → ApiOptions → HTTPClientOptions,
mirroring the existing AppVersion pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR ensures the detected invoking agent is consistently included in the User-Agent header for gh api requests and for the auth flow’s post-login getViewer request, addressing gaps from the original invoking-agent rollout.

Changes:

  • Add InvokingAgent to cmdutil.Factory and wire it through the default factory constructor.
  • Thread InvokingAgent into gh api’s HTTP client construction and add a regression test for User-Agent.
  • Update auth flow getViewer to reuse the provided HTTP client and add a test to confirm User-Agent/auth header behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pkg/cmdutil/factory.go Adds InvokingAgent to the shared factory so commands can access it.
pkg/cmd/factory/default.go Populates Factory.InvokingAgent from the factory constructor input.
pkg/cmd/api/api.go Plumbs InvokingAgent into api.HTTPClientOptions used by gh api.
pkg/cmd/api/api_test.go Adds coverage to assert gh api includes Agent/<name> in User-Agent.
internal/authflow/flow.go Changes getViewer to reuse the caller’s HTTP client and wrap transport with auth.
internal/authflow/flow_test.go Adds a regression test ensuring getViewer preserves UA and sets Authorization.
AGENTS.md Documents running go test ./... and make lint before committing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/authflow/flow.go
@williammartin williammartin changed the title Propagate InvokingAgent to gh api and authflow User-Agent Ensure api and auth commands record agentic invocations Mar 26, 2026
williammartin and others added 2 commits March 26, 2026 17:26
getViewer was building a new HTTP client from scratch, losing
AppVersion and InvokingAgent from the plain client already passed
into AuthFlow. Reuse the existing client by shallow-copying it and
wrapping its transport with AddAuthTokenHeader for the new token.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@williammartin williammartin marked this pull request as ready for review March 26, 2026 17:30
@williammartin williammartin requested a review from a team as a code owner March 26, 2026 17:30
@williammartin williammartin requested a review from babakks March 26, 2026 17:30
Copy link
Copy Markdown
Member

@babakks babakks left a comment

Choose a reason for hiding this comment

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

LGTM. Just a comment on one of the tests.

Comment thread internal/authflow/flow_test.go Outdated
Comment on lines +17 to +35
// Outer transport sets User-Agent, simulating the factory-built client's header middleware.
// Inner transport captures headers as-received to verify they survived the wrapping.
plainClient := &http.Client{
Transport: &roundTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", "GitHub CLI 1.2.3 Agent/copilot-cli")
return (&http.Client{
Transport: &roundTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
receivedUA = req.Header.Get("User-Agent")
receivedAuth = req.Header.Get("Authorization")
return &http.Response{
StatusCode: 200,
Header: http.Header{"Content-Type": []string{"application/json"}},
Body: io.NopCloser(bytes.NewBufferString(`{"data":{"viewer":{"login":"monalisa"}}}`)),
Request: req,
}, nil
}},
}).Transport.RoundTrip(req)
}},
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know it's meant to simulate the actual transport middlewares, but it seems like a trivial assertion, when we set the UA header in the outer transport and then we capture it in the inner one.

In other words, the test should only assert the auth header is set.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the point with the user-agent assertion is to ensure the header isn't being mutated. I know it's a trivial assertion, but I guess tests are cheap. If we really care that this user agent isn't mutated in between somehow, it seems like a fair idea.

BTW, (nit) I would prefer if this test code was a bit more readable by breaking apart this code.

	// Inner transport captures headers as-received to verify they survived the wrapping.
	innerTransport := &roundTripper{
		roundTrip: func(req *http.Request) (*http.Response, error) {
			receivedUA = req.Header.Get("User-Agent")
			receivedAuth = req.Header.Get("Authorization")
			return &http.Response{
				StatusCode: 200,
				Header:     http.Header{"Content-Type": []string{"application/json"}},
				Body:       io.NopCloser(bytes.NewBufferString(`{"data":{"viewer":{"login":"monalisa"}}}`)),
				Request:    req,
			}, nil
		},
	}

	// Outer transport sets User-Agent, simulating the factory-built client's header middleware.
	plainClient := &http.Client{
		Transport: &roundTripper{
			roundTrip: func(req *http.Request) (*http.Response, error) {
				req.Header.Set("User-Agent", "GitHub CLI 1.2.3 Agent/copilot-cli")
				return innerTransport.RoundTrip(req)
			},
		},
	}

Copy link
Copy Markdown
Member

@babakks babakks Mar 27, 2026

Choose a reason for hiding this comment

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

Yeah, I thought about that, too. But it can't be the case either. Because the new transport that getViewer makes, will wrap the HTTP client's transport (the two-layer we build in the test). I mean, there's no way for getViewer to add a transport in the middle of the chain.

We don't need the two layers. Only one is enough. However, we can check the UA header is not set, as an assertion that getViewer has nothing to do wth it (and therefore further transport layers see the UA header is unset, and would then safely add it).

Copy link
Copy Markdown
Member

@BagToad BagToad left a comment

Choose a reason for hiding this comment

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

LGTM, small nit on the tests

Signed-off-by: Babak K. Shandiz <babakks@github.com>
Copy link
Copy Markdown
Member

@babakks babakks left a comment

Choose a reason for hiding this comment

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

Changed the discussed test. Pinging @BagToad for a review.

@babakks babakks requested a review from BagToad March 27, 2026 11:51
sungdark pushed a commit to sungdark/claude-builders-bounty that referenced this pull request Mar 27, 2026
CLI: claude-review --pr <URL>
GitHub Action: .github/workflows/review.yml for automated PR reviews
Structured Markdown output: Summary / Risks / Suggestions / Confidence Score
Tested on 2 real GitHub PRs (cli/cli#13046, cli/cli#13048)

Payment address: eB51DWp1uECrLZRLsE2cnyZUzfRWvzUzaJzkatTpQV9
@babakks babakks merged commit 68c6d9e into trunk Mar 27, 2026
11 checks passed
@babakks babakks deleted the wm/gh-api-agent branch March 27, 2026 17:25
@t72053166-eng t72053166-eng mentioned this pull request Apr 17, 2026
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.

4 participants