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.
Related Issues
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-codePerformance 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 forCoding-Agent/. A newUserAgent(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 givesclient_name(lowercased) andclient_version, and theCoding-Agent/andDistribution/tags give the rest. RequestContextholdsUserAgentinstead of the raw string. The analytics observer andbuild_querytake 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_typeclaim (User#user_type:human,project_bot,service_account, ...). The server ignored it becauseClaimshad no field. It now readsuser_type, andtoken_type(personal_access_tokenoroauth_access_token, absent for browser sessions), which gitlab-org/gitlab!264924 (merged) adds. Both useserde(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_distributionreadsGITLAB_ORBIT_DISTRIBUTION=glab, which glab already sets. Theglab orbitlauncher text now uses it too.orbit_cli1-0-1 addsdistribution.
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.