inbox-triage · aeonfun — Claude Code skill: GitHub notification triage
Sorts your GitHub notifications and pulls out the ones that need action: vuln PR replies, security alerts, review requests and mentions.
npx skills add aeonfun/aeon --skill inbox-triage -a claude-codeWhat inbox-triage does
The skill pulls the notification feed with gh api /notifications --paginate and assigns every entry to one bucket: SECURITY, VULN_REPLY, REVIEW_NEEDED, MENTION or GENERAL. Only unread items from the last 14 days survive the filter. Each vuln PR reply then gets an age in days and a label of CRITICAL, AGING or FRESH.
Output lands in memory/topics/inbox-triage.md. Critical vuln replies are appended to the ## Known Follow-ups section of memory/MEMORY.md, and the run is logged in memory/logs/${today}.md. A notification goes out only when a security alert shows up, a vuln PR reply is critical, a review was requested, or three or more mentions piled up.
No extra token to configure: gh handles auth on its own through GITHUB_TOKEN, which GitHub Actions provides. The WebFetch fallback is a dead end for private data because it cannot send an authorization header. This sits next to vuln-tracker rather than replacing it — that one follows PR and disclosure state by branch name, this one covers the inbox. Hand-kept notes in MEMORY.md still matter.
What it helps with
- spot a maintainer reply on a vuln PR that has gone quiet
- catch a security advisory on a watched repo
- collect review requests into one list
- separate real mentions from subscription noise
- refresh the follow-up queue in
MEMORY.mdwithout opening GitHub
Example prompts for the agent
- Triage my GitHub notifications and show what needs action
- Run inbox-triage and update
memory/topics/inbox-triage.md - Check for critical vuln PR replies from the last 14 days
How to install inbox-triage
1Skills CLI (Vercel)
npx skills add aeonfun/aeon --skill inbox-triage -a claude-code2Manually: put the files in place
~/.claude/skills/inbox-triage/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/inbox-triage/
1Skills CLI (Vercel)
npx skills add aeonfun/aeon --skill inbox-triage -a codex2Manually: put the files in place
.agents/skills/inbox-triage/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add aeonfun/aeon --skill inbox-triage -a cursor2Manually: put the files in place
.cursor/skills/inbox-triage/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install aeonfun/aeon inbox-triage2Manually: put the files in place
.github/skills/inbox-triage/SKILL.md1Skills CLI (Vercel)
npx skills add aeonfun/aeon --skill inbox-triage -a gemini-cli2Platform CLI
gemini skills install https://github.com/aeonfun/aeon --path skills/inbox-triageManually: put the files in place
.devin/skills/inbox-triage/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add aeonfun/aeon --skill inbox-triage -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about inbox-triage
How do I install inbox-triage in Claude Code?
Run npx skills add aeonfun/aeon --skill inbox-triage -a claude-code. The skill lands at ~/.claude/skills/inbox-triage/SKILL.md.
What does inbox-triage do?
It reads the GitHub notification feed, keeps unread items from the last 14 days and sorts them into categories. Vuln PR replies get an age and an urgency label, then the skill writes a summary and sends a notification when the threshold is met.
How is inbox-triage different from vuln-tracker?
vuln-tracker follows PR and disclosure lifecycle through branch names and draft state. inbox-triage works on the notification layer: inbound replies, review requests, security alerts and mentions.
Source text
SKILL.md · MIT
---
name: inbox-triage
description: Daily GitHub notification inbox triage - surfaces aging vuln PR replies, security advisories, review requests, and mentions that need action
metadata:
title: Inbox Triage
category: dev
var: ""
tags:
- github
- security
- meta
---
Today is ${today}. Read `memory/MEMORY.md` before starting.
## Why this skill exists
Hand-tracked follow-ups live in MEMORY.md. `vuln-tracker` tracks the operator's vuln PRs by scanning branch names and ages the `memory/pending-disclosures/` queue. Neither reads from the actual GitHub notification inbox. When a maintainer replies to a vuln PR — or a security advisory opens on a watched repo — it sits unread until someone manually checks GitHub. This skill reads the inbox and routes what needs action.
`vuln-tracker` covers PR and disclosure lifecycle by branch and draft state. This skill covers the **notification layer** — inbound responses, review requests, security alerts, mentions.
## Steps
### 1. Fetch GitHub notifications
Run:
```bash
gh api /notifications --paginate 2>&1
```
Parse the JSON array. If the command errors or returns an empty array `[]`, log `INBOX_TRIAGE_SKIP: no notifications` and stop.
Limit to the first 100 notifications if `--paginate` returns more (GitHub caps at 50 per page; two pages is enough).
For each notification record:
- `id`
- `reason` — why you're being notified (mention, review_requested, author, state_change, security_alert, assign, etc.)
- `subject.title`
- `subject.type` — PullRequest, Issue, Release, etc.
- `subject.url` — API URL for the subject
- `repository.full_name`
- `updated_at` — ISO timestamp
### 2. Filter
Keep notifications where `unread: true` AND `updated_at` is within the last 14 days. Discard older or read ones.
If zero remain after filtering: log `INBOX_TRIAGE_SKIP: no actionable notifications within 14 days` and stop.
### 3. Categorize
Assign each notification to exactly one category (first match wins):
| Category | Match criteria |
|----------|---------------|
| `SECURITY` | `reason == "security_alert"` OR title contains any of: vulnerability, vuln, CVE, advisory, security |
| `VULN_REPLY` | `subject.type == "PullRequest"` AND `reason` is one of: author, state_change, comment AND `repository.full_name` is NOT under the operator's own account/org (derive the operator's GitHub handle from `soul/SOUL.md` or the workflow's `GITHUB_ACTOR` — these are PRs filed on third-party repos by the vuln-scanner) |
| `REVIEW_NEEDED` | `reason == "review_requested"` |
| `MENTION` | `reason == "mention"` OR `reason == "team_mention"` |
| `GENERAL` | everything else |
### 4. Age vuln PR replies
For each `VULN_REPLY` notification, compute `age_days` = today minus `updated_at` date (integer days).
Flag urgency:
- `CRITICAL` — age_days > 7 (maintainer likely hasn't responded)
- `AGING` — age_days 3–7
- `FRESH` — age_days < 3
Cross-reference with `memory/topics/vuln-followup.md` if it exists: look for the PR title in that file and pull any tracked notes (e.g. "approved", "NEEDS-ANSWER", merge status).
### 5. Resolve HTML URLs for action items
For each notification in SECURITY, VULN_REPLY (CRITICAL or AGING), REVIEW_NEEDED, and MENTION categories:
Try to get the HTML URL via:
```bash
gh api {subject.url} --jq '.html_url' 2>/dev/null
```
If that fails, construct the URL manually:
`https://github.com/{repository.full_name}/pulls/{number}` for PRs
`https://github.com/{repository.full_name}/issues/{number}` for issues
(Extract the number from the tail of `subject.url`.)
### 6. Write triage summary
Overwrite `memory/topics/inbox-triage.md`:
```markdown
# GitHub Inbox Triage
Last run: {today}
Scanned: {N} unread notifications ({N} within 14 days)
## Action Required
### Security ({count})
{for each SECURITY item, sorted by age:}
- **{repo}**: {title} ({age_days}d) — {html_url}
{if none:}
None.
### Vuln PR Replies ({count_critical} critical, {count_aging} aging)
{for each VULN_REPLY sorted by age desc:}
- **[{CRITICAL|AGING|FRESH}]** `{repo}` ({age_days}d): {title} — {html_url}
{if vuln-followup note found:} _{tracked note}_
{if none:}
None.
### Review Requested ({count})
{for each REVIEW_NEEDED item:}
- **{repo}**: {title} — {html_url}
{if none:}
None.
### Mentions ({count})
{for each MENTION item:}
- **{repo}**: {title} — {html_url}
{if none:}
None.
## No Action Needed
{count_general} general notifications (subscriptions, automated state changes).
```
### 7. Update MEMORY.md known follow-ups
Read `memory/MEMORY.md`. Find the `## Known Follow-ups` section.
**Add** any VULN_REPLY CRITICAL item not already tracked there — append:
```
- **{repo} #{number} NEEDS-ANSWER** — {age_days}d since maintainer activity ({url})
```
**Update** any existing NEEDS-ANSWER item for a PR that now appears as FRESH in VULN_REPLY (maintainer responded recently) — change its note to `RESPONDED — verify resolution`.
Do NOT add GENERAL, REVIEW_NEEDED, MENTION, or SECURITY items to MEMORY.md Known Follow-ups (too noisy; security items warrant a separate issue if severe).
### 8. Send notification
Only send if at least one of:
- Any SECURITY item
- Any VULN_REPLY where urgency == CRITICAL
- Any REVIEW_NEEDED item
- Three or more MENTION items
Write to `.pending-notify-temp/inbox-triage-${today}.md`:
```
inbox — {today}
{if SECURITY:}
security alert: {repo} — {title}
{if VULN_REPLY CRITICAL:}
vuln PRs aging: {comma-separated list of "repo (Nd)"}
{if REVIEW_NEEDED:}
review needed: {comma-separated repo list}
{if 3+ MENTION:}
{N} mentions
read it: memory/topics/inbox-triage.md
```
Then:
```bash
./notify -f .pending-notify-temp/inbox-triage-${today}.md
```
If nothing meets the threshold: skip notification. Log that no notification was sent.
### 9. Log
Append to `memory/logs/${today}.md`:
```markdown
### inbox-triage
- **Scanned:** {N} notifications
- **Security:** {N}
- **Vuln replies:** {N total} ({N_critical} critical, {N_aging} aging, {N_fresh} fresh)
- **Review needed:** {N}
- **Mentions:** {N}
- **MEMORY.md follow-ups updated:** {yes/no — what changed}
- **Notification sent:** {yes/no}
- INBOX_TRIAGE_OK
```
If skipped:
```markdown
### inbox-triage
- INBOX_TRIAGE_SKIP: {reason}
```
## Required Env Vars
None beyond `GITHUB_TOKEN`, which GitHub Actions sets automatically and `gh` uses internally.
## Network Note
Uses `gh api` for all GitHub calls — it handles auth internally, so no `$SECRET` ever appears on the command line for the Bash permission layer to refuse. `gh api` works in a GitHub Actions run. If `gh api /notifications` fails (rate limit, auth error), log the error and exit with `INBOX_TRIAGE_SKIP: api error`. Use WebFetch as a fallback only if `gh` is unavailable — the endpoint is `https://api.github.com/notifications` with `Authorization: Bearer $GITHUB_TOKEN`, but WebFetch can't carry that auth header; prefer `gh`.
## What this is NOT
- Not a replacement for hand-tracked follow-ups in MEMORY.md. This reads the raw GitHub inbox.
- Not a duplicate of `vuln-tracker` — vuln-tracker tracks lifecycle by branch name. This catches inbound maintainer replies via notifications.
- Not a duplicate of `vuln-tracker`'s disclosure-queue aging - that manages `memory/pending-disclosures/` advisory drafts. This reads GitHub security alerts and PR responses.
© aeonfun · MIT · aeonfun/aeon
Similar skills
All skills for Git and GitHub →inbox-triage by other authors comes first.