Context
Trawl's pre-/update-stack billing.webhook.service.js emitted subscription_changed PostHog analytics on plan change. This was dropped by /update-stack #1316 (correctly — trawl-specific code). Now promoting to devkit because AnalyticsService is a devkit-level abstraction (Wave 1–4 PostHog integration, Node #3640/#3653).
What
- Add
AnalyticsService.isConfigured() to lib/services/analytics.js — returns client !== null; cheap pre-check for callers that want to skip payload construction when PostHog is not active.
- In
billing.webhook.service.js handleSubscriptionUpdated: emit subscription_changed analytics event after plan.changed fires, carrying previousPlan/newPlan/isDowngrade; guarded by isConfigured() so downstreams without PostHog are clean no-ops. Non-fatal try/catch.
Why generic + safe
AnalyticsService.isConfigured() returns false when the downstream isn't running PostHog → the capture is a clean no-op. Downstreams using PostHog (trawl, future) get the events automatically after /update-stack.
Cross-ref
- Regression introduced by: /update-stack trawl#1316
- Trawl follow-up: comes-io/trawl_node#1315
- Alignment plan: infra
2026-06-01-trawl-promote-up-followups.md Task 5
Context
Trawl's pre-/update-stack billing.webhook.service.js emitted
subscription_changedPostHog analytics on plan change. This was dropped by /update-stack #1316 (correctly — trawl-specific code). Now promoting to devkit becauseAnalyticsServiceis a devkit-level abstraction (Wave 1–4 PostHog integration, Node #3640/#3653).What
AnalyticsService.isConfigured()tolib/services/analytics.js— returnsclient !== null; cheap pre-check for callers that want to skip payload construction when PostHog is not active.billing.webhook.service.js handleSubscriptionUpdated: emitsubscription_changedanalytics event afterplan.changedfires, carryingpreviousPlan/newPlan/isDowngrade; guarded byisConfigured()so downstreams without PostHog are clean no-ops. Non-fatal try/catch.Why generic + safe
AnalyticsService.isConfigured()returns false when the downstream isn't running PostHog → the capture is a clean no-op. Downstreams using PostHog (trawl, future) get the events automatically after/update-stack.Cross-ref
2026-06-01-trawl-promote-up-followups.mdTask 5