feat(origin): detect parent agent/IDE and propagate to HeyGen API + PostHog#171
Merged
Conversation
…ytics Adds internal/origin: a port of the hyperframes-oss agent-runtime detector (13 vendor rules + gemini_managed_agent filesystem detection). Existence- based env-var checks; never reads secret values. Detect() is called once at startup from internal/client.New and internal/analytics.newWithCapture and the result is cached for the process lifetime. Wiring: - internal/client.Do() sets `X-HeyGen-Client-Origin: <origin>` on every outbound request when origin is non-empty (header is omitted when unknown so the backend can distinguish "unknown" from explicit-empty). - internal/analytics CommandRun / CommandRunComplete attach `client_origin` to PostHog events. Empty origin → property is omitted (same separation rationale). Why: the backend's compute_request_source label scheme can't currently distinguish a CLI invocation from claude_code vs cursor vs a plain shell — user-agent matching is fragile and our internal CLI sets a generic UA. A self-declared client header matches the existing X-HeyGen-Client-User-Agent trust pattern and a follow-up backend PR will key on it to yield `cli:claude_code`, `cli:cursor`, etc., falling back to today's behavior. Detection: any of CLAUDECODE, CLAUDE_CODE_ENTRYPOINT, CODEX_THREAD_ID, CODEX_CI, CODEX_SANDBOX_NETWORK_DISABLED, TERM_PROGRAM=cursor (exact) or windsurf (case-insensitive), GITHUB_ACTIONS=true paired with COPILOT_AGENT_ID or RUNNER_NAME=Copilot, REPL_ID / REPLIT_USER, HERMES_QUIET, OPENCLAW_STATE_DIR / OPENCLAW_CONFIG_PATH, PI_CODING_AGENT, CLINE_ACTIVE, GEMINI_CLI, CRUSH. gemini_managed_agent requires both /.agents/ as a directory AND gVisor kernel — neither alone is unique. First match wins; ordering pinned by test. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
somanshreddy
approved these changes
Jun 12, 2026
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.
Description
Adds a new
internal/originpackage that detects which parent agent / IDE / managed sandbox launched the CLI, and wires the result into two outbound surfaces:X-HeyGen-Client-Origin: <origin>header on every HTTP request from the CLI (internal/client/client.go). The header is omitted when origin is unknown so the backend can distinguish unknown from explicit-empty. This matches the existing self-declared-trust pattern ofX-HeyGen-Client-User-Agent.client_originproperty on every PostHog event from the CLI (internal/analytics/analytics.go). Same omit-when-empty rule so funnel queries get clean cohorts.Detect()is called once at startup fromclient.Newandanalytics.newWithCaptureand the result is cached for the process lifetime — parent context doesn't change mid-invocation.Why
The backend's
compute_request_sourcelabel scheme currently producescli/cli:api_keyfor every CLI invocation, regardless of whether it came from a developer inclaude_code,cursor, a CI runner, or a plain shell. User-agent matching server-side is fragile and our internal CLI ships a generic UA. A self-declared client header is honest about the trust model and lets the backend follow up withcli:claude_code,cli:cursor, etc., falling back to today's behavior when the header is absent.This is the CLI half of the change. A follow-up backend PR (stacked on the in-flight one that just dropped server-side cli provider-qualification) will read the header.
Detection rules
Ported from hyperframes-oss
packages/cli/src/telemetry/agent_runtime.tsso the two CLIs stay in lockstep on which agent names exist and how each is identified. Existence checks only — we never read the value of an env var (some are secrets).claude_codeCLAUDECODEorCLAUDE_CODE_ENTRYPOINTcodexCODEX_THREAD_IDorCODEX_CIorCODEX_SANDBOX_NETWORK_DISABLEDcursorTERM_PROGRAM=cursor(exact)windsurfTERM_PROGRAM~windsurf(case-insensitive)copilot_agentGITHUB_ACTIONS=trueAND (COPILOT_AGENT_IDset ORRUNNER_NAME=Copilot)replitREPL_IDorREPLIT_USERhermesHERMES_QUIETopenclawOPENCLAW_STATE_DIRorOPENCLAW_CONFIG_PATHpiPI_CODING_AGENTclineCLINE_ACTIVEgemini_cliGEMINI_CLIcrushCRUSHgemini_managed_agent/.agents/is a directory AND gVisor kernel detected (4.19.0-gvisorosrelease orgVisorin/proc/version)First match wins; ordering pinned by test (
TestDetect_FirstMatchWins).Testing
go test ./internal/origin/...— 25 subcases covering every vendor rule, empty-env baseline, case-sensitivity (cursor exact vs windsurf case-insensitive), first-match-wins ordering, Copilot's two-marker conjunction, and the non-Linux short-circuit onisGeminiManagedAgent.go test ./internal/client/...— addedTestClient_Do_SetsClientOrigin(header present when origin set) andTestClient_Do_OmitsClientOriginWhenEmpty(no header at all when origin empty —http.Headermap key absent, not empty string value).go test ./internal/analytics/...— addedTestCommandRun_IncludesClientOrigin,TestCommandRunComplete_IncludesClientOrigin, andTestCommandRun_OmitsClientOriginWhenEmpty.go test ./...— full suite green.make build— builds locally.golangci-lint run ./...(v2.11.4, matching CI) — 0 issues.Follow-up: backend PR will read
X-HeyGen-Client-Originfromcompute_request_sourceto producecli:<origin>labels.PR by Jerrai