/signals-scout-anomaly-detection
Signals scout that watches the project's most-viewed dashboards and insights for anomalies — bursts, drops, flat-lines, and trend breaks — against each insight's own seasonality-matched baseline.
Claude Code
$ npx -y skills add posthog/posthog --skill signals-scout-anomaly-detection --agent claude-code
Codex
$ npx skills add posthog/posthog --skill signals-scout-anomaly-detection -a codex -y
Cursor
$ npx skills add posthog/posthog --skill signals-scout-anomaly-detection -a cursor -y
Gemini CLI
$ npx skills add posthog/posthog --skill signals-scout-anomaly-detection -a gemini-cli -y
GitHub Copilot
$ npx skills add posthog/posthog --skill signals-scout-anomaly-detection -a github-copilot -y
OpenCode
$ npx skills add posthog/posthog --skill signals-scout-anomaly-detection -a opencode -y
Windsurf
$ npx skills add posthog/posthog --skill signals-scout-anomaly-detection -a windsurf -y
SKILL.md
signals-scout-anomaly-detection.SKILL.mdname: signals-scout-anomaly-detection
scout-display-name: Anomaly detection
description: >
Signals scout that surfaces anomalies in the project's saved insights — bursts, drops,
flat-lines, and trend breaks. Where anomaly scores exist, it judges the flagged episode starts;
elsewhere it watches the most-viewed dashboards and insights against each insight's own
seasonality-matched baseline.
compatibility: >
Runs as the PostHog Signals scout in a Claude sandbox with read-only analytics scopes
plus signal_scout_internal:write (scratchpad), signal_scout_report:write (the report
channel — emit_report / edit_report), and notebook:write (the notebook write-up behind
each report). Assumes the signals-scout MCP tool family plus the dashboard/insight,
anomaly-search, anomaly-insight-get, alert-simulate, inbox-reports, and notebook tools listed in the body's MCP tools section.
allowed_tools:
- emit_report
- edit_report
metadata:
owner_team: signals
scope: anomaly_detection
scout-precheck-query: |
WITH recent AS (
SELECT
toInt(properties.$anomaly_insight_id) AS insight_id,
properties.$anomaly_series_key AS series_key,
properties.$anomaly_series_label AS series_label,
properties.$anomaly_breakdown_value AS breakdown_value,
coalesce(toDateTime(properties.$anomaly_bucket_start), timestamp) AS bucket,
toInt(properties.$anomaly_flag) AS flag,
toFloat(properties.$anomaly_value) AS value,
toDateTime(properties.$anomaly_scored_at) AS scored_at,
JSONExtractString(properties, '$anomaly_details') AS details
FROM events
WHERE event = '$anomaly_score'
AND properties.$anomaly_source = 'insight'
AND properties.$anomaly_interval = 'day'
AND timestamp >= {now} - INTERVAL 4 DAY
ORDER BY scored_at DESC
LIMIT 1 BY series_key, bucket
),
candidates AS (
SELECT
insight_id,
series_key,
series_label,
breakdown_value,
bucket,
value,
if(JSONExtractRaw(details, 'raw_zscore') IN ('', 'null'), NULL, JSONExtractFloat(details, 'raw_zscore')) AS raw_zscore
FROM recent
WHERE flag = 1
AND scored_at > {since}
AND bucket >= {now} - INTERVAL 3 DAY
AND coalesce(raw_zscore, 99) >= 3.5
),
history AS (
SELECT
properties.$anomaly_series_key AS series_key,
coalesce(toDateTime(properties.$anomaly_bucket_start), timestamp) AS bucket,
toInt(properties.$anomaly_flag) AS flag,
toFloat(properties.$anomaly_value) AS value
FROM events
WHERE event = '$anomaly_score'
AND properties.$anomaly_source = 'insight'
AND properties.$anomaly_interval = 'day'
AND timestamp >= {now} - INTERVAL 35 DAY
AND series_key IN (SELECT series_key FROM candidates)
ORDER BY toDateTime(properties.$anomaly_scored_at) DESC
LIMIT 1 BY series_key, bucket
),
judged AS (
SELECT
c.insight_id AS insight_id,
c.series_key AS series_key,
c.series_label AS series_label,
c.breakdown_value AS breakdown_value,
c.bucket AS bucket,
c.value AS value,
c.raw_zscore AS raw_zscore,
countIf(h.bucket < c.bucket) AS prior_buckets,
countIf(h.bucket < c.bucket AND h.value != 0) AS nonzero_prior_buckets,
avgIf(h.flag, h.bucket < c.bucket) AS prior_flag_rate,
maxIf(h.flag, toDate(h.bucket) = toDate(c.bucket) - 1) AS previous_flag,
medianIf(h.value, h.bucket < c.bucket) AS median_28d,
medianIf(h.value, h.bucket < c.bucket AND toDayOfWeek(h.bucket) = toDayOfWeek(c.bucket)) AS median_same_weekday
FROM candidates AS c
INNER JOIN history AS h ON h.series_key = c.series_key
WHERE h.bucket >= c.bucket - INTERVAL 28 DAY AND h.bucket <= c.bucket
GROUP BY c.insight_id, c.series_key, c.series_label, c.breakdown_value, c.bucket, c.value, c.raw_zscore
)
SELECT
'scored' AS mode,
insight_id,
series_label,
breakdown_value,
toDate(bucket) AS bucket_date,
value,
round(median_28d, 2) AS median_28d,
round(median_same_weekday, 2) AS median_same_weekday,
round(raw_zscore, 2) AS raw_zscore,
round(prior_flag_rate, 3) AS prior_flag_rate,
prior_buckets
FROM judged
WHERE prior_buckets >= 14
AND nonzero_prior_buckets >= 0.8 * prior_buckets
AND prior_flag_rate < 0.15
AND coalesce(previous_flag, 0) = 0
AND greatest(value, median_28d) >= 20
AND abs(value - median_28d) >= 0.3 * median_28d
AND abs(value - median_same_weekday) >= 0.3 * median_same_weekday
ORDER BY abs(log(greatest(value, 1) / greatest(median_28d, 1))) DESC
LIMIT 1 BY insight_id, bucket_date
LIMIT 20
UNION ALL
SELECT 'legacy' AS mode, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
FROM (
SELECT uniqExact(toDate(coalesce(toDateTime(properties.$anomaly_bucket_start), timestamp))) AS scored_days
FROM events
WHERE event = '$anomaly_score'
AND properties.$anomaly_source = 'insight'
AND properties.$anomaly_interval = 'day'
AND timestamp >= {now} - INTERVAL 28 DAY
AND {managed_by} = 'team'
AND {since} < {now} - INTERVAL 23 HOUR
)
WHERE scored_days < 14
AND {managed_by} = 'team'
AND {since} < {now} - INTERVAL 23 HOURSignals scout: dashboard & insight anomalies
You are a focused anomaly-detection scout. You watch the dashboards and insights this team actually cares about and surface **recent** anomalies in them — a metric that suddenly spiked, cratered, flat-lined, or broke its trend in the last few hours or days — so a human gets told before they'd notice on their own.
Step 0: pick the mode
Pick one mode at the start of each run, before any other work. The mode sets the method for t
Read more
name: signals-scout-anomaly-detection
scout-display-name: Anomaly detection
description: >
Signals scout that surfaces anomalies in the project's saved insights — bursts, drops,
flat-lines, and trend breaks. Where anomaly scores exist, it judges the flagged episode starts;
elsewhere it watches the most-viewed dashboards and insights against each insight's own
seasonality-matched baseline.
compatibility: >
Runs as the PostHog Signals scout in a Claude sandbox with read-only analytics scopes
plus signal_scout_internal:write (scratchpad), signal_scout_report:write (the report
channel — emit_report / edit_report), and notebook:write (the notebook write-up behind
each report). Assumes the signals-scout MCP tool family plus the dashboard/insight,
anomaly-search, anomaly-insight-get, alert-simulate, inbox-reports, and notebook tools listed in the body's MCP tools section.
allowed_tools:
- emit_report
- edit_report
metadata:
owner_team: signals
scope: anomaly_detection
scout-precheck-query: |
WITH recent AS (
SELECT
toInt(properties.$anomaly_insight_id) AS insight_id,
properties.$anomaly_series_key AS series_key,
properties.$anomaly_series_label AS series_label,
properties.$anomaly_breakdown_value AS breakdown_value,
coalesce(toDateTime(properties.$anomaly_bucket_start), timestamp) AS bucket,
toInt(properties.$anomaly_flag) AS flag,
toFloat(properties.$anomaly_value) AS value,
toDateTime(properties.$anomaly_scored_at) AS scored_at,
JSONExtractString(properties, '$anomaly_details') AS details
FROM events
WHERE event = '$anomaly_score'
AND properties.$anomaly_source = 'insight'
AND properties.$anomaly_interval = 'day'
AND timestamp >= {now} - INTERVAL 4 DAY
ORDER BY scored_at DESC
LIMIT 1 BY series_key, bucket
),
candidates AS (
SELECT
insight_id,
series_key,
series_label,
breakdown_value,
bucket,
value,
if(JSONExtractRaw(details, 'raw_zscore') IN ('', 'null'), NULL, JSONExtractFloat(details, 'raw_zscore')) AS raw_zscore
FROM recent
WHERE flag = 1
AND scored_at > {since}
AND bucket >= {now} - INTERVAL 3 DAY
AND coalesce(raw_zscore, 99) >= 3.5
),
history AS (
SELECT
properties.$anomaly_series_key AS series_key,
coalesce(toDateTime(properties.$anomaly_bucket_start), timestamp) AS bucket,
toInt(properties.$anomaly_flag) AS flag,
toFloat(properties.$anomaly_value) AS value
FROM events
WHERE event = '$anomaly_score'
AND properties.$anomaly_source = 'insight'
AND properties.$anomaly_interval = 'day'
AND timestamp >= {now} - INTERVAL 35 DAY
AND series_key IN (SELECT series_key FROM candidates)
ORDER BY toDateTime(properties.$anomaly_scored_at) DESC
LIMIT 1 BY series_key, bucket
),
judged AS (
SELECT
c.insight_id AS insight_id,
c.series_key AS series_key,
c.series_label AS series_label,
c.breakdown_value AS breakdown_value,
c.bucket AS bucket,
c.value AS value,
c.raw_zscore AS raw_zscore,
countIf(h.bucket < c.bucket) AS prior_buckets,
countIf(h.bucket < c.bucket AND h.value != 0) AS nonzero_prior_buckets,
avgIf(h.flag, h.bucket < c.bucket) AS prior_flag_rate,
maxIf(h.flag, toDate(h.bucket) = toDate(c.bucket) - 1) AS previous_flag,
medianIf(h.value, h.bucket < c.bucket) AS median_28d,
medianIf(h.value, h.bucket < c.bucket AND toDayOfWeek(h.bucket) = toDayOfWeek(c.bucket)) AS median_same_weekday
FROM candidates AS c
INNER JOIN history AS h ON h.series_key = c.series_key
WHERE h.bucket >= c.bucket - INTERVAL 28 DAY AND h.bucket <= c.bucket
GROUP BY c.insight_id, c.series_key, c.series_label, c.breakdown_value, c.bucket, c.value, c.raw_zscore
)
SELECT
'scored' AS mode,
insight_id,
series_label,
breakdown_value,
toDate(bucket) AS bucket_date,
value,
round(median_28d, 2) AS median_28d,
round(median_same_weekday, 2) AS median_same_weekday,
round(raw_zscore, 2) AS raw_zscore,
round(prior_flag_rate, 3) AS prior_flag_rate,
prior_buckets
FROM judged
WHERE prior_buckets >= 14
AND nonzero_prior_buckets >= 0.8 * prior_buckets
AND prior_flag_rate < 0.15
AND coalesce(previous_flag, 0) = 0
AND greatest(value, median_28d) >= 20
AND abs(value - median_28d) >= 0.3 * median_28d
AND abs(value - median_same_weekday) >= 0.3 * median_same_weekday
ORDER BY abs(log(greatest(value, 1) / greatest(median_28d, 1))) DESC
LIMIT 1 BY insight_id, bucket_date
LIMIT 20
UNION ALL
SELECT 'legacy' AS mode, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
FROM (
SELECT uniqExact(toDate(coalesce(toDateTime(properties.$anomaly_bucket_start), timestamp))) AS scored_days
FROM events
WHERE event = '$anomaly_score'
AND properties.$anomaly_source = 'insight'
AND properties.$anomaly_interval = 'day'
AND timestamp >= {now} - INTERVAL 28 DAY
AND {managed_by} = 'team'
AND {since} < {now} - INTERVAL 23 HOUR
)
WHERE scored_days < 14
AND {managed_by} = 'team'
AND {since} < {now} - INTERVAL 23 HOURSignals scout: dashboard & insight anomalies
You are a focused anomaly-detection scout. You watch the dashboards and insights this team actually cares about and surface **recent** anomalies in them — a metric that suddenly spiked, cratered, flat-lined, or broke its trend in the last few hours or days — so a human gets told before they'd notice on their own.
Step 0: pick the mode
Pick one mode at the start of each run, before any other work. The mode sets the method for t
:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.
More skills from other plugins.
paddleocr-doc-parsing
Use this skill to extract structured Markdown/JSON from PDFs and document images—tables with cell-level precision, formulas as…
13c-metabolic-flux
Estimates intracellular metabolic fluxes from steady-state carbon-13 isotope-tracing measurements using validated atom maps,…
Questions about signals-scout-anomaly-detection.
- Which coding agents can use signals-scout-anomaly-detection?
- signals-scout-anomaly-detection installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

