Skip to content

feat: detect AI coding agents in the user agent - #419

Open
zentron wants to merge 4 commits into
mainfrom
robe/claude-ua
Open

zentron wants to merge 4 commits into
mainfrom
robe/claude-ua

Conversation

@zentron

@zentron zentron commented May 14, 2026 •

Copy link
Copy Markdown
Contributor

Background

AI coding agents increasingly drive the Octopus CLI, the Terraform provider and other tools built on this SDK, but none of that traffic is identifiable today. Agents don't change the headers of the tools they run; they only export environment variables to the processes they launch. Reading those variables here gives the server a signal for agent-driven API usage.

Results

The user agent gains an AIAgent/<Name> token after the automation environment when an agent is detected:

go-octopusdeploy/v2.x.x (linux; amd64) go/go1.24.7 GitHubActions AIAgent/ClaudeCode octopus/2.x.x (release;deploy)
  • AI_AGENT first. This is the emerging cross-vendor convention (@vercel/detect-agent). Its value is mapped to a known name by prefix, e.g. claude-code_2-1-322_agent → ClaudeCode, github_copilot_vscode_agent → GitHubCopilot. The raw value is never sent, since it is free-form and user-settable. An unrecognised value reports as AIAgent/Other.
  • Vendor variables as a fallback, checked in a fixed order so nested agents give a stable answer: Claude Code, Cursor, Codex, Gemini CLI, GitHub Copilot, OpenCode, Antigravity, Goose, Amp, Cline, Roo Code, Augment, Qwen Code, Factory Droid, Kimi CLI, Trae, Aider and OpenHands.
  • Noisy variables excluded. CURSOR_TRACE_ID, COPILOT_GITHUB_TOKEN, GOOSE_PROVIDER, REPL_ID and ZED_TERM are set in ordinary human sessions, so they would label people as agents.
  • Self-reported identifiers are matched after lowercasing and collapsing runs of underscores and whitespace into a hyphen, so Claude Code and claude_code map the same way.
  • No token is added when no agent is detected, so existing user agents are unchanged.

Testing

  • Unit tests in ai_agent_provider_test.go cover each agent's variables, value-matched rules, excluded variables, nested-agent ordering, AI_AGENT mapping and precedence, and that the raw AI_AGENT value never appears in the output. The existing CI detection tests still pass.
  • Checked against a real Claude Code session, which produced AIAgent/ClaudeCode.
  • The tests were run in an isolated copy of the package, because the authoring environment couldn't download module dependencies, so this PR's CI run is the first full build.

How to review this PR

The logic is all in pkg/services/api/ai_agent_provider.go, and the api.go change is the four lines that append the token. The agent list is the part most worth a second opinion: the variable names come from other detectors (.NET Aspire's CodingAgentDetector, @vercel/detect-agent, is-ai-agent) and are mostly undocumented vendor internals. This file is the reference copy of the list: the TypeScript port (api-client.ts#294) and the .NET port point back to it in a comment.

Caveats

  • Detection is self-reported and spoofable. It's suitable for analytics, not for authorisation or audit decisions.
  • Coverage depends on the list. A new agent shows up as Other (if it sets AI_AGENT) or not at all until it is added and an SDK release ships.
  • AGENT=amp comes from a secondary source. If Amp doesn't set it, that rule never fires.
  • The server stores this header on events. Anything that parses it by position should tolerate the extra token.

Generated by Claude Code

@NickJosevski NickJosevski 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.

you still want this in?

@CLAassistant

CLAassistant commented Nov 12, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@zentron zentron changed the title Add ClaudeCode environment variable support feat: detect AI coding agents in the user agent Nov 12, 2026
zentron and others added 2 commits November 13, 2026 08:57
Report the AI agent driving a process as its own user agent token,
separate from the automation (CI) environment, so an agent running in
CI or on an Octopus worker reports both rather than one at random.

- Honour the emerging AI_AGENT convention, mapping known values to a
  canonical name and passing the sanitised raw value through as a comment
- Fall back to vendor variables for Claude Code, Cursor, Codex, Gemini
  CLI, GitHub Copilot, OpenCode, Antigravity, Goose, Amp, Cline, Roo
  Code, Augment, Qwen Code, Factory Droid, Kimi CLI, Trae, Aider and
  OpenHands, checked in a fixed order
- Exclude variables that are set without an agent driving the process
  (e.g. CURSOR_TRACE_ID, COPILOT_GITHUB_TOKEN, REPL_ID)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ijpGHFpAqBBMhn5WKUq2
Map AI_AGENT to one of the known agent names instead of passing the raw
value through as a user agent comment. Unrecognised values report as
AIAgent/Other. The raw value is free-form and user-settable, so it is now
only used for matching, which also removes the need to sanitise it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ijpGHFpAqBBMhn5WKUq2
Collapse runs of underscores and whitespace into a single hyphen before
matching a self-reported agent identifier, so a name such as
"Claude Code" maps the same way in all three clients.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ijpGHFpAqBBMhn5WKUq2
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