feat(analytics): record the client behind each Orbit query

What does this MR do and why?

Query analytics label every API caller outside the UI, Duo, and MCP as rest, so glab, the Orbit CLI, scripts, and bots look the same in Snowflake. Telling them apart needed the Rails logs in Kibana. We need that split to decide which entry points to keep stable. Query events now record the HTTP client, whether glab launched the CLI, the user type, and the credential type.

What the new fields record

Examples of User-Agents that call the Orbit API today, and what the query event records for each:

Caller client_name client_version Other fields
Orbit CLI in a CI job orbit 0.151.0 user_type: project_bot
Orbit CLI inside an agent orbit 0.154.1 coding_agent: claude-code, pi, cursor, or gitlab-lsp_…__duo-cli
glab orbit (after the next CLI release) orbit 0.159.0 distribution: glab
glab api inside an agent glab v1.114.0 coding_agent: claude-code or opencode
Python scripts python-requests, python-httpx, python-urllib 2.34.2, 0.28.1, 3.14 token_type: personal_access_token
Shell scripts curl 8.7.1
Node.js scripts node
Java clients apache-httpclient 5.5.2
Third-party connectors openai_gitlabconnector 0.1 token_type: oauth_access_token
Browsers mozilla 5.0
MCP clients (mcp source type) go-http-client, node, gitlab-workhorse-mcp-client

A User-Agent with characters other than letters, digits, and . _ + -, or longer than 64 characters, records null. The OS in the User-Agent comment, such as (linux, x86_64), is not recorded.

Uses the schemas from https://gitlab.com/gitlab-org/iglu/-/merge_requests/220 (merged). gitlab-org/gitlab!264924 (merged) sends the token type claim; until it ships, that field stays null.

Testing

Unit tests for the User-Agent parser, the CLI User-Agent, the new claims, and the schema validation of the new fields. Before and after for the CLI User-Agent:

before: orbit/0.158.0 (macos, aarch64) Coding-Agent/claude-code
after:  orbit/0.158.0 (macos, aarch64) Distribution/glab Coding-Agent/claude-code

Performance Analysis

One split of a short header for each request.

  • This merge request does not introduce any performance regression. If a performance regression is expected, explain why.
Agent context: long-form analysis, file-by-file walkthroughs, profiler output, alternatives considered

Server

  • Rails already forwards the caller's User-Agent as x-client-user-agent. The server parsed it only for Coding-Agent/. A new UserAgent (src/user_agent.rs, top level because the gRPC reader, the request context, analytics, and billing all read it) parses it once per request: the first token gives client_name (lowercased) and client_version, and the Coding-Agent/ and Distribution/ tags give the rest.
  • RequestContext holds UserAgent instead of the raw string. The analytics observer and build_query take it in place of the coding agent string. Billing still gets only the coding agent, so the billing payload does not change.

Caller credentials

  • Rails already sends a user_type claim (User#user_type: human, project_bot, service_account, ...). The server ignored it because Claims had no field. It now reads user_type, and token_type (personal_access_token or oauth_access_token, absent for browser sessions), which gitlab-org/gitlab!264924 (merged) adds. Both use serde(default), so a JWT without them still parses.
  • Both go into the event with a lenient parse: an oversized value drops the field, not the event.

CLI

  • detect_distribution reads GITLAB_ORBIT_DISTRIBUTION=glab, which glab already sets. The glab orbit launcher text now uses it too.
  • orbit_cli 1-0-1 adds distribution.

Schemas

orbit_query 2-2-1 to 2-2-3 also brings the skip field and the has_cursor deprecation from 2-2-2. This MR does not set skip. The vendored files are byte-identical to the merged Iglu schemas. The large diff of the vendored 2-2-3 file comes from the formatting of the old vendored 2-2-1 copy.

Not in scope: how the CLI was run (terminal, script, CI; dropped in review), the real endpoint in tool_name, the response format, and MCP client names from the MCP handshake.

Edited by Jean-Gabriel Doyon

Merge request reports

Loading
Loading