Files
oh-my-opencode/src/shared/posthog-activity-state.ts
T
YeonGyu-Kim 669e0667be fix(telemetry): dedupe plugin_loaded event to once per UTC day
The plugin_loaded event was emitted on every plugin reload, generating
high event volume on PostHog (proportional to opencode restarts per
user per day). With MAU > 60K and active power users restarting
frequently, this drove unnecessary event spend.

Add a separate daily dedup state field (lastPluginLoadedDayUTC) so the
plugin_loaded capture only fires once per UTC day per machine. The
existing daily activity dedup (lastActiveDayUTC, used by
omo_daily_active) is preserved as an independent gate so the two
dimensions cannot overwrite each other in the activity state file.
2026-04-28 15:01:40 +09:00

111 lines
2.7 KiB
TypeScript

import { existsSync, mkdirSync, readFileSync } from "node:fs"
import { join } from "node:path"
import { getDataDir } from "./data-path"
import { log } from "./logger"
import { CACHE_DIR_NAME } from "./plugin-identity"
import { writeFileAtomically } from "./write-file-atomically"
type PostHogActivityState = {
lastActiveDayUTC?: string
lastPluginLoadedDayUTC?: string
}
type PostHogActivityCaptureState = {
dayUTC: string
captureDaily: boolean
}
type PluginLoadedCaptureState = {
dayUTC: string
capturePluginLoaded: boolean
}
const POSTHOG_ACTIVITY_STATE_FILE = "posthog-activity.json"
function getPostHogActivityStateFilePath(): string {
return join(getDataDir(), CACHE_DIR_NAME, POSTHOG_ACTIVITY_STATE_FILE)
}
function getUtcDayString(date: Date): string {
return date.toISOString().slice(0, 10)
}
function isPostHogActivityState(value: unknown): value is PostHogActivityState {
return value !== null && typeof value === "object" && !Array.isArray(value)
}
function readPostHogActivityState(): PostHogActivityState {
const stateFilePath = getPostHogActivityStateFilePath()
if (!existsSync(stateFilePath)) {
return {}
}
try {
const content = readFileSync(stateFilePath, "utf-8")
const parsed: unknown = JSON.parse(content)
if (!isPostHogActivityState(parsed)) {
return {}
}
return parsed
} catch (error) {
log("[posthog-activity-state] Failed to read activity state", {
error: String(error),
stateFilePath,
})
return {}
}
}
function writePostHogActivityState(nextState: PostHogActivityState): void {
const stateFilePath = getPostHogActivityStateFilePath()
try {
mkdirSync(join(getDataDir(), CACHE_DIR_NAME), { recursive: true })
writeFileAtomically(stateFilePath, `${JSON.stringify(nextState, null, 2)}\n`)
} catch (error) {
log("[posthog-activity-state] Failed to write activity state", {
error: String(error),
stateFilePath,
})
}
}
export function getPostHogActivityCaptureState(now: Date = new Date()): PostHogActivityCaptureState {
const state = readPostHogActivityState()
const dayUTC = getUtcDayString(now)
const captureDaily = state.lastActiveDayUTC !== dayUTC
if (captureDaily) {
writePostHogActivityState({
...state,
lastActiveDayUTC: dayUTC,
})
}
return {
dayUTC,
captureDaily,
}
}
export function getPluginLoadedCaptureState(now: Date = new Date()): PluginLoadedCaptureState {
const state = readPostHogActivityState()
const dayUTC = getUtcDayString(now)
const capturePluginLoaded = state.lastPluginLoadedDayUTC !== dayUTC
if (capturePluginLoaded) {
writePostHogActivityState({
...state,
lastPluginLoadedDayUTC: dayUTC,
})
}
return {
dayUTC,
capturePluginLoaded,
}
}