Skip to content

Report the client environment on API requests - #100

Closed
kylegani wants to merge 1 commit into
mainfrom
kyle/agent-env-header
Closed

Report the client environment on API requests#100
kylegani wants to merge 1 commit into
mainfrom
kyle/agent-env-header

Conversation

@kylegani

Copy link
Copy Markdown
Contributor

Why

Deploy attribution today cannot tell an agent-driven deploy from a human one. X-Source: cli and X-CLI-Version are already sent on every request, but neither says what invoked the CLI, so every automated deploy lands in the same bucket as an engineer at a terminal. The mission-control agents page reports a ~32% automation share of deploying principals against a ~70% automation share of build volume, and that gap is currently an undifferentiated blob: service accounts used by CI, by coding agents, and by internal tooling all look identical.

What

A new internal/clientenv package classifies the invoking environment and emits it as X-Client-Env alongside the existing source and version headers. Three shapes:

  • agent:<name> for a detected coding agent (claude, cowork, cursor, cursor-cli, codex, gemini, antigravity, augment-cli, opencode, github-copilot, devin, plus anything self-declared via AI_AGENT)
  • ci for a CI system (CI plus 13 vendor-specific variables)
  • interactive otherwise

The header goes on the JSON API client, the multipart RunApp path, and both device-auth endpoints. Value is computed once per process via sync.Once.

Design constraints held

Detection never influences behaviour. No output-format default, no confirmation-prompt suppression, no branch anywhere reads this classification. It is observational only, which is why the non-TTY deploy guard shipped in #87 keys off stdin TTY-ness rather than agent detection. Detect takes its environment lookup and filesystem probe as parameters so the whole matrix is testable without touching the real environment.

AI_AGENT values are untrusted. Lowercased, restricted to [a-z0-9._-], capped at 64 characters. A value that sanitizes to nothing becomes agent:unknown rather than silently degrading to interactive, so a declared-but-unparseable agent still lands in the agent bucket.

REPL_ID is deliberately not a detector. It reports that the process runs on Replit, not that an agent drives it, so keying on it would classify humans working in the Replit editor as agents. A Replit agent that sets AI_AGENT is still detected. This reverses an earlier draft of this branch that mapped REPL_ID to agent:replit; the test now pins the corrected behaviour both ways.

Agent detection precedes CI detection, because an agent running inside a CI job is more specifically an agent, and the hosted-agent surfaces we care about all set CI too.

Rider: DO_NOT_TRACK

pkg/bugsnag previously ignored DO_NOT_TRACK and only honoured the CLI's own telemetry config. It now disables error reporting when DO_NOT_TRACK is set to anything other than empty, 0, or false, matching the config-based path.

X-Client-Env itself is still sent under DO_NOT_TRACK. It carries no identity, no machine identifier and no path: it is one enum value on an already-authenticated request, in the same class as the X-Source and X-CLI-Version headers that have always been sent unconditionally. Flagging it here as a deliberate call rather than an oversight.

Validation

  • go test ./internal/clientenv/... ./internal/api/... ./internal/auth/... ./pkg/bugsnag/... green. 30+ detection cases, header-presence tests on both the API client and the OAuth endpoints, and a DO_NOT_TRACK matrix.
  • gofmt clean. golangci-lint on the touched packages reports one finding, nilerr in internal/auth/docker.go, verified pre-existing by re-running against a stash of this branch.
  • Repo-wide make lint and go build ./... fail on vendored cortex/server/node_modules AWS CDK templates, unrelated to this change and present on main.

Depends on

Nothing to work, but the header is only stored once dashboard-backend PR #4126 lands. Until then it is accepted and discarded.

Adds an X-Client-Env header alongside the existing source and version
headers, reporting whether the CLI was invoked by a named agent, by CI,
or interactively. Deploy attribution currently cannot distinguish an
agent-driven deploy from a human one.

Detection reads the environment only and never influences behaviour,
output defaults or confirmation prompts. DO_NOT_TRACK now also disables
error reporting, which previously ignored it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylegani

Copy link
Copy Markdown
Contributor Author

Superseded by #101: rebranched to feat/cli-client-env-header to match the repo's branch and conventional-commit conventions, and the detector matrix was reworked against the vercel/detect-agent registry (8 agents were missing, 4 identifiers diverged, 3 env vars unread).

@kylegani kylegani closed this Aug 23, 2026
@kylegani
kylegani deleted the kyle/agent-env-header branch August 23, 2026 10:33
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