Skip to content

feat(http): send User-Agent header on swamp-club requests#1477

Merged
stack72 merged 1 commit into
mainfrom
worktree-header
May 30, 2026
Merged

feat(http): send User-Agent header on swamp-club requests#1477
stack72 merged 1 commit into
mainfrom
worktree-header

Conversation

@stack72
Copy link
Copy Markdown
Contributor

@stack72 stack72 commented May 30, 2026

Summary

Adds a User-Agent: swamp-cli/<version> header to outbound requests to swamp-club so the server can attribute traffic by client version.

The header is injected at the identity composition root rather than at each call site:

  • loadIdentity() now sets a userAgent field on ClientIdentity, built from the CLI VERSION constant and exported as USER_AGENT.
  • mergeIdentityHeaders() emits it, so both swamp-club API clients (SwampClubClient and ExtensionApiClient) are covered by one change. Caller-supplied headers still take precedence on conflict.
  • The telemetry sender (telemetry.swamp-club.com/ingest) builds requests outside the ClientIdentity path, so it gains an optional userAgent constructor arg wired from USER_AGENT at its construction sites in mod.ts.

The version comes from the VERSION constant that scripts/compile.ts stamps at release-build time, so released binaries report a real version (dev builds report the source default, swamp-cli/<date>.<time>.0-sha.).

Deliberately out of scope

  • Update checker (artifacts.swamp-club.com) — anonymous static CDN downloads, no identity path.
  • Source downloader — fetches from GitHub, not swamp-club.

Test Plan

  • New client_identity_test.ts: header set / omitted / combined with other identity headers / caller override.
  • Updated load_identity_test.ts: USER_AGENT format and that it's always present even on the no-config-dir fallback path.
  • New http_telemetry_sender_test.ts case: User-Agent sent when provided.
  • Verified Deno honors a custom User-Agent override on fetch (it does).
  • deno fmt --check, deno lint, deno run test (6517 passed) all green.

🤖 Generated with Claude Code

Tag outbound swamp-club traffic with a `User-Agent: swamp-cli/<version>`
header so the server can attribute requests by client version.

The header is injected at the identity composition root: `loadIdentity()`
now sets a `userAgent` field on `ClientIdentity` (built from the CLI
`VERSION` constant and exported as `USER_AGENT`), and
`mergeIdentityHeaders()` emits it. This covers both swamp-club API
clients (`SwampClubClient` and `ExtensionApiClient`) with a single
change, with caller-supplied headers still taking precedence.

The telemetry sender (`telemetry.swamp-club.com`) builds its requests
outside the `ClientIdentity` path, so it gains an optional `userAgent`
constructor arg wired from `USER_AGENT` at its construction sites. The
update checker (artifacts CDN) and source downloader (GitHub) are left
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial Review

Critical / High

None found.

Medium

None found.

Low

  1. src/infrastructure/telemetry/http_telemetry_sender.ts:62 — When this.userAgent is set, the User-Agent header is included via object spread into a Record<string, string> and passed to fetch. Deno's fetch does honor custom User-Agent headers (unlike browsers), but if Deno's behavior ever changes to append or ignore a custom User-Agent (as browsers do per the Fetch spec), this would silently stop working with no indication. The mergeIdentityHeaders path uses Headers.set() which is the canonical approach. Not actionable today — just noting the difference in mechanism between the two code paths.

  2. src/cli/load_identity.ts:26USER_AGENT is module-level, so it captures VERSION at import time. This is correct for a CLI (process starts, imports, runs, exits), but worth noting: if this module were ever imported in a long-lived process where VERSION could theoretically change (it can't — it's a const), the value would be stale. Purely theoretical; VERSION is a compile-time constant.

Verdict

PASS — Clean, well-scoped change. The mergeIdentityHeaders approach correctly injects User-Agent for both SwampClubClient and ExtensionApiClient with a single code path. Caller-header-wins precedence is preserved (identity headers set first, caller headers override via Headers.set). The telemetry sender gets its own parallel path since it bypasses ClientIdentity, and the constructor arg is optional so existing test call sites are unaffected. All production construction sites in mod.ts pass USER_AGENT. Test coverage is thorough — including the caller-override case which validates the precedence contract.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Clean, well-scoped PR. The composition-root injection pattern for User-Agent is the right approach — it covers both swamp-club API clients via mergeIdentityHeaders with a single change, and handles the telemetry sender's separate request path via constructor injection.

Blocking Issues

None.

Suggestions

None — the PR is well-structured:

  • DDD alignment: ClientIdentity is a proper value object; adding userAgent as an optional field is consistent. The composition root (loadIdentity) assembles identity from infrastructure concerns and passes it down — textbook dependency inversion.
  • Test coverage: Thorough — client_identity_test.ts covers set/omit/combined/caller-override cases; load_identity_test.ts verifies the format and that userAgent survives the failure path; http_telemetry_sender_test.ts verifies the header hits the wire.
  • Import boundaries: No libswamp boundary violations. ClientIdentity lives in src/infrastructure/, so direct CLI imports are correct per project conventions.
  • Security: User-Agent only reveals CLI version — appropriate for traffic attribution.
  • Precedence: Caller headers override identity headers (tested), preserving the existing contract documented in mergeIdentityHeaders.

@stack72 stack72 merged commit 726fa90 into main May 30, 2026
11 checks passed
@stack72 stack72 deleted the worktree-header branch May 30, 2026 00:33
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