feat(media-use): classify and tag heygen provider telemetry, harden test isolation#2130
feat(media-use): classify and tag heygen provider telemetry, harden test isolation#2130miguel-heygen wants to merge 5 commits into
Conversation
Adds a stable reason-code classifier (not_found/not_authenticated/ outdated/rate_limited/other) alongside the existing display-message classifier, and reports it via a new media_use_provider_error event so quota/rate-limit failures are distinguishable from other provider errors even when a fallback provider later succeeds.
Adds a sparse auth_method property (oauth/api_key) to media_use_resolve telemetry for heygen-family providers, derived via a new heygenAuthMethod() helper alongside the existing heygenAuthHeaders(). Absent for every non-heygen provider.
…ception seam resolve.test.mjs already set DO_NOT_TRACK=1 for every spawned resolve.mjs invocation, so local test runs were not actually leaking events. But that safety relied entirely on every call site remembering to set the env var, with no way to prove it. Add a MEDIA_USE_TELEMETRY_HOST override read at the point telemetry.mjs builds its POST url (falling back to the real endpoint when unset), and a test that points it at a local HTTP server to assert a real event is actually intercepted rather than trusting the env-var default alone.
f5d6abf to
e2ed3c6
Compare
Warn once on stderr if MEDIA_USE_TELEMETRY_HOST is ever set outside a
test/CI context, since the existing catch{} around the network call
would otherwise swallow a silent redirect with zero signal. Also widen
the rate_limited classifier to catch "too many requests" (the literal
HTTP 429 reason phrase) and "throttled", which were falling through to
"other".
reportHeygenFailure's telemetry call was fire-and-forget with no join point, and its callers (voice-provider.mjs, heygen-search.mjs) are sync call sites that can't await it themselves. On the resolve-miss path, process.exit() could follow shortly after, racing the still-in-flight request on its own non-keepalive connection and dropping the event. Track each pending report in a module-level set and flush it before the miss/success telemetry a process.exit() may follow.
There was a problem hiding this comment.
Reviewed the diff end-to-end (all 9 files), traced the telemetry send path + flush ordering, and checked the classifier for exhaustiveness. Code LGTM — no correctness blockers. Clean and well-tested, and the telemetry-privacy invariant holds: reason is a fixed enum (never raw stderr), auth_method is oauth/api_key/absent, and undefined values are dropped by JSON.stringify. The isolation fix genuinely closes the env-var-default gap with a real interception seam.
⚠️ Merge blocker (edited in after cross-checking with @tai): CI is red — theSkills: manifest in syncjob is failing on a staleskills-manifest.jsonhash (all other 30+ checks pass). Regenerate the manifest (bun run --cwd packages/cli gen:skills-manifest) and commit before merge. Code is clean; this is a stale generated-artifact gate, not a logic issue.Also worth addressing — email-case identity split:
heygenAccountDistinctId(telemetry.mjs) uses.trim()only and the heygen-cli side uses the raw.trim().toLowerCase()on both sides, or confirm downstream already normalizes.
Non-blocking nits:
resolve.mjs:420-424— in theprovenanceliteral,...heygenAuthMethodFor(provider)is spread before...searchResult.metadata?.provenance. Inert today (no provider emitsauthMethodin provenance), but a future one would shadow the computed value. Since the computed value is the intended source of truth, consider spreading it after the provenance (or documenting the precedence).telemetry.mjs—isTestOrCiContext()(keys onNODE_ENV==="test") andoptedOut()(keys on"development") are near-identical env predicates serving different purposes. Intentional, but a cross-referencing comment or shared helper would prevent future drift. No bug — the warning path is only reachable afteroptedOut()returns false (posthogHost()is only called frompostBatch→track()past its guard).heygen.mjs—heygenAuthMethod()andheygenAuthHeaders()independently encode "Authorization header ⇒ OAuth"; a sharedisOauthCred()helper would single-source that invariant.heygen-cli.mjsrate-limit classifier —lower.includes("quota")/"throttled"are broad substring matches on combined stderr+stdout (e.g. "quota check passed" would misclassify asrate_limited). Acceptable sincereasonis a coarse grouping enum, not behavior-gating — just noting the inherent precision ceiling of substring matching.
Questions:
- Cache-hit
auth_methodsemantics: on a cache/manifest hit,result()emits the auth method stored when the asset was first fetched; on a fresh resolve it's the current method. So the same asset resolved under OAuth then re-resolved from cache under an API key reportsoauthboth times. Is "how the asset was originally obtained" the intended meaning of the tag? (I think that's the right choice for a free-vs-paid conversion signal — worth a one-line comment since the two paths compute it differently.) reused-explicitpath (resolve.mjs:1062) preserves the pre-existingauthMethodfrom the cached record — confirming that's intended, consistent with Q1.
— Somu
Summary
media-use's heygen-cli integration could tell you a resolve succeeded, but not why it failed along the way, or whether a successful call went through the free (OAuth) or paid (API-key) path. This closes both gaps, plus a real test-isolation bug where local test runs could reach the production telemetry endpoint.
Companion change to heygen-com/heygen-cli#212 (identity unification + auth telemetry) — the two land independently since neither repo's release blocks the other.
What changed
Design decisions
reasonvalue, not the display message or raw stderr text — stays a small enum a dashboard can group by, not free text.Testing
oxlint/oxfmt --check: clean across all changed files.Post-Deploy Monitoring & Validation