# Cro Backlog Monitor

> Twice-daily scan of idle pages (6 AM & 6 PM UTC): dispatch builds, notify when pages need prioritization, trigger research for empty backlogs. Runs as the first pass of the cro-monitor cron.

- Skill: `warpdotdev/cro-backlog-monitor` (Agent Skill)
- Install (CLI): `npx skillmds@latest add warpdotdev/cro-backlog-monitor`
- Raw SKILL.md: https://api.skillmd.com/api/skills/warpdotdev/cro-backlog-monitor/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: warpdotdev (https://skillmd.com/u/warpdotdev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/warpdotdev/cro-backlog-monitor

---


# Backlog Monitor

## Configuration

Read `.warp/skills/cro/references/config.md` for Notion IDs and data access.
Read `.warp/skills/cro/references/config-slack.md` for Slack posting config (token, channels, Thread TS protocol).

## Operating Rules

- **One experiment per page.** Never dispatch a build on a page that has an active experiment (Status in `3-building`, `4-ready for review`, `5-live:validating`, `6-live:running`, `7-analyzing`). Also treat a page as blocked if it has a recently analyzed final-status row (`8-no-ship`, `8-inconclusive`, or `9-shipped`) with a non-empty `Ship/Kill PR` that has not merged yet; that is a lifecycle inconsistency, not an idle page.
- **CRO-only scope.** Never dispatch SEO-only tests. Read `references/config.md` → Experiment Scope. If a `1-proposed`, `2-prioritized`, or `3-building` Roadmap entry is clearly SEO-only, set Status → `0-archived`, log the reason in the agent session, and do not post to Slack.
- **Only process experiment-eligible pages.** Skip Page Registry entries whose `Research Status` is `low-traffic`, `cro-excluded`, or `needs-primary-metric`; do not dispatch builds, request prioritization, trigger research, or post Slack messages for them.
- **Agents never prioritize.** Never change status to `2-prioritized`.
- **No duplicate idle reminders.** Do not post a new page-level idle notification if an existing top-level idle notification for the same page is already present in #your-notifications-channel. The existing message is the reminder; repeated cron runs must stay silent.
- **Parallel dispatches are fine.** Multiple pages can have builds dispatched in the same run — each triggers its own agent.
- Follow the **Slack Thread TS protocol** from `config-slack.md` for all posts.
- **Slack token: use ONLY `$EXPERIMENT_BUD_SLACK_BOT_TOKEN`.** See the Token section in `config-slack.md` for the full rule.

## Procedure

### Step 1: Read pipeline state

Query the Roadmap & Backlog for all non-archived entries:

```bash
curl -s -X POST "https://api.notion.com/v1/databases/NOTION_ROADMAP_DB_ID/query" \
  -H "Authorization: Bearer $NOTION_INTEGRATION_TOKEN" \
  -H "Notion-Version: 2022-06-28" \
  -H "Content-Type: application/json" \
  -d '{"filter": {"property": "Status", "select": {"does_not_equal": "0-archived"}}}'
```

Before grouping, classify Roadmap entries with the SEO-only scope rules in `references/config.md`. Archive clearly SEO-only entries in `1-proposed`, `2-prioritized`, or `3-building` by setting Status → `0-archived`; exclude them from active/prioritized/proposed counts. Do not post a Slack notification for this cleanup.

Group by Page path. For each page, note:
- Active experiments: any entry with Status in `3-building`, `4-ready for review`, `5-live:validating`, `6-live:running`, `7-analyzing`
- Pending cleanup experiments: any entry with Status in `8-no-ship`, `8-inconclusive`, or `9-shipped` and a non-empty `Ship/Kill PR`. Check the PR state with `gh pr view`. If the PR is not merged, count the page as active/blocked, log `Lifecycle inconsistency: final status but Ship/Kill PR unmerged for {page_path}: {pr_url}`, and do not dispatch another build or post an idle-page notification for that page.
- Prioritized hypotheses: Status = `2-prioritized`, sorted by RICE Score descending
- Proposed hypotheses: Status = `1-proposed`

Also query the Page Registry for all pages where:
- `CRO Excluded = false`
- `Research Status` is NOT `low-traffic`
- `Research Status` is NOT `cro-excluded`
- `Research Status` is NOT `needs-primary-metric`

For each eligible page, also read:
- `Owner` — determines who to @-mention in page-specific Slack posts (see `references/config.md` → Page Owners)
- `Daily Traffic` — used only as a defensive cross-check; if it is present and `< 50`, skip the page silently even if `Research Status` is stale
- `Research Status` — log skip reasons in agent output if an ineligible page appears in the result set unexpectedly

Pages with ineligible research statuses are waiting on traffic or human input — don't auto-dispatch builds, request prioritization, trigger research, or post Slack messages for them. `page_registry_sync` will auto-advance them once they become eligible. If `Daily Traffic` is present and `< 50`, skip the page silently even if `Research Status` is stale.

### Step 2: Process each idle page

For each experiment-eligible page with **no active experiment**:

**2a. Has `2-prioritized` hypotheses → dispatch build**

Pick the highest RICE score entry. Update its status to `3-building`:

```bash
curl -s -X PATCH "https://api.notion.com/v1/pages/{{HYPOTHESIS_PAGE_ID}}" \
  -H "Authorization: Bearer $NOTION_INTEGRATION_TOKEN" \
  -H "Notion-Version: 2022-06-28" \
  -H "Content-Type: application/json" \
  -d '{"properties": {"Status": {"select": {"name": "3-building"}}}}'
```

This triggers the build agent via Notion automation → bud_server.

The build agent handles the Slack notification for this experiment.

**2b. No `2-prioritized` but has `1-proposed` → request prioritization**

Before posting, dedupe against existing page-level idle reminders:

1. Call Slack `conversations.history` for #your-notifications-channel (`SLACK_NOTIFICATIONS_CHANNEL_ID`) and scan recent top-level messages from the `experiment-bud` app, paginating at least 30 days back or 1,000 messages, whichever comes first.
2. Treat messages from either `app_id = SLACK_APP_ID`, `bot_id = SLACK_BOT_ID`, or `user = SLACK_BOT_USER_ID` as Experiment Bud messages. Do not rely on `username`.
3. For each candidate idle reminder, parse the page path with this exact regex so both rendered emoji and Slack emoji aliases are handled:
   `^(?:⏳|:hourglass_flowing_sand:)\s+(.*?)\s+is idle(?:\s+with|:)`
   Compare that parsed path to the current `page_path` using exact string equality after trimming whitespace. Do not use substring matching because the homepage path `/` would match every page path.
4. If any candidate has the same parsed page path and the idle-reminder phrase `is idle` plus `proposed hypothes` plus `none prioritized`, skip posting for that page. Log `Skipping duplicate idle reminder for {page_path}: existing Slack message {ts}` in the agent output.
5. Only post if no existing idle reminder for that page is found.

Post to #your-notifications-channel (as a new message, not threaded — this is a page-level notification). Use the canonical snippet from `config-slack.md` with `channel='SLACK_NOTIFICATIONS_CHANNEL_ID'` and no `thread_ts`. Resolve `{owner_mention}` from the page's `Owner` (per `config-slack.md` → Page Owners); if `Owner` is empty, omit the mention entirely.

Do not post this notification for pages whose `Research Status` is `low-traffic`, `cro-excluded`, or `needs-primary-metric`, or whose `Daily Traffic` is present and `< 50`. These pages are not currently experiment-eligible, so asking humans to prioritize hypotheses creates noise.

```
⏳ {page_path} is idle: {n} proposed {hypothesis/hypotheses}, none prioritized.
{owner_mention} — choose the next experiment in <{roadmap_url}|Roadmap>: prioritize an existing idea or add a new one with status 2-prioritized.
```

Use `hypothesis` when `n=1`; otherwise use `hypotheses`.

**2c. No hypotheses at all (no `1-proposed`, no `2-prioritized`) → trigger research**

Update the Page Registry entry's Research Status to `generate-more-hypotheses`:

```bash
curl -s -X PATCH "https://api.notion.com/v1/pages/{{PAGE_REGISTRY_ENTRY_ID}}" \
  -H "Authorization: Bearer $NOTION_INTEGRATION_TOKEN" \
  -H "Notion-Version: 2022-06-28" \
  -H "Content-Type: application/json" \
  -d '{"properties": {"Research Status": {"select": {"name": "generate-more-hypotheses"}}}}'
```

This triggers the research agent via Notion automation → bud_server.

### Step 3: Done

This skill's only Slack output is the `⏳` idle page notification from Step 2b.

