Skip to content

Commit 6ac12bc

Browse files
chore: sync skills (agent-skills-v0.86.0, context-mill@v1.13.0) (#66)
Co-authored-by: releaser-ai-plugin[bot] <273148615+releaser-ai-plugin[bot]@users.noreply.github.com>
1 parent ff08c37 commit 6ac12bc

10 files changed

Lines changed: 704 additions & 4 deletions

File tree

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "posthog",
33
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Claude Code. Optionally capture Claude Code sessions to PostHog LLM Analytics.",
4-
"version": "1.1.19",
4+
"version": "1.1.20",
55
"author": {
66
"name": "PostHog",
77
"email": "hey@posthog.com",

.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "posthog",
3-
"version": "1.0.18",
3+
"version": "1.0.19",
44
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Codex",
55
"author": {
66
"name": "PostHog",

.cursor-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "posthog",
33
"displayName": "PostHog",
4-
"version": "1.1.15",
4+
"version": "1.1.16",
55
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Cursor",
66
"author": {
77
"name": "PostHog",

gemini-extension.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "posthog",
3-
"version": "1.0.17",
3+
"version": "1.0.18",
44
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Gemini CLI",
55
"mcpServers": {
66
"posthog": {

skills/.sync-manifest

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
analyzing-experiment-session-replays
22
auditing-experiments-flags
33
auditing-warehouse-data-health
4+
authoring-log-alerts
45
cleaning-up-stale-feature-flags
56
configuring-experiment-analytics
67
configuring-experiment-rollout
@@ -12,6 +13,7 @@ diagnosing-missing-recordings
1213
diagnosing-sdk-health
1314
exploring-apm-traces
1415
exploring-autocapture-events
16+
exploring-live-traffic
1517
exploring-llm-clusters
1618
exploring-llm-costs
1719
exploring-llm-evaluations
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
---
2+
name: authoring-log-alerts
3+
description: >
4+
Author useful, low-noise log alerts on services in a PostHog project. Use when the user asks to set up
5+
alerts for their logs, suggest alerts they should add, or evaluate whether a service is worth monitoring.
6+
Covers service triage, baseline characterisation, threshold drafting, back-testing via simulate, and
7+
shipping with a notification destination.
8+
---
9+
10+
# Authoring log alerts
11+
12+
Authoring an alert is a _measurement_ problem, not a guessing problem. You are not trying to be exhaustive — you
13+
are trying to land thresholds that fire 0–3 times per week on real production patterns, on services that matter.
14+
15+
## When to use this skill
16+
17+
- The user asks to "set up alerts" / "suggest alerts" for their project.
18+
- The user wants to evaluate whether a service is producing alertable signal.
19+
- The user has just enabled log alerting and wants a starter set.
20+
21+
## When _not_ to use this skill
22+
23+
- Tuning an alert that already exists — that's a different job (use `posthog:logs-alerts-events-list` to inspect
24+
fire/resolve cadence and `posthog:logs-alerts-partial-update` to adjust).
25+
- Investigating an active incident — pull rows with `posthog:query-logs`, don't author an alert mid-incident.
26+
27+
## Tools
28+
29+
| Tool | Job | Where it fits |
30+
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------ |
31+
| `posthog:logs-services` | Top-25 services in window with log_count, error_count, error_rate, sparkline. | Step 1 — triage. |
32+
| `posthog:logs-attributes-list` / `posthog:logs-attribute-values-list` | Discover keys/values for narrower filters. | Step 2, optional. |
33+
| `posthog:logs-count-ranges` | Adaptive time-bucketed counts for a filter. | Step 3 — baseline. |
34+
| `posthog:logs-alerts-simulate-create` | Replay a draft config against `-7d` history with full state machine. | Step 4 — validate. |
35+
| `posthog:logs-alerts-create` | Persist the alert. | Step 5 — ship. |
36+
| `posthog:logs-alerts-destinations-create` | Wire the alert to Slack or webhook. | Step 5 — ship. |
37+
38+
Do **not** call `posthog:query-logs` during authoring. You need distributions, not rows. Reserve `posthog:query-logs` for
39+
the very end if the user asks "show me a sample of what would have fired" — `limit: 10` is plenty.
40+
41+
## Workflow
42+
43+
### 1. Triage — pick candidate services
44+
45+
Call `posthog:logs-services` for the last 24h with no filters. The response is capped at 25 services and includes a
46+
sparkline, so it is small and bounded.
47+
48+
A service is a candidate when **both** are true:
49+
50+
- `log_count` is non-trivial (≥ ~1k in 24h — quieter services produce too little signal to alert on).
51+
- `error_rate` is non-zero, **or** the user has named the service explicitly.
52+
53+
Skip services with high volume but `error_rate == 0` unless the user wants a volume-shape alert (e.g. "warn me
54+
if api-gateway suddenly stops producing logs"). Volume-floor alerts use `threshold_operator: below` and need
55+
different reasoning — see [references/volume-floor-alerts.md](./references/volume-floor-alerts.md).
56+
57+
If the user names a service, treat it as a candidate even without error signal.
58+
59+
### 2. (Optional) Narrow the filter
60+
61+
If a service has many error sub-types, an alert on "all errors" is usually too broad. Use
62+
`posthog:logs-attributes-list` (try `attribute_type: log`) and `posthog:logs-attribute-values-list` to find a discriminator —
63+
common ones are `http.status_code`, `error.type`, `k8s.container.name`. Add the narrowing filter to your draft.
64+
65+
Keep it simple: one severity filter + one or two attribute filters is plenty. Multi-clause filters are
66+
harder to reason about and rarely improve precision.
67+
68+
### 3. Baseline — characterise the candidate over 7 days
69+
70+
Call `posthog:logs-count-ranges` with the candidate's filters, `dateRange: { date_from: "-7d" }`, and
71+
`targetBuckets: 24` (one bucket ≈ 7h). The response gives you bucket counts.
72+
73+
**Do not eyeball the percentiles or scale the threshold to the alert window manually.** Pipe the
74+
count-ranges response into the helper script:
75+
76+
```bash
77+
echo '<count-ranges JSON>' | python3 scripts/baseline_stats.py --window-minutes 5
78+
```
79+
80+
The script returns:
81+
82+
```json
83+
{
84+
"n_buckets": 12,
85+
"bucket_minutes": 420.0,
86+
"alert_window_minutes": 5,
87+
"stats": { "p50": 12.0, "p95": 71.25, "p99": 126.25, "max": 140 },
88+
"suggested_threshold_count": 5,
89+
"rationale": "max(p99=126.25, median*3=36.0, floor=5) scaled from 420m bucket to 5m window",
90+
"health": []
91+
}
92+
```
93+
94+
Use `suggested_threshold_count` as your starting threshold. Read `health`:
95+
96+
| `health` flag | What it means | What to do |
97+
| ----------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
98+
| `sparse:N_of_M_buckets` | Too few non-empty buckets for a 7d baseline. | Widen filter, extend to `-30d`, or skip. |
99+
| `empty` | All buckets are zero. | Skip — no signal. |
100+
| `spiky` | `max` is 10×+ `p95`. | Count-threshold alerts work well. Proceed. |
101+
| `flat` | `p95``p50`. | Be cautious — either no incidents in lookback, or the metric is too smooth. Try a longer lookback or skip. |
102+
| `[]` (empty) | Healthy distribution. | Proceed. |
103+
104+
### 4. Draft and simulate
105+
106+
Pick a starter draft from these defaults — see [references/threshold-defaults.md](./references/threshold-defaults.md)
107+
for the reasoning:
108+
109+
| Setting | Default | Notes |
110+
| --------------------- | ------------------------------------------- | --------------------------------------------------------------------- |
111+
| `threshold_count` | `suggested_threshold_count` from the script | Already scaled to the alert window. |
112+
| `threshold_operator` | `above` | Use `below` only for volume-floor alerts. |
113+
| `window_minutes` | `5` | Allowed: 5, 10, 15, 30, 60. Must match what you passed to the script. |
114+
| `evaluation_periods` | `3` | M in N-of-M. |
115+
| `datapoints_to_alarm` | `2` | N in N-of-M. 2-of-3 reduces flap from a single noisy bucket. |
116+
| `cooldown_minutes` | `30` | Minimum time between repeat fires. |
117+
118+
Call `posthog:logs-alerts-simulate-create` with these settings and `date_from: "-7d"`. The response gives you `fire_count`
119+
and `resolve_count`.
120+
121+
### 5. Iterate — three rounds, then ship or skip
122+
123+
Target: `fire_count` between 0 and ~3 over `-7d`. If outside the band:
124+
125+
| Outcome | Adjustment |
126+
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
127+
| `fire_count` = 0 over 7d _and_ the baseline was spiky | Lower `threshold_count` toward `stats.p95` from the script, or drop to 1-of-2. |
128+
| `fire_count` = 0 _and_ the baseline was flat | The service has no alertable signal. Skip it; log why. |
129+
| `fire_count` > 5 | Raise `threshold_count` toward `stats.max` from the script, or move to 3-of-5 for a smoother window. |
130+
| `fire_count` is fine but resolve_count never matches fire_count | Cooldown is too long, or the underlying state is genuinely sticky. Acceptable for now. |
131+
132+
When adjusting the threshold, **read values from the script's `stats` block — never recompute percentiles
133+
by hand.**
134+
135+
Cap iteration at **3 simulate calls per candidate**. If you can't land in the band in 3 rounds, the metric
136+
is wrong — either the filter is too broad, the window is wrong, or the service genuinely doesn't have a
137+
threshold-shape signal. Note it and move on.
138+
139+
### 6. Ship — create + attach destination
140+
141+
Once a draft simulates cleanly:
142+
143+
1. Call `posthog:logs-alerts-create` with the validated config. Use a name like `<service> error rate (auto)` so the
144+
user can see at a glance which alerts came from this skill.
145+
2. Call `posthog:logs-alerts-destinations-create` to wire it to a notification target. **An alert with no destination
146+
is silent.** Always confirm the channel name or webhook URL with the user before attaching — never wire
147+
an auto-generated alert to a production channel without explicit confirmation. If the user is unsure,
148+
suggest a low-traffic testing channel for the first few alerts.
149+
150+
If the user wants alerts created in `enabled: false` state for review-then-flip, pass `enabled: false` to
151+
`-create` and tell them how many drafts you produced.
152+
153+
## Filter shape — required
154+
155+
The `filters` field on `posthog:logs-alerts-create` takes a subset of `LogsViewerFilters` and **must contain at
156+
least one of**:
157+
158+
- `severityLevels` — list of `["trace","debug","info","warn","error","fatal"]`
159+
- `serviceNames` — list of service name strings
160+
- `filterGroup` — property filter group
161+
162+
The same shape goes into `posthog:logs-alerts-simulate-create`'s `filters` field. Match the simulate filters to the alert filters
163+
exactly — otherwise the simulation is testing a different alert than the one you ship.
164+
165+
Example minimum:
166+
167+
```json
168+
{
169+
"severityLevels": ["error", "fatal"],
170+
"serviceNames": ["api-gateway"]
171+
}
172+
```
173+
174+
## Token-economy rules
175+
176+
- One `posthog:logs-services` call at the start, not per-candidate.
177+
- One `posthog:logs-count-ranges` call per candidate at `targetBuckets: 24`. Don't go above 30 during authoring.
178+
- ≤ 3 `posthog:logs-alerts-simulate-create` calls per candidate.
179+
- Zero `posthog:query-logs` calls during the authoring loop.
180+
- Prefer reporting a small set of well-validated alerts over a long list of unvalidated drafts.
181+
182+
## Output
183+
184+
Report what you did, in this shape:
185+
186+
- For each shipped alert: name, filters, threshold, simulated fire_count over 7d, destination.
187+
- For each skipped candidate: service name + why (flat baseline, can't land threshold, low volume).
188+
- Total simulate calls made, total alerts created.
189+
190+
The user should be able to read this and decide whether to disable any drafts before they go live.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Threshold defaults — reasoning
2+
3+
The math here is implemented in [`../scripts/baseline_stats.py`](../scripts/baseline_stats.py). The agent
4+
should not reproduce these calculations inline — pipe the `posthog:logs-count-ranges` response into the script and
5+
read `suggested_threshold_count`. This section explains _what the script computes and why_, so you can
6+
sanity-check its output and reason about edge cases.
7+
8+
## `threshold_count = max(p99, median × 3, floor) × (window_minutes / bucket_minutes)`
9+
10+
The bucket-level threshold takes the max of three terms:
11+
12+
1. **p99 of bucket counts.** The 99th percentile of recent buckets is a data-driven "above normal" line.
13+
Works when the baseline has enough non-empty buckets to compute a percentile.
14+
2. **median × 3.** Catches services where p99 is misleadingly close to the median (flat baselines), or where
15+
the lookback didn't include any spikes.
16+
3. **floor (default 5).** Alerting on counts of 1 or 2 produces too much noise on small services. Below 5
17+
matches/window, prefer a different alert shape (e.g. existence-based) or skip.
18+
19+
The bucket-level threshold is then **rate-scaled to the alert window** — a 7h bucket with a threshold of
20+
1000 errors equals ~12 errors per 5-minute window. The script does this scaling; the rationale field shows
21+
the math.
22+
23+
The scaling assumes errors arrive uniformly within a bucket, which is rarely true — a real spike can pack
24+
the entire bucket's count into a single 5-minute window. That's exactly why `posthog:logs-alerts-simulate-create` is the
25+
final arbiter: it replays the alert state machine against actual per-minute history, not the rate-scaled
26+
average.
27+
28+
## `window_minutes = 5`
29+
30+
The default minimum. Reasons to go higher:
31+
32+
- The service is bursty in 5-minute chunks but the _trend_ over 30 minutes is what matters → use 30.
33+
- Notifications at 5-minute resolution would be too noisy for the user (e.g. expected periodic spikes during
34+
cron) → smooth with a 30 or 60 minute window.
35+
36+
Allowed: `5`, `10`, `15`, `30`, `60`. Don't pick a value not on this list — the API rejects it.
37+
38+
## `evaluation_periods = 3`, `datapoints_to_alarm = 2` (2-of-3)
39+
40+
N-of-M is the cheap, high-signal way to dampen flap. 2-of-3 means: out of the last 3 check intervals, at
41+
least 2 must breach to fire. A single noisy interval doesn't trip the alert. A sustained problem still does.
42+
43+
When to deviate:
44+
45+
- **1-of-1** — fire instantly on a single bucket breach. Use only for incidents you cannot afford to delay
46+
(e.g. payments service erroring at all).
47+
- **3-of-5** — smoother, slower. Use when the service has known short bursts that are not real incidents.
48+
- Higher than 5 — diminishing returns; if 5 buckets aren't enough signal, the threshold is wrong.
49+
50+
## `cooldown_minutes = 30`
51+
52+
After a fire, suppress repeat fires for 30 minutes. This avoids paging the same channel every check interval
53+
during an ongoing incident — once the alert is firing, the user already knows.
54+
55+
Use 0 for snapshot-style alerts where every breach is independently interesting (rare).
56+
57+
## Avoid these footguns
58+
59+
- **`threshold_operator: below` without justification.** Below-threshold alerts measure absence — useful for
60+
"service stopped logging" but easy to misuse. If the service has any quiet hours (overnight, weekends),
61+
a below-threshold alert will fire at 3am every night. See [volume-floor-alerts.md](./volume-floor-alerts.md).
62+
- **Filtering by message text alone (`searchTerm` or `message icontains`).** Brittle to log format changes.
63+
Prefer a structured attribute (`http.status_code`, `error.type`) when one exists.
64+
- **Filtering by `trace_id`/`span_id`.** Not useful in alerts — these are per-request and never repeat at a
65+
rate that crosses a meaningful threshold.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Volume-floor alerts (`threshold_operator: below`)
2+
3+
Use to alert when a service _stops_ producing logs — a strong signal that the service is down, the logging
4+
pipeline is broken, or something upstream stopped sending traffic.
5+
6+
## When to use
7+
8+
- The user says "tell me if X stops logging."
9+
- A service has dependably constant volume across all hours (no nightly drop, no weekend drop) and the user
10+
cares about availability.
11+
12+
## When NOT to use
13+
14+
- The service has any quiet hours (overnight, weekends, batch-only). Below-threshold alerts will fire every
15+
quiet period.
16+
- The service is bursty by design — a 5-minute "no logs" window is normal.
17+
- You don't have at least 7 days of stable baseline. With less data, "below normal" is unknowable.
18+
19+
## How to size the threshold
20+
21+
The `baseline_stats.py` script doesn't suggest below-thresholds directly — derive it manually from the
22+
script's output. Don't compute by hand; use Python:
23+
24+
```bash
25+
python3 -c "import sys, json; d=json.load(sys.stdin); print(max(1, round(d['stats']['p50'] * (5/d['bucket_minutes']) * 0.25)))" < stats.json
26+
```
27+
28+
The reasoning:
29+
30+
1. Take `p50` from the script (the typical bucket count).
31+
2. Rate-scale to the alert window (`window_minutes / bucket_minutes`).
32+
3. Multiply by 0.25 — fire when volume drops to 25% of the typical bucket. Buffer absorbs normal variance.
33+
4. Set `threshold_operator: below`.
34+
35+
## Recommended N-of-M for floors
36+
37+
- `evaluation_periods: 3`, `datapoints_to_alarm: 3` (3-of-3) — require 3 consecutive quiet windows. A single
38+
blip won't fire.
39+
- `window_minutes: 15` minimum — 5-minute floor alerts on noisy services are unreliable.
40+
- `cooldown_minutes: 60` — once you know the service is quiet, no point re-paging every check.
41+
42+
## Simulate is essential here
43+
44+
Floor alerts are easy to misconfigure into "fires every night." Always run `posthog:logs-alerts-simulate-create` over `-7d`
45+
before shipping. If `fire_count > 0` and the user has not had outages in the last 7 days, the threshold is
46+
too aggressive.

0 commit comments

Comments
 (0)