Skip to content

Send a real User-Agent on API requests - #79

Open
Kevin-Chant wants to merge 3 commits into
mainfrom
kc/identify-cli-user-agent
Open

Kevin-Chant wants to merge 3 commits into
mainfrom
kc/identify-cli-user-agent

Conversation

@Kevin-Chant

@Kevin-Chant Kevin-Chant commented Sep 18, 2026

Copy link
Copy Markdown
Member

Features and Changes

We send Speakeasy's default User-Agent, which reads as a library and carries the generator's version (0.0.1) rather than ours — GrowthBook can only spot CLI traffic by the Go module path buried in it. growthbook/growthbook#7038 adds per-client API usage tracking and this is the half that makes the CLI visible to it.

Requests now send growthbook-cli/<version> (<go version>; <os>/<arch>). The version comes from customcfg.Version rather than cli.Version, because internal/cli imports this package. goreleaser stamps that via ldflags; when it's unstamped we read the module version out of the build info, so go installed binaries report their real version too. Only a go build from a checkout still reports dev, which has no version to report anyway.

The wrapper goes outside the diagnostics client so --debug prints what actually goes on the wire. WrapClientForUserAgent lives in a hand-written custom_useragent.go; the one-line hook in the generated client.go is folded into that file's existing patch.

Testing

  • growthbook projects list against a local GrowthBook -> server logs client: "cli", clientVersion: "dev"
  • Same built with goreleaser's -X ...customcfg.Version=2.6.0 -> clientVersion: "2.6.0"
  • ldflags beat build info when both are present
  • --debug -> prints the real User-Agent, not the SDK default
  • Patch reverse-applies to the pristine generated client.go and reproduces the working file exactly

The generated SDK sent Speakeasy's default, which carries the generator's
version rather than the CLI's, so GrowthBook could only identify CLI
traffic by the Go module path buried in it.

Sends `growthbook-cli/<version> (<go version>; <os>/<arch>)`, taking the
version from `customcfg.Version` — `internal/cli` imports this package,
so its `Version` isn't reachable from here.

The wrapper sits outside the diagnostics client so `--debug` prints what
actually goes on the wire. The one-line hook in the generated client.go
is rebuilt into its existing patch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kevin-Chant and others added 2 commits September 18, 2026 15:00
Self-review follow-up. customcfg.Version is only stamped by goreleaser, so
anything built with `go install module@vX` reported "dev" despite knowing
its own version. Read it from the module build info when ldflags left the
sentinel behind. A `go build` from a checkout still reports "dev", since
build info has no version to give there either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Forcing the User-Agent made `-H "User-Agent: ..."` silently do nothing, so a
documented global flag stopped working for that one header.

The back-end classifies first-party clients with an anchored regex
(`/^growthbook-(cli|mcp|skills|coderefs)\/(\S+)/i` in api-client.util.ts) and
records the raw User-Agent alongside the bucket. RFC 9110 User-Agent is a
space-separated list of product tokens, most significant first, so prepending
ours satisfies the regex while leaving the caller's token on the wire: a proxy
or wrapper can still identify itself, and CLI attribution cannot be lost.

The compat check in updatecheck.go bypasses the SDK client, so it went out as
Go's default User-Agent and landed in the back-end's generic `go` bucket - CLI
traffic mislabelled as someone's Go integration. It now sets the same header.
`UserAgent()` moved to customcfg because internal/client already imports that
package for `Version`; the reverse direction would cycle.

The `-H` help text is amended from custom_startup.go rather than by widening
the root.go patch, which stays one line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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