feat: tag CLI requests with the command that made them - #132
Merged
Conversation
Nothing on the backend can currently tell which product a CLI request belongs to, or that it came from the CLI at all. `request_log.py` identifies clients by `X-Fern-SDK-Name`, which the CLI does not send — its entire header set is accept, accept-encoding, authorization, host, user-agent. `path_pattern` is already logged, so once CLI traffic is identifiable most products fall out of the path. The one thing paths cannot express is agents-as-code: `agents push` and `agents create` hit the same convai endpoints. This adds that missing dimension. Each workflow handler opens a CommandScope, which appends `cmd/<command>` to the User-Agent for the duration of the command: User-Agent: elevenlabs-cli/1.0.0 cmd/agents.push The User-Agent rather than a header because `RequestOptions.additional_headers` never reaches the wire: `HttpClient::send_request` only calls `apply_custom_headers` on its non-executor branch and this CLI always uses the executor. That is the same reason the `X-Source: agents-cli` we set is silently dropped, and why v0's attribution via `is_agents_cli_x_source` no longer fires. Tags are static strings chosen per handler, never derived from argv. `extract_subcommand_path` would have been convenient but returns positional tokens, so `agents test my-agent` would put the agent's name in the User-Agent and from there into request logs. Verified against a local server: agents pull -> elevenlabs-cli/1.0.0 cmd/agents.pull models list (generated) -> elevenlabs-cli/1.0.0 ELEVENLABS_VIA=envtoken/2.0 ... -> elevenlabs-cli/1.0.0 envtoken/2.0 cmd/agents.pull --via partner/9.9 -> elevenlabs-cli/1.0.0 partner/9.9 The last case is a deliberate gap: the flag sets an override that beats the env var wholesale, so an explicit --via drops the tag. Losing telemetry is the right side to fail on versus clobbering a consumer's own token. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Patch release carrying the command tag, so the backend can attribute CLI traffic to a product. cargo-dist reads the release version from the crate, so the tag has to match: v1.0.1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PennyroyalTea
approved these changes
Aug 26, 2026
Merged
PaulAsjes
added a commit
that referenced
this pull request
Aug 26, 2026
* [fern-generated] Update SDK Generated by Fern CLI Version: unknown Generators: - fernapi/fern-cli-generator: 0.38.4 * [fern-replay] Applied customizations Patches applied (1): - patch-3324bcc8: build: publish prereleases to homebrew and scoop Patches with unresolved conflicts (4): - patch-809b383f: fix(deps): patch two advisories; keep the verification script - patch-590ac6e0: Alpha build - patch-composite-7dd8e976: Customer customizations (composite) - patch-147b7f4d: feat: tag CLI requests with the command that made them (#132) Run `fern-replay resolve` to apply these customizations. * [fern-replay] Resolved conflicts Keep publish-prereleases = false. A tap has no prerelease channel, so publishing one would serve an RC to every stable brew/scoop user; the restored customization dates from the alpha, before 1.0.0 shipped. Cargo.toml/Cargo.lock resolved to the generated 1.1.0: 1.0.1 was never released, so 1.1.0 carries the unreleased telemetry patch plus this generation's multipart and anyOf fixes. * chore: ignore locally generated skills and verify-project `fern replay resolve` stages with `git add -A`, so these local artifacts were swept into its resolution commit twice. --------- Co-authored-by: fern-api[bot] <115122769+fern-api[bot]@users.noreply.github.com> Co-authored-by: Paul Asjes <paul.asjes@elevenlabs.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nothing on the backend can currently tell that a request came from the CLI, let alone which product it belongs to.
request_log.pyidentifies clients byX-Fern-SDK-Name; the CLI's entire header set isaccept, accept-encoding, authorization, host, user-agent.path_patternis already logged, so once CLI traffic is identifiable most products fall out of the path. The one thing paths cannot express is agents-as-code:agents pushandagents createhit the same convai endpoints. This adds that missing dimension.Each workflow handler opens a
CommandScope, which appendscmd/<command>to the User-Agent for the duration of the command:Pairs with the backend change in
xi(feat/log-cli-traffic), which parses this intoclient_kind,sdk_name,sdk_versionandcli_command.Why the User-Agent and not a header
RequestOptions.additional_headersnever reaches the wire.HttpClient::send_requestonly callsapply_custom_headerson its non-executor branch, and this CLI always uses the executor:That is also why the
X-Source: agents-cliwe already set is silently dropped — and why v0's attribution viais_agents_cli_x_source(xi_shared_workspace/generation_attribution.py:144,constants.py:43) stopped firing on v1. Worth reporting upstream alongside theparse_responsestatus-check fix they shipped in 0.38.0, since it is the same structural gap.Tags are static, never derived from argv
crate::cli_args::extract_subcommand_pathis public and would have been convenient, but it returns positional tokens —agents test my-agentwould put the agent's name in the User-Agent and from there into request logs. Every tag here is a static string chosen per handler, and the&'static strsignature enforces it.Verified on the wire
Plus a leak check:
agents test "my secret agent name"puts nothing user-supplied in any User-Agent.The last row is a deliberate gap.
--viasets an override that beats the env var wholesale, so an explicit--viadrops our tag. Losing telemetry is the right side to fail on versus clobbering a consumer's own token — but it means--viausers are invisible in the command dimension. Closing that needs framework support for composing the two.Scope
Covers the 15 hand-written workflow handlers, which is exactly the set
path_patterncannot distinguish. Generated commands (TTS, dubbing, …) are already product-identifiable from the path. Command-level data for those needs Fern to emit it at dispatch, whereop_pathis known.Tests
4 new unit tests covering the tag format, append-not-clobber, restore-on-drop, and that the env key tracks the configured flag name (so a
userAgentSuffixFlagrename upstream cannot silently detach this). Full suite green: 1903 + 68 + 2 + 755 + doc.All changes are under
cli/elevenlabs/workflow/, which is fernignored.