Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion skills/omnibus/auditing-experiments-flags/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ When the user asks for a comprehensive audit of both experiments and flags:
1. Fetch all experiments via `experiment-list` and all flags via `feature-flag-get-all`.
2. Run all experiment checks and all flag checks.
3. Apply [recurring patterns](./references/synthesis-patterns.md) to identify patterns across multiple findings.
4. If there are more than 5 entities with findings, output as a notebook artifact via `notebooks-create` for easier navigation. Otherwise report inline.
4. If there are more than 5 entities with findings, write them to a notebook for easier navigation. Otherwise report inline. Create the notebook from the project's own notebook tools. Run `search notebooks?-` to load them and read the titles.

## Output format

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Discriminated on `config.type`:
- `tracking_pixel` — a bodyless `GET`, so read query params: `"inputs": { "event": { "value": "{request.query.ph_event}" }, "distinct_id": { "value": "{request.query.ph_distinct_id}" } }`. Body references resolve empty here and the pixel still returns its 200 GIF, so every hit is dropped with no error to retry on.
- `batch` — `{ "type": "batch", "filters": { "properties": [<cond>] } }`. The audience: person-property conditions and/or cohort references. **No event/action filters** (silently dropped, so rejected). Does not fire on enable — dispatch a one-off broadcast with `workflows-run-batch`, or make it **recurring** with `workflows-schedule-create` (attaches an RRULE schedule; each firing re-broadcasts to this same `config.filters.properties` audience). A recurring broadcast is a `batch` trigger plus a schedule. Editing `config.filters` pauses any schedule attached to it, since the confirmed recipient count no longer holds - preview again and re-create the cadence.
- `schedule` — `{ "type": "schedule" }`. One person-less run per occurrence, for jobs that act on their own (for example a "Create AI task" step). Attach the cadence with `workflows-schedule-create`; no audience preview is needed. Changing this trigger to `batch` pauses any schedule already attached, because that cadence was never sized against an audience - re-create it after the blast-radius preview.
- `internal-event` (flag-gated) — `{ "type": "internal-event", "filters": { "source": "internal-events", "events": [{ "id": "<event>", "type": "events" }], "properties": [<cond>] } }`. Fires once for each matching event on the internal-events stream; `filters.events` must name at least one allowed event id. Runs have no associated person, so person-dependent steps are unavailable. Test-run with an event named in `filters.events` and no person; any other event name is skipped at the trigger. The Slack trigger is this with `$slack_message_received` in `filters.events`: the run's event carries properties `integration_id`, `channel`, `channel_type`, `slack_team_id`, `user`, `bot_id`, `app_id`, `subtype`, `text`, `ts`, `thread_ts`, `is_thread_reply`, `is_ext_shared_channel`, and `slack_event` (the raw Slack payload), and a workflow leaving draft must include an exact `channel` property filter. The GitHub trigger uses `$github_event_received` and requires exact `repository` and `event_type` filters.
- `internal-event` (flag-gated) — `{ "type": "internal-event", "filters": { "source": "internal-events", "events": [{ "id": "<event>", "type": "events" }], "properties": [<cond>] } }`. Fires once for each matching event on the internal-events stream; `filters.events` must name at least one allowed event id. Runs have no associated person, so person-dependent steps are unavailable. Test-run with an event named in `filters.events` and no person; any other event name is skipped at the trigger. The Slack trigger is this with `$slack_message_received` in `filters.events`: the run's event carries properties `integration_id`, `channel`, `channel_type`, `slack_team_id`, `user`, `bot_id`, `app_id`, `subtype`, `text`, `ts`, `thread_ts`, `is_thread_reply`, `is_ext_shared_channel`, and `slack_event` (the raw Slack payload). A workflow leaving draft must include one exact `channel` property filter whose value contains one or more channel IDs; several IDs match any listed channel. The GitHub trigger uses `$github_event_received` and requires exact `repository` and `event_type` filters.

### Trigger masking (throttling an event trigger)

Expand Down
15 changes: 7 additions & 8 deletions skills/omnibus/choosing-trend-or-slope-view/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,13 @@ are the points you want compared (the slope uses the first and last interval).
## Important limits

- **Two surfaces, one computation.** Both the inline slope (Max's `query-trends`
result card) and the saved-insight slope (`ChartDisplayType.SlopeGraph`, behind the
`slope-graph-insight` feature flag) show the same thing: the first interval's value
vs the last interval's value, at the chosen group-by interval. The grouping defines
the slope — group by month to compare the first vs last month, by day for the first
vs last day. Use the inline view for a quick before→after on a result you're already
looking at; reach for the saved display to persist it on a dashboard where the flag
is enabled. A still-accumulating final period is shown as-is with a dashed connector,
the same affordance the line chart uses for an incomplete tail.
result card) and the saved-insight slope (`ChartDisplayType.SlopeGraph`) show the
same thing: the first interval's value vs the last interval's value, at the chosen
group-by interval. The grouping defines the slope - group by month to compare the
first vs last month, by day for the first vs last day. Use the inline view for a
quick before-and-after on a result you're already looking at; use the saved display
to persist it on a dashboard. A still-accumulating final period is shown as-is with
a dashed connector, the same affordance the line chart uses for an incomplete tail.
- For period-over-period on a single series (this month vs last), a line with
`compareFilter: { "compare": true }` overlays the two periods; a slope is the
better fit when comparing the endpoints of **many** series at once.
16 changes: 12 additions & 4 deletions skills/omnibus/copying-flags-across-projects/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,28 @@ Targets must be in the same organization as the source. Call `posthog:projects-g

For a multi-target copy, the tool accepts up to 50 target project ids in a single call. Successes and failures are reported per target, so a partial failure does not block the rest.

### 3. Preview the source flag
### 3. Preview the source flag and its dependencies

Call `posthog:feature-flag-get-definition` on the source flag and present a concise summary to the user before copying:
Call `posthog:feature-flag-get-definition` on the source flag. Then always call `posthog:feature-flags-copy-dependencies-check` with `feature_flag_key`, `from_project`, and `target_project_ids`, rather than trying to detect dependencies first — it copies nothing, and returns an empty result with no warnings when the flag has none, so running it on every copy is cheap and never skips a real dependency.

A false `can_copy_dependencies` does not always mean the copy is blocked. It is also false when there is nothing to copy. Report a problem only when `warnings` is non-empty.

Present a concise summary to the user before copying:

- Flag key, name, and active state in the source
- Filter groups (rollout %, property filters, variant splits)
- Any cohort references in `filters.groups[].properties[]` — these will be remapped server-side, but the user should know whether the target project already has matching cohorts
- Whether the flag has encrypted payloads (`has_encrypted_payloads`) or is remote configuration (`is_remote_configuration`)
- Whether scheduled changes exist (the user can opt to copy them in step 4)
- The dependency check's `copied_dependency_keys` (dependencies a copy would create in a target), `reused_dependency_keys` (dependencies a target already satisfies), and any `warnings`

### 4. Confirm copy options

Default to the safest combination and ask the user to override only if they explicitly want different behavior:
One confirmation covers all three options. Default to the safest combination and ask the user to override only if they explicitly want different behavior:

- **`disable_copied_flag: true`** — the copied flag lands disabled in the target. Recommended by default; turning a flag on in a new project should be a deliberate, observed action.
- **`copy_schedule: false`** — scheduled changes do not come along. Recommended by default; schedules are usually project-specific.
- **`copy_dependencies: false`** — dependency flags do not come along. Set it to `true` only when the user approves copying the keys step 3's check reported.

If the user says "promote it as-is" or "turn it on in prod", switch `disable_copied_flag` to `false`. If they say "include the rollout schedule" or "with the scheduled rollout", switch `copy_schedule` to `true`.

Expand All @@ -64,6 +70,7 @@ Call `posthog:feature-flags-copy-flags-create` with:
- `target_project_ids`: the resolved list of target project ids
- `disable_copied_flag`: from step 4 (default `true`)
- `copy_schedule`: from step 4 (default `false`)
- `copy_dependencies`: from step 4 (default `false`). Omitting it silently skips dependency copying, even when the user approved it.

### 6. Report per-target outcome

Expand Down Expand Up @@ -104,7 +111,8 @@ If any targets failed, ask the user whether to retry the failed ones, skip them,

## Available tools

- `posthog:feature-flags-copy-flags-create` — performs the copy. Required fields: `feature_flag_key`, `from_project`, `target_project_ids`. Optional: `disable_copied_flag`, `copy_schedule`.
- `posthog:feature-flags-copy-flags-create` — performs the copy. Required fields: `feature_flag_key`, `from_project`, `target_project_ids`. Optional: `disable_copied_flag`, `copy_schedule`, `copy_dependencies`.
- `posthog:feature-flags-copy-dependencies-check` — previews what a copy would do to the flag's transitive flag dependencies. Copies nothing. Call it before every copy; it returns an empty result when the flag has no dependencies.
- `posthog:feature-flag-get-all` — find a flag by key/name in a given project when the user only gave a friendly name.
- `posthog:feature-flag-get-definition` — fetch the full source flag (filters, variants, cohort references, encryption flags) so you can preview before copying.
- `posthog:projects-get` — list projects in the active organization, used to resolve and validate target project ids.
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ FROM
argMaxState(properties.$exception_functions.-1, timestamp) AS function_state,
argMaxState(properties.$exception_sources.-1, timestamp) AS source_state,
argMaxState(properties.$lib, timestamp) AS library_state,
least(19, intDiv(dateDiff('seconds', toDateTime(toDateTime('2026-09-05 09:00:00.000000')), timestamp), greatest(1, intDiv(dateDiff('seconds', toDateTime(toDateTime('2026-09-05 09:00:00.000000')), toDateTime(toDateTime('2026-09-06 09:19:14.282325'))), 20)))) AS bin_idx,
least(19, intDiv(dateDiff('seconds', toDateTime(toDateTime('2026-09-06 11:00:00.000000')), timestamp), greatest(1, intDiv(dateDiff('seconds', toDateTime(toDateTime('2026-09-06 11:00:00.000000')), toDateTime(toDateTime('2026-09-07 11:51:49.000722'))), 20)))) AS bin_idx,
count() AS occ,
uniqState(nullIf(e.$session_id, '')) AS sessions_state,
uniqState(coalesce(nullIf(toString(e.event_person_id), '00000000-0000-0000-0000-000000000000'), e.distinct_id)) AS users_state
FROM
events AS e
WHERE
and(equals(e.event, '$exception'), isNotNull(e.properties.$exception_fingerprint), true, greaterOrEquals(e.timestamp, toDateTime(toDateTime('2026-09-05 09:00:00.000000'))), lessOrEquals(e.timestamp, toDateTime(toDateTime('2026-09-06 09:19:14.282325'))), or(greater(position(lower(e.properties.$exception_types), lower('constant')), 0), greater(position(lower(e.properties.$exception_values), lower('constant')), 0), greater(position(lower(e.properties.$exception_sources), lower('constant')), 0), greater(position(lower(e.properties.$exception_functions), lower('constant')), 0), greater(position(lower(e.properties.email), lower('constant')), 0), greater(position(lower(e.person.properties.email), lower('constant')), 0)), equals(properties.tag, 'max_ai'))
and(equals(e.event, '$exception'), isNotNull(e.properties.$exception_fingerprint), true, greaterOrEquals(e.timestamp, toDateTime(toDateTime('2026-09-06 11:00:00.000000'))), lessOrEquals(e.timestamp, toDateTime(toDateTime('2026-09-07 11:51:49.000722'))), or(greater(position(lower(e.properties.$exception_types), lower('constant')), 0), greater(position(lower(e.properties.$exception_values), lower('constant')), 0), greater(position(lower(e.properties.$exception_sources), lower('constant')), 0), greater(position(lower(e.properties.$exception_functions), lower('constant')), 0), greater(position(lower(e.properties.email), lower('constant')), 0), greater(position(lower(e.person.properties.email), lower('constant')), 0)), equals(properties.tag, 'max_ai'))
GROUP BY
fp_hash,
bin_idx) AS ev
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ SELECT
FROM
logs
WHERE
and(and(greaterOrEquals(toStartOfDay(time_bucket), toStartOfDay(assumeNotNull(toDateTime('2025-12-09 00:00:00')))), lessOrEquals(toStartOfDay(time_bucket), toStartOfDay(assumeNotNull(toDateTime('2025-12-10 00:00:00'))))), 1, greaterOrEquals(timestamp, toDateTime('2026-09-05 09:19:15.045917')), indexHint(like(lower(body), '%timeout%')), ilike(toString(body), '%timeout%'), in(severity_text, tuple('warn', 'error', 'fatal')))
and(and(greaterOrEquals(toStartOfDay(time_bucket), toStartOfDay(assumeNotNull(toDateTime('2025-12-09 00:00:00')))), lessOrEquals(toStartOfDay(time_bucket), toStartOfDay(assumeNotNull(toDateTime('2025-12-10 00:00:00'))))), 1, greaterOrEquals(timestamp, toDateTime('2026-09-06 11:51:49.799122')), indexHint(like(lower(body), '%timeout%')), ilike(toString(body), '%timeout%'), in(severity_text, tuple('warn', 'error', 'fatal')))
ORDER BY
timestamp DESC,
uuid DESC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ SELECT
sum(s.console_error_count) AS console_error_count,
max(s.retention_period_days) AS retention_period_days,
plus(dateTrunc('DAY', start_time), toIntervalDay(coalesce(retention_period_days, 30))) AS expiry_time,
date_diff('DAY', toDateTime('2026-09-06 09:19:15.327807'), expiry_time) AS recording_ttl,
greaterOrEquals(max(s._timestamp), toDateTime('2026-09-06 09:14:15.327300')) AS ongoing,
date_diff('DAY', toDateTime('2026-09-07 11:51:50.070056'), expiry_time) AS recording_ttl,
greaterOrEquals(max(s._timestamp), toDateTime('2026-09-07 11:46:50.069514')) AS ongoing,
round(least(greatest(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 0), 100), 2) AS activity_score,
coalesce(max(s.surfacing_score), 0.36) AS surfacing_score
FROM
raw_session_replay_events AS s
WHERE
and(greaterOrEquals(s.min_first_timestamp, toDateTime('2026-09-03 00:00:00.000000')), lessOrEquals(s.min_first_timestamp, toDateTime('2026-09-06 09:19:15.327479')))
and(greaterOrEquals(s.min_first_timestamp, toDateTime('2026-09-04 00:00:00.000000')), lessOrEquals(s.min_first_timestamp, toDateTime('2026-09-07 11:51:50.069704')))
GROUP BY
session_id
HAVING
and(greaterOrEquals(expiry_time, toDateTime('2026-09-06 09:19:15.327704')), equals(max(s.is_deleted), 0), greater(active_seconds, 5.0))
and(greaterOrEquals(expiry_time, toDateTime('2026-09-07 11:51:50.069937')), equals(max(s.is_deleted), 0), greater(active_seconds, 5.0))
ORDER BY
start_time DESC,
session_id DESC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ SELECT
FROM
sessions
WHERE
and(less($start_timestamp, toDateTime('2026-09-06 09:19:21.250701')), greater($start_timestamp, toDateTime('2026-09-05 09:19:16.251095')))
and(less($start_timestamp, toDateTime('2026-09-07 11:51:55.948743')), greater($start_timestamp, toDateTime('2026-09-06 11:51:50.949169')))
ORDER BY
$start_timestamp DESC
LIMIT 50000
Expand Down
2 changes: 1 addition & 1 deletion skills/omnibus/signals-scout-session-replay/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ Direct calls (read-only):
- `query-session-recordings-list` — resolve `$session_id`s to watchable recordings (pass `session_ids` + a matching `date_from`); order by `console_error_count` or `activity_score` when shortlisting.
- `session-recording-get` — one recording's metadata for a finding's example links.
- `session-recording-summaries-list` / `session-recording-summary-get` — stored AI summaries (list filters: `session_ids`, `has_exceptions`, `outcome`; get returns segment-level detail). A 404 just means no summary exists — never trigger generation.
- `heatmaps-list` / `heatmaps-events` — spatial corroboration for a cluster. Feature-gated: skip silently if absent.
- `heatmaps-list` / `heatmaps-events` — spatial corroboration for a cluster.
- `vision-scanners-list` / `vision-scanners-observations-list` / `vision-observations-list` / `vision-quota-retrieve` — scanner config, observation health, and quota. Feature-gated and often absent even where replay vision is in use — lead with `$recording_observed` SQL; these are the optional mechanism-confirmation layer.
- `advanced-activity-logs-list` (`scopes: ["Team"]` + `start_date`/`end_date`) — dating recording-config changes against capture cliffs.
- `read-data-schema` — confirm `$rageclick` / `$dead_click` / replay SDK properties exist before aggregating. Inbox & reviewer routing (mechanics in `authoring-scouts` → `references/report-contract.md`):
Expand Down
3 changes: 2 additions & 1 deletion skills/omnibus/skills-store/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ PostHog is the primary store for team-shared skills — always use the PostHog M
| `posthog:skill-file-delete` | Remove one bundled file from a skill |
| `posthog:skill-file-rename` | Rename one bundled file (move without rewriting content) |
| `posthog:skill-duplicate` | Duplicate an existing skill under a new name |
| `posthog:skill-rename` | Rename a skill, keeping its versions, files, and owners |
| `posthog:skill-archive` | Archive all versions of a skill by name (cannot be undone) |

Skills use progressive disclosure: discover by description, fetch the body only when relevant, and pull individual files on demand. Do not fetch every file eagerly.
Expand Down Expand Up @@ -232,7 +233,7 @@ description: >-
Use when the user asks to list, run, or manage PostHog skills,
or references /phs, "ph skills", or "posthog skills".
user-invocable: true
allowed-tools: mcp__posthog__skill-list, mcp__posthog__skill-get, mcp__posthog__skill-create, mcp__posthog__skill-update, mcp__posthog__skill-file-get, mcp__posthog__skill-file-create, mcp__posthog__skill-file-delete, mcp__posthog__skill-file-rename, mcp__posthog__skill-duplicate
allowed-tools: mcp__posthog__skill-list, mcp__posthog__skill-get, mcp__posthog__skill-create, mcp__posthog__skill-update, mcp__posthog__skill-file-get, mcp__posthog__skill-file-create, mcp__posthog__skill-file-delete, mcp__posthog__skill-file-rename, mcp__posthog__skill-duplicate, mcp__posthog__skill-rename
---

# PostHog Skills Store
Expand Down
3 changes: 3 additions & 0 deletions skills/omnibus/working-with-skills/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Editing an existing skill?
│ └─ Rename .......................... skill-file-rename
└─ Wholesale bundle reset (rare!) ....... update(files=[...]) # replaces ALL files

Renaming the skill itself?
└─► skill-rename (keeps versions, files, and owners)

Want a fork as the starting point?
└─► skill-duplicate (then update the copy)

Expand Down