Skip to content

Record agentic invocations in User-Agent header#13023

Merged
williammartin merged 1 commit into
trunkfrom
agent-in-header
Mar 24, 2026
Merged

Record agentic invocations in User-Agent header#13023
williammartin merged 1 commit into
trunkfrom
agent-in-header

Conversation

@williammartin

@williammartin williammartin commented Mar 24, 2026

Copy link
Copy Markdown
Member

Description

There has been a significant increase in agentic use of gh in the last few months and it's important we are able to identify that usage. This PR tries to detect which agent is invoking gh by checking well-known environment variables and includes the agent name in the User-Agent header sent to GitHub APIs.

Supported agents: Codex, Gemini CLI, Copilot CLI, OpenCode, Claude Code, and Amp. The generic AI_AGENT env var is also supported with validation ([a-zA-Z0-9_-]) to prevent header injection.

Format

GitHub CLI 2.x.y Agent/<agent-name>

Acceptance Criteria

Given I set GH_DEBUG=api in the environment
When I invoke gh in a shell tool in Claude Code CLI, Gemini CLI, Codex CLI, Copilot CLI, Amp, or OpenCode
Then I see that the User-Agent header in http requests contains the agent name

Running in copilot CLI:

GH_DEBUG=api gh pr view
│ * Request to https://api.github.com/graphql
│ > POST /graphql HTTP/1.1
│ > Host: api.github.com
│ > Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
│ > Authorization: token ████████████████████
│ > Content-Length: 461
│ > Content-Type: application/json
│ > Graphql-Features: merge_queue
│ > Time-Zone: Europe/Amsterdam
│ > User-Agent: GitHub CLI v2.88.1-4-gc1daf35db Agent/copilot-cli
│ > X-Github-Api-Version: 2022-11-28

Given I set GH_DEBUG=api in the environment
When I invoke gh directly (in copilot CLI, run with COPILOT_CLI unset in the environment)
Then I see that the User-Agent header in http requests does not contain an agent name

Running in shell:

GH_DEBUG=api gh pr view
* Request to https://api.github.com/graphql
> POST /graphql HTTP/1.1
> Host: api.github.com
> Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
> Authorization: token ████████████████████
> Content-Length: 1968
> Content-Type: application/json; charset=utf-8
> Graphql-Features: merge_queue
> Time-Zone: Europe/Amsterdam
> User-Agent: GitHub CLI 2.88.1
> X-Github-Api-Version: 2022-11-28

Comment thread api/client.go
)

const (
accept = "Accept"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Was unused.

@williammartin williammartin marked this pull request as ready for review March 24, 2026 17:16
@williammartin williammartin requested review from a team as code owners March 24, 2026 17:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds lightweight agent detection (via well-known environment variables) and threads the detected agent name into API client construction so that gh includes an Agent/<name> suffix in the User-Agent header for GitHub API requests, improving visibility into agentic CLI usage.

Changes:

  • Introduces internal/agents with Detect() plus tests for supported agent environment variable signals and precedence.
  • Extends factory and HTTP client plumbing to pass an invokingAgent value into api.NewHTTPClient, appending it to the User-Agent.
  • Updates call sites and tests for the new factory/HTTP client function signatures.

Reviewed changes

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

Show a summary per file
File Description
internal/agents/detect.go Adds agent detection + validation for AI_AGENT values.
internal/agents/detect_test.go Adds unit tests covering detection signals and precedence.
internal/ghcmd/cmd.go Wires detected agent into factory creation at CLI entrypoint.
api/http_client.go Adds InvokingAgent option and appends it to User-Agent.
api/http_client_test.go Adds test asserting agent suffix is included in User-Agent.
pkg/cmd/factory/default.go Threads invokingAgent through factory into HTTP client options.
pkg/cmd/factory/default_test.go Updates factory constructor usage for new parameter.
pkg/cmd/search/shared/shared_test.go Updates factory constructor usage for new parameter.
pkg/cmd/attestation/verify/verify_integration_test.go Updates factory constructor usage for new parameter.
api/client.go Removes an unused header constant.

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

Comment thread api/http_client.go
Comment on lines +52 to +55
ua := fmt.Sprintf("GitHub CLI %s", opts.AppVersion)
if opts.InvokingAgent != "" {
ua = fmt.Sprintf("%s Agent/%s", ua, opts.InvokingAgent)
}

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

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

opts.InvokingAgent is appended to the User-Agent verbatim. If a future caller passes an untrusted/unsanitized value (e.g. containing \r/\n or other invalid header characters), requests can fail with "invalid header field value" and this undermines the stated header-injection hardening. Consider validating/sanitizing opts.InvokingAgent inside NewHTTPClient (e.g. allow only [A-Za-z0-9_-]+ and otherwise omit the agent suffix), and add a small test for the invalid-value case.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nah we sanitize at the agent layer. I don't want to create a gh.AgentName just to deal with this.

@babakks babakks left a comment

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.

LGTM!

Comment thread internal/agents/detect.go
agentOpencode AgentName = "opencode"
)

var validAgentName = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)

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.

Maybe also dots, slashes (/ and ), and brackets? As some agent may decide to add versions (in brackets).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Purely based on what we've seen so far out in the wild, I'm inclined to leave this alone until we see something otherwise.

Detect which AI coding agent is invoking gh by checking well-known
environment variables and include the agent name in the User-Agent
header sent to GitHub APIs.

Supported agents: Codex, Gemini CLI, Copilot CLI, OpenCode,
Claude Code, and Amp. Generic AI_AGENT env var is also supported
with validation to prevent header injection.

Fixes github/cli#1111

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@williammartin

Copy link
Copy Markdown
Member Author

Bypassing package security review cause it's not that interesting.

@williammartin williammartin merged commit 1ea2952 into trunk Mar 24, 2026
11 checks passed
@williammartin williammartin deleted the agent-in-header branch March 24, 2026 19:28
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Apr 2, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [cli/cli](https://github.com/cli/cli) | minor | `v2.88.1` → `v2.89.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>cli/cli (cli/cli)</summary>

### [`v2.89.0`](https://github.com/cli/cli/releases/tag/v2.89.0): GitHub CLI 2.89.0

[Compare Source](cli/cli@v2.88.1...v2.89.0)

#### :copilot: `gh agent-task` now works on ghe.com tenancies

`gh agent-task` commands previously failed with `401 Unauthorized` for users on ghe.com tenancy hosts because the Copilot API URL was hardcoded. The URL is now resolved dynamically per host, so `gh agent-task` works correctly regardless of your GitHub hosting environment.

#### Experimental new prompter

A new TUI-based prompter powered by [charmbracelet/huh](https://github.com/charmbracelet/huh) is available behind the `GH_EXPERIMENTAL_PROMPTER` environment variable. This is an early preview — try it out and share feedback!

```
export GH_EXPERIMENTAL_PROMPTER=1
```

#### `gh issue create` and `gh issue transfer` no longer require extra token scopes

`gh issue create` and `gh issue transfer` previously fetched repository fields they didn't need, which could require additional token scopes. These commands now fetch only the minimal fields necessary for issue operations.

#### What's Changed

##### ✨ Features

- `gh pr create`, `gh issue create`, `gh issue edit`: search-based assignee selection and login-based mutation on github.com by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13009](cli/cli#13009)
- Add experimental huh-only prompter gated by `GH_EXPERIMENTAL_PROMPTER` by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;12859](cli/cli#12859)

##### 🐛 Fixes

- fix(agent-task): resolve Copilot API URL dynamically for ghe.com tenancies by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;12956](cli/cli#12956)
- fix(issue): avoid fetching unnecessary fields in `issue create` and `issue transfer` by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12884](cli/cli#12884)
- fix: resolve data race in codespaces port forwarder by [@&#8203;Lslightly](https://github.com/Lslightly) in [#&#8203;13033](cli/cli#13033)

##### 📚 Docs & Chores

- Record agentic invocations in User-Agent header by [@&#8203;williammartin](https://github.com/williammartin) in [#&#8203;13023](cli/cli#13023)
- docs: clarify that `gh pr edit --add-reviewer` can re-request reviews by [@&#8203;joshjohanning](https://github.com/joshjohanning) in [#&#8203;13021](cli/cli#13021)
- Add AGENTS.md by [@&#8203;williammartin](https://github.com/williammartin) in [#&#8203;13024](cli/cli#13024)
- Fix typo: remove extra space in README.md link by [@&#8203;realMelTuc](https://github.com/realMelTuc) in [#&#8203;12725](cli/cli#12725)
- Align triage.md with current triage process by [@&#8203;tidy-dev](https://github.com/tidy-dev) in [#&#8203;13030](cli/cli#13030)
- Remove auto-labels from issue templates by [@&#8203;tidy-dev](https://github.com/tidy-dev) in [#&#8203;12972](cli/cli#12972)
- Consolidate actor-mode signals into `ApiActorsSupported` by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13025](cli/cli#13025)
- Fix acceptance test failures: git identity, headRepository JSON, obsolete traversal test by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13037](cli/cli#13037)

##### :dependabot: Dependencies

- chore(deps): bump google.golang.org/grpc from 1.79.2 to 1.79.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;12963](cli/cli#12963)
- chore(deps): bump github.com/google/go-containerregistry from 0.20.7 to 0.21.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;12962](cli/cli#12962)
- chore(deps): bump github.com/zalando/go-keyring from 0.2.6 to 0.2.8 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13031](cli/cli#13031)
- chore(deps): bump microsoft/setup-msbuild from 2.0.0 to 3.0.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13005](cli/cli#13005)
- chore(deps): bump mislav/bump-homebrew-formula-action from 3.6 to 4.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13004](cli/cli#13004)
- chore(deps): bump azure/login from 2.3.0 to 3.0.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;12951](cli/cli#12951)

#### New Contributors

- [@&#8203;joshjohanning](https://github.com/joshjohanning) made their first contribution in [#&#8203;13021](cli/cli#13021)
- [@&#8203;realMelTuc](https://github.com/realMelTuc) made their first contribution in [#&#8203;12725](cli/cli#12725)
- [@&#8203;Lslightly](https://github.com/Lslightly) made their first contribution in [#&#8203;13033](cli/cli#13033)

**Full Changelog**: [v2.88.1...v2.89.0](cli/cli@v2.88.1...v2.89.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My45MS40IiwidXBkYXRlZEluVmVyIjoiNDMuOTEuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6Om1pbm9yIl19-->
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.

3 participants