Skip to content

feat(telemetry): send x-githits-* client headers on every API request - #24

Merged
jlitola merged 1 commit into
mainfrom
feat/telemetry-headers
Apr 21, 2026
Merged

feat(telemetry): send x-githits-* client headers on every API request#24
jlitola merged 1 commit into
mainfrom
feat/telemetry-headers

Conversation

@jlitola

@jlitola jlitola commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Emits four x-githits-* HTTP headers on every API request (REST + GraphQL) plus an MCP clientInfo hook. Gives the backend a reliable signal for distinguishing direct-CLI usage from MCP-mode usage, tracking which AI agents / IDEs drive the CLI, and grouping requests by terminal session — all without leaking raw identifiers.

Four headers, two transport layers

Header Value
x-githits-client-name githits-cli (direct CLI) / githits-cli/mcp (MCP server mode)
x-githits-client-version Read from package.json (0.1.9)
x-githits-agent name[/version] — from GITHITS_AGENT env, MCP clientInfo, or auto-detect
x-githits-session-id SHA-256 truncated to 64 bits; cached per process

Emitted on both:

  • RESTGitHitsService /search, /languages, /feedbacks via the shared headers() builder.
  • GraphQLpostPkgseerGraphql spreads into the fetch headers alongside Authorization / Content-Type.

Agent detection (priority order)

  1. setAgentInfo() — explicit runtime override.
  2. MCP clientInfo via a lazy provider (when running as MCP server). The MCP SDK sets _clientVersion synchronously inside _oninitialize before the initialize response is returned, so every tool call arriving after the handshake sees a populated value. Using a lazy provider — rather than an oninitialized notification callback — avoids the race where the first tool call can slip through before the async notification dispatches.
  3. Env-var auto-detect: OPENCODE, CLAUDECODE, CURSOR_TRACE_ID, WINDSURF_CONFIG_DIR, ZED_TERM, VSCODE_PID (first match wins).
  4. Explicit GITHITS_AGENT=name/version env — overrides auto-detect but not setAgentInfo.

Session-id detection

Most-specific terminal-session env var wins: TERM_SESSION_ID, ITERM_SESSION_ID, WEZTERM_PANE, KITTY_PID, ALACRITTY_SOCKET, WT_SESSION, VSCODE_PID, SUPERSET_PANE_ID, SUPERSET_WORKSPACE_ID, STARSHIP_SESSION_KEY, SSH_CONNECTION → parent PID → process PID → random UUID. Raw value is hashed (SHA-256, truncated to 16 hex chars / 64 bits) before ever touching the wire.

Safety invariants

  • Every header value passes through sanitizeHeaderValue — strips C0 / C1 control chars, trims, enforces a 256-byte cap. Blank or oversized values are dropped rather than truncated to a half-value.
  • buildClientHeaders() is try/catch-wrapped around the full body and returns {} on any internal error — API requests never fail because of a header-generation problem.
  • Header function is evaluated per-request, so values set after client creation (setAgentInfo, setMcpClientVersionProvider) reach subsequent requests.
  • MCP clientInfo provider is try/catch-wrapped internally — SDK access failures degrade to env detection and never crash the MCP session.
  • Header spread order on both transports puts x-githits-* first, then hardcoded Authorization / Content-Type / User-Agent, so no hypothetical collision could drop the hardcoded headers.

Testing

  • 71 unit tests covering session-id resolution priority, caching, agent env-detection + override, MCP clientInfo provider (precedence, per-call invocation, throwing-provider fallback, explicit-override win), agent-string parsing, header sanitisation, and buildClientHeaders composition.
  • Transport-level assertions — new tests in pkgseer-graphql.test.ts and githits-service.test.ts capture request headers and pin that x-githits-* values actually land on the wire, not just that the builder emits them.
  • MCP integrationmcp.test.ts asserts startMcpServer flips clientMode to githits-cli/mcp and registers the lazy MCP clientInfo provider. afterEach cleanup prevents test-state pollution across files.
  • Full suite: 1401 pass.
  • Typecheck / lint / build clean.

Live-smoke

Ran a mock-fetch harness against postPkgseerGraphql with:

GITHITS_AGENT=smoke/1.2.3 TERM_SESSION_ID=smoke-session-abc …

Confirmed on the wire:

x-githits-client-name: githits-cli
x-githits-client-version: 0.1.9
x-githits-agent: smoke/1.2.3
x-githits-session-id: 0ba8a49b8a7d3106
User-Agent: githits-cli/0.1.9

Test plan

  • bun test passes (1401 / 0)
  • bun run typecheck / bun run build / bun run lint clean
  • Live-smoke via mock-fetch harness confirms headers land on the wire
  • REST + GraphQL transport sites both spread buildClientHeaders()
  • MCP server startup sets client mode and registers lazy clientInfo provider
  • Transport-level assertions pin x-githits-* on the wire, not just builder output

🤖 Generated with Claude Code

Emits four `x-githits-*` HTTP headers on every request (REST +
GraphQL) plus an MCP `clientInfo` hook so backend telemetry can
distinguish direct-CLI traffic from MCP-mode traffic, identify
the AI agent / IDE driving the CLI, and group requests by
terminal session — all without leaking raw identifiers.

## Four headers, two transports

| Header | Value |
|---|---|
| `x-githits-client-name` | `githits-cli` (direct CLI) / `githits-cli/mcp` (MCP server) |
| `x-githits-client-version` | Read from `package.json` |
| `x-githits-agent` | `name[/version]` — `GITHITS_AGENT` env, MCP clientInfo, or auto-detect |
| `x-githits-session-id` | SHA-256 truncated to 64 bits; cached per process |

Emitted on both:

- **REST** (`src/services/githits-service.ts`) — `/search`,
  `/languages`, `/feedbacks` via the shared `headers()` builder.
- **GraphQL** (`src/shared/pkgseer-graphql.ts`) — spreads into the
  fetch `headers` alongside `Authorization` / `Content-Type`.

## Agent detection (priority order)

1. `setAgentInfo()` — explicit runtime override.
2. MCP `clientInfo` via a lazy provider (when running as MCP
   server). The MCP SDK sets `_clientVersion` synchronously
   inside its `_oninitialize` handler before the initialize
   response is returned, so every tool call arriving after the
   handshake sees a populated value. Using a lazy provider
   (rather than an `oninitialized` notification callback) avoids
   the race where the first tool call can slip through before
   the async notification dispatches.
3. Env-var auto-detect: `OPENCODE`, `CLAUDECODE`,
   `CURSOR_TRACE_ID`, `WINDSURF_CONFIG_DIR`, `ZED_TERM`,
   `VSCODE_PID` (first match wins).
4. Explicit `GITHITS_AGENT=name/version` env override —
   overrides auto-detect but not `setAgentInfo`.

## Session-id detection

Most-specific terminal-session env var wins: `TERM_SESSION_ID`,
`ITERM_SESSION_ID`, `WEZTERM_PANE`, `KITTY_PID`,
`ALACRITTY_SOCKET`, `WT_SESSION`, `VSCODE_PID`, `SUPERSET_PANE_ID`,
`SUPERSET_WORKSPACE_ID`, `STARSHIP_SESSION_KEY`, `SSH_CONNECTION`.
Falls back to parent PID, then process PID, then a random UUID.
Raw value is SHA-256-hashed and truncated to 16 hex chars (64
bits) before ever touching the wire — raw session identifiers
never leave the client. Result is cached for the lifetime of
the process.

## Safety invariants

- Every header value passes through `sanitizeHeaderValue` —
  strips C0 / C1 control chars, trims, enforces a 256-byte cap.
  Blank or oversized values are **dropped** rather than
  truncated to a half-value.
- `buildClientHeaders()` is wrapped in try/catch and returns
  `{}` on any internal error — API requests never fail because
  of a header-generation problem.
- Header builder is evaluated **per-request**, so values set
  after client creation (`setAgentInfo`, `setMcpClientVersionProvider`)
  reach subsequent requests.
- MCP `setMcpClientVersionProvider` callback is try/catch-
  wrapped internally — SDK access failures never block a
  request or crash the MCP session.
- Header spread order on both transports puts `x-githits-*`
  first, then hardcoded `Authorization` / `Content-Type` /
  `User-Agent`, so no hypothetical collision could drop the
  hardcoded headers.

## Testing

- 71 new unit tests covering session-id resolution priority,
  caching, agent detection + env-override, MCP clientInfo
  provider (precedence, per-call invocation, throwing-provider
  fallback, explicit-override win), header sanitisation, and
  `buildClientHeaders` composition.
- Transport-level assertions: new tests in
  `pkgseer-graphql.test.ts` and `githits-service.test.ts`
  capture request headers and pin that `x-githits-*` headers
  actually reach the wire — not just that the builder emits
  them.
- MCP integration: `mcp.test.ts` asserts `startMcpServer`
  flips clientMode to `githits-cli/mcp` and registers the
  lazy MCP clientInfo provider. `afterEach` cleanup prevents
  test pollution across files.
- Full suite: 1401 pass.
- Typecheck / lint / build clean.

## Live-smoke

Ran a mock-fetch harness against `postPkgseerGraphql` with
`GITHITS_AGENT=smoke/1.2.3` and `TERM_SESSION_ID=smoke-session-abc`;
confirmed on the wire:

```
x-githits-client-name: githits-cli
x-githits-client-version: 0.1.9
x-githits-agent: smoke/1.2.3
x-githits-session-id: 0ba8a49b8a7d3106
User-Agent: githits-cli/0.1.9
```
@jlitola
jlitola force-pushed the feat/telemetry-headers branch from 82387c3 to b0076c6 Compare April 21, 2026 17:38
@jlitola
jlitola merged commit 744550f into main Apr 21, 2026
3 checks passed
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