agentsclimarketplace

Posthog analytics with opt out and thread queue

Skill kjuhwa/skills-hub/skills/cli/posthog-analytics-with-opt-out-and-thread-queue

Self-correcting knowledge corpus for Claude Code — 9 stable shape clusters, bias-correction pipeline baked into contribution flow. 47 papers, 45 techniques, 1.1k skills.

Install
npx -y skills add kjuhwa/skills-hub --skill posthog-analytics-with-opt-out-and-thread-queue

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Background-thread CLI analytics that respects DO_NOT_TRACK + custom opt-out env, persists an anonymous UUID in ~/.config, queues events with bounded size, and flushes on atexit with a timeout.

SKILL.md

3.5 KB, 671 tokens by cl100k_base, as published. Nobody here has run it

CLI Analytics with Opt-Out and Thread Queue

When to use

You're shipping an open-source CLI and want anonymous telemetry to demonstrate adoption to sponsors, but with strict guarantees: never block CLI execution, respect industry-standard opt-out flags, never collect PII, always be ready for the user to disable it.

How it works

  • Honor both DO_NOT_TRACK=1 and a custom <APP>_ANALYTICS_DISABLED=1.
  • Anonymous ID = first-run UUID stored in ~/.config/<app>/anonymous_id. Falls back to a per-process UUID if writing fails.
  • Events queue into a bounded queue.Queue(maxsize=128); full queue silently drops.
  • A daemon thread drains the queue with httpx, posting to PostHog with a 2-second timeout.
  • atexit.register(self.shutdown) ensures pending events flush within 1 second on CLI exit.
  • A drained Event is signalled via threading.Event so shutdown can wait for in-flight sends.
  • Base properties (cli_version, python_version, os_family) attached to every event automatically.

Example

class Analytics:
    def __init__(self):
        self._disabled = _is_opted_out()
        self._anonymous_id = _get_or_create_anonymous_id()
        self._queue: queue.Queue = queue.Queue(maxsize=128)
        self._pending = 0; self._drained = threading.Event(); self._drained.set()
        self._worker = None; self._shutdown = False
        if not self._disabled:
            atexit.register(self.shutdown)

    def capture(self, event, properties=None):
        if self._disabled or self._shutdown: return
        envelope = _Envelope(event=event.value,
                             properties=_BASE_PROPERTIES | (properties or {}))
        self._ensure_worker()
        try:
            with self._pending_lock:
                self._pending += 1; self._drained.clear()
            self._queue.put_nowait(envelope)
        except queue.Full:
            self._mark_done()

    def _worker_loop(self):
        with httpx.Client(timeout=2.0) as client:
            while True:
                item = self._queue.get()
                if item is None: self._queue.task_done(); break
                try: self._send(client, item)
                finally:
                    self._queue.task_done()
                    self._mark_done()
            # drain remainder on shutdown ...

def _is_opted_out():
    return os.getenv("MYAPP_ANALYTICS_DISABLED","0") == "1" or os.getenv("DO_NOT_TRACK","0") == "1"

Gotchas

  • Always honor DO_NOT_TRACK (consoledoc.org standard) — it's the universal opt-out.
  • Bounded queue + dropped overflow keeps the CLI responsive even when the network is slow.
  • atexit flush MUST have a timeout (1s here) so CLI exit isn't held hostage by a stuck network.
  • Disable analytics inside CI runners and pytest by detecting CI=1/PYTEST_CURRENT_TEST env so adoption metrics aren't polluted.
  • Never include hostnames, file contents, or alert payloads in event properties — only OS-level metadata.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.