Skip to content

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

Open
zentron wants to merge 2 commits into
mainfrom
robe/ai-agent-user-agent
Open

zentron wants to merge 2 commits into
mainfrom
robe/ai-agent-user-agent

Conversation

@zentron

@zentron zentron commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Background

AI coding agents increasingly drive tools built on this client, including the Octopus MCP server, but the User-Agent doesn't identify that traffic. Unlike the Go and .NET clients, this client also has no environment detection at all today.

This ports the agent detection added to go-octopusdeploy in OctopusDeploy/go-octopusdeploy#419, so the server sees the same agent names from every client.

Results

When an agent is detected, the User-Agent gains an AIAgent/<Name> token between the library and the requesting app:

ts-octopusdeploy AIAgent/ClaudeCode GitHubActions (release;deploy;v3)
  • New aiAgent configuration option. This takes a lesson from the MCP server, which learns the connected agent from the MCP initialize handshake (clientInfo). That's a better signal than environment variables, so callers that know the agent can pass it in. It's mapped to a known name and takes precedence over environment detection.
  • Then AI_AGENT, the emerging cross-vendor convention, mapped to a known name by prefix (e.g. claude-code_2-1-322_agent → ClaudeCode).
  • Then vendor variables, checked in a fixed order: 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.
  • Only a normalised name is sent. An identifier that isn't recognised reports as AIAgent/Other, and raw values are never included.
  • Detection only runs in Node, inside the existing branch that sets the User-Agent. Browsers are unaffected.
  • No token is added when no agent is detected, so existing User-Agents are unchanged.

Testing

  • aiAgent.test.ts covers each agent's variables, value-matched rules, excluded variables, nested agents, AI_AGENT mapping and precedence, the caller-supplied agent, and that raw values never appear. createUserAgent.test.ts covers the assembled header.
  • A shared set of 20 inputs produced identical results in the Go, .NET and TypeScript implementations.
  • tsc and ESLint pass. I only ran the unit test suites, not the integration tests that need a live Octopus instance.

How to review this PR

The logic is in src/aiAgent.ts. createUserAgent.ts replaces the inline string building in axiosAdapter.ts. The agent list is ported from go-octopusdeploy, which is the reference copy; a comment at the top of the file points there.

Caveats

  • Detection is self-reported and spoofable, so it's suitable for analytics, not authorisation or audit decisions.
  • The server stores this header on events. Anything that parses it by position should tolerate the extra token.
  • A follow-up in the MCP server can pass clientInfo.name as aiAgent, so its requests carry the normalised name too.

Generated by Claude Code

Report the AI agent driving the client as its own User-Agent token,
between the library and the requesting app:

  ts-octopusdeploy AIAgent/ClaudeCode GitHubActions (release;deploy;v3)

Callers that know the agent from a protocol, such as an MCP server with
the connected client's clientInfo, can pass it as `aiAgent` in the client
configuration. That takes precedence over AI_AGENT, which takes
precedence over vendor-specific environment variables. Only a
normalised agent name is sent. The agent list is ported from
go-octopusdeploy, which is the reference implementation.

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

This branch has not been deployed

No deployments
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.

1 participant