Skip to main content
ClaudeWave
Skill510 estrellas del repoactualizado today

fork-first-run-alert

This Claude Code skill monitors when forks of the parent repository complete their first workflow run and immediately emits a named alert, catching new activations the day they happen rather than waiting up to six days for the weekly cohort snapshot. It reads from cached fork-cohort state and a persistent seen-list, diffs them daily, and notifies operators with fork metadata when a new active fork is detected for the first time.

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/aaronjmars/aeon /tmp/fork-first-run-alert && cp -r /tmp/fork-first-run-alert/skills/fork-first-run-alert ~/.claude/skills/fork-first-run-alert
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

> **${var}** — Optional. `dry-run` skips notify (state still updates). `owner/repo` overrides the parent repo. Empty = normal run.

Today is ${today}. A new fork completing its very first workflow run is the highest-signal community event Aeon emits — someone deployed, configured secrets, and actually ran the agent. `fork-cohort` already names this transition (`NEW_ACTIVE`) but only fires on Sundays. Mid-week activations sit in the void for up to 6 days until the next cohort run. This skill catches them the day they happen.

## Why this exists

`fork-cohort` (Sunday 19:00 UTC) bucketises every fork as COLD / STALE / ACTIVE / POWER and flags weekly transitions — but a fork that activates Monday morning waits 6 days before anyone notices. Two costs:

- **Operator** loses the chance to reach out while the new fork owner is still in setup-flow with the agent open in another tab.
- **New operator** doesn't feel seen — Aeon's "you matter to us" loop runs on a weekly cadence when activation itself is a same-day event.

This skill closes the gap with a daily cron that diffs the cohort's ACTIVE set against a persistent seen-list and emits per-fork named alerts the day each fork first runs.

## Two-sided value

| Side | What they get |
|------|---------------|
| Operator (@aaronjmars + community ops) | Same-day named ping — "Fork `speend/aeon` just ran its first skill" — with link, run count if detectable, fork stargazers |
| New fork owner | The community sees them on day one rather than waiting through a six-day silent cohort cycle |

## Inputs

Reads in this order:

| Source | Purpose | Required? |
|--------|---------|-----------|
| `memory/topics/fork-cohort-state.json` | Cached ACTIVE/POWER list — preferred fast-path, no API hits per fork | Optional |
| `memory/topics/fork-first-run-state.json` | Persistent seen-list of forks already alerted | Auto-created on first run |
| `gh api repos/{parent}/forks?per_page=100 --paginate` | Live fallback when cohort state is missing or >8 days stale | Fallback only |
| `gh api repos/{fork}/actions/runs?per_page=1` | Per new-active fork — fetch most-recent-run metadata for the alert | Per new fork |

Writes:
- `memory/topics/fork-first-run-state.json` — updated seen-list every run.
- `memory/logs/${today}.md` — one log block per run, even on `QUIET`.
- Notification via `./notify` — only when a gate fires.

No new secrets. Uses `gh api` exclusively (auth via `GITHUB_TOKEN`).

## State schema

`memory/topics/fork-first-run-state.json`:

```json
{
  "parent_repo": "aaronjmars/aeon",
  "last_run": "2026-05-17",
  "last_status": "FORK_FIRST_RUN_ALERT_OK",
  "seen": {
    "speend/aeon": {
      "first_seen_active_at": "2026-05-15",
      "first_seen_active_run_at": "2026-05-14T18:32:00Z",
      "announced_at": "2026-05-15",
      "stargazers": 0
    }
  }
}
```

Key invariants:
- `seen[fork]` is set the first run the fork shows up as ACTIVE/POWER. Once present, it is never re-announced.
- `first_seen_active_run_at` is the fork's most-recent-workflow-run timestamp at announce time — informational, not used for gating.
- LRU cap: 500 entries. When the cap is hit, drop the oldest by `announced_at` to keep the file bounded for long-running deployments.

## Steps

### 1. Parse var

- If `${var}` matches `^dry-run` → `MODE=dry-run`. Strip the prefix; remainder is treated as the parent override.
- Otherwise `MODE=execute`.
- If the remainder matches `^[a-z0-9][a-z0-9-]*/[a-zA-Z0-9._-]+$` (case-insensitive owner/repo) → `PARENT_OVERRIDE` is set.
- Otherwise the remainder must be empty; non-empty unparseable → log `FORK_FIRST_RUN_ALERT_BAD_VAR: ${var}` and exit (no notify).

### 2. Resolve parent repo

```bash
mkdir -p memory/topics
[ -f memory/topics/fork-first-run-state.json ] || echo '{"parent_repo":null,"last_run":null,"last_status":null,"seen":{}}' > memory/topics/fork-first-run-state.json

if [ -n "${PARENT_OVERRIDE}" ]; then
  PARENT_REPO="${PARENT_OVERRIDE}"
else
  PARENT_REPO=$(gh api repos/$(gh repo view --json nameWithOwner -q .nameWithOwner) --jq '.parent.full_name // .full_name')
fi
```

If `PARENT_REPO` changes vs the value already stored in state, treat the seen-list as scoped to the prior parent and **reset it** for the new parent (a manual var override with a different upstream is a new tracking universe). Write a `_Reset: parent changed from {old} to {new}_` marker in the log block.

### 3. Source the active-fork list

Prefer the cached cohort state when fresh:

```bash
COHORT_STATE="memory/topics/fork-cohort-state.json"
COHORT_AGE_DAYS=99
if [ -f "${COHORT_STATE}" ]; then
  COHORT_LAST_RUN=$(jq -r '.last_run // empty' "${COHORT_STATE}")
  if [ -n "${COHORT_LAST_RUN}" ]; then
    COHORT_AGE_DAYS=$(( ( $(date -u +%s) - $(date -u -d "${COHORT_LAST_RUN}" +%s) ) / 86400 ))
  fi
fi
```

| Condition | Source for ACTIVE list |
|-----------|------------------------|
| Cohort state exists AND `COHORT_AGE_DAYS <= 8` AND its `parent_repo` matches | Read `forks` object, filter `bucket ∈ {ACTIVE, POWER}` |
| Otherwise | Live fallback (step 3a) |

Eight days is one cohort cycle plus a one-day grace — within that window the cohort's ACTIVE list is still the ground truth and is cheaper than per-fork API calls.

#### 3a. Live fallback

```bash
gh api "repos/${PARENT_REPO}/forks" --paginate \
  --jq '[.[] | select(.archived != true and .disabled != true) | {full_name, owner: .owner.login, stargazers_count, created_at, pushed_at}]' \
  > /tmp/forks.json
```

Per fork (cap at 80 — same budget guard as fork-cohort):

```bash
LAST_RUN=$(gh api "repos/${FORK_FULL_NAME}/actions/runs?per_page=1" \
  --jq '.workflow_runs[0].updated_at // empty' 2>/dev/null)
```

A fork is ACTIVE if `LAST_RUN` is non-empty AND `(now - LAST_RUN) < 7 days`. 403 → retry once after 60s; persistent → skip the fork (log `unreadable`). 404 (Actions disabled) → treat as not-active. 5xx → retry once after 10s; persistent → skip.

If the live fallback itself fails to list forks after retry → exit `FORK_FIR