Skip to content
Closed
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Official skills and plugins for OpenHands — the open-source AI software engine
| flarglebargle | skill | A test skill that responds to the magic word 'flarglebargle' with a compliment. Use for testing skill activation and ... | — |
| frontend-design | skill | Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks ... | — |
| github | skill | Interact with GitHub repositories, pull requests, issues, and workflows using the GITHUB_TOKEN environment variable a... | — |
| github-event-poller | skill | Create an OpenHands automation that polls a GitHub repository on a cron schedule for new events (issues, PRs, release... | — |
| github-pr-review | skill | Post structured PR reviews to GitHub with inline comments/suggestions in a single API call. | `/github-pr-review` |
| gitlab | skill | Interact with GitLab repositories, merge requests, and APIs using the GITLAB_TOKEN environment variable. Use when wor... | — |
| iterate | skill | Iterate on a GitHub pull request — drive it through CI, code review, and QA until merge-ready. Monitors state, fixes ... | `/iterate`, `/verify`, `/babysit` |
Expand Down
14 changes: 14 additions & 0 deletions marketplaces/openhands-extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,20 @@
"pull-request",
"iterate"
]
},
{
"name": "github-event-poller",
"source": "./skills/github-event-poller",
"description": "Create an OpenHands automation that polls a GitHub repository on a cron schedule for new events (issues, PRs, releases, comments, push, etc.) and runs user-defined handler instructions. Prefers a configured GitHub MCP server in the sandbox, falls back to a GITHUB_TOKEN environment variable.",
"category": "integration",
"keywords": [
"github",
"automation",
"cron",
"polling",
"events",
"monitoring"
]
}
]
}
160 changes: 160 additions & 0 deletions skills/github-event-poller/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
name: github-event-poller
description: This skill should be used when the user wants to create an OpenHands automation that polls a GitHub repository for new events on a schedule (e.g. "watch a repo for new issues / PRs / releases / comments", "poll GitHub for events", "set up a recurring GitHub activity check"). It scaffolds and registers a cron-triggered OpenHands automation that reads the GitHub Events / REST API (preferring a GitHub MCP server if configured, falling back to a `GITHUB_TOKEN` env var) and runs user-defined handler instructions against each new event.
triggers:
- poll github
- poll a github repo
- poll repo events
- poll repository events
- github event poller
- github events automation
- github activity automation
- watch github repo
- watch a github repository
- monitor github repo
- monitor a github repository
- github polling
- schedule github poll
- cron github
- github cron job
---

# GitHub Event Poller

Creates a **cron-triggered OpenHands automation** that periodically polls a GitHub repository for new events and runs user-defined handler instructions on them.

This is different from the built-in GitHub *webhook* trigger:

| Capability | Webhook trigger (`source: github`) | This skill (cron + REST polling) |
| ---------------------- | ----------------------------------------------- | ------------------------------------------- |
| Needs webhook access | Yes (must own the repo / org or App install) | **No** — any repo readable by your token |
| Latency | Real-time | As frequent as your cron (e.g. every 5 min) |
| Public-repo monitoring | Hard (you don't own webhook config) | **Easy** — just read public events |
| State / deduplication | Handled by webhook delivery | Lookback window (managed by this skill) |

Use this skill when the user **doesn't own** the target repo's webhook config, or when they explicitly want a polling model.

## Prerequisites — confirm before creating

Before calling the API, verify each of these. If anything is missing, ask the user.

1. **Target repo** — `owner/repo` (e.g. `OpenHands/OpenHands`).
2. **Event types of interest** — any combination of:
`issues`, `pull_request`, `issue_comment`, `pull_request_review`, `pull_request_review_comment`, `push`, `release`, `create`, `delete`, `fork`, `watch`, `member`, `public`.
GitHub's `/repos/{owner}/{repo}/events` returns these via the unified Events API.
3. **Cron schedule** — default `*/15 * * * *` (every 15 minutes). Don't go below 5 minutes against the GitHub API.
4. **Handler instructions** — natural-language description of what to do for each new event. Should be parameterizable by event type if needed.
5. **Auth strategy for the automation sandbox**:
- **Preferred:** a GitHub MCP server is configured in the user's OpenHands Cloud account. Check by asking the user (or with the API listed below). The automation's runtime prompt instructs the agent to *prefer MCP tools* when present.
- **Fallback:** the automation sandbox has a `GITHUB_TOKEN` secret. The runtime prompt falls back to `curl https://api.github.com/...` with that token.
- At least one of these **must** be available, or the automation will be unable to read GitHub. If neither is present, tell the user and stop — do not create the automation.

### Checking for a configured GitHub MCP server

Ask the user, or inspect their account settings via the OpenHands API (host comes from `<HOST>` if provided, else `https://app.all-hands.dev`):

```bash
curl -s "${OPENHANDS_HOST}/api/conversations/settings" \
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
| python3 -c 'import sys,json; d=json.load(sys.stdin); print(json.dumps(d.get("mcp_config",{}),indent=2))'
```

Look for an entry whose name or `command`/`url` references `github`, `mcp-github`, or `github-mcp-server`. If none, fall back to `GITHUB_TOKEN`.

### Checking that GITHUB_TOKEN is available

```bash
curl -s "${OPENHANDS_HOST}/api/automation/v1" \
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" | head -c 200 >/dev/null # smoke-test API key
```

`GITHUB_TOKEN` is an OpenHands Cloud secret and is injected automatically into automation sandboxes when registered. If the user isn't sure, have them add it under **Settings → Secrets**.

## How to create the automation

Always use the **prompt preset** (`POST /api/automation/v1/preset/prompt`). Never write a custom SDK script for this skill.

The body has three parameterised pieces:

1. **Cron trigger** — `trigger.schedule`, `trigger.timezone`.
2. **Repos** — clone the target repo into the sandbox so its skills (`AGENTS.md`, etc.) are auto-loaded and the agent can run repo-aware actions.
3. **Prompt** — built from the template in `references/runtime-prompt-template.md`. Substitute these placeholders:
- `{{OWNER_REPO}}` — e.g. `OpenHands/OpenHands`
- `{{EVENT_TYPES}}` — JSON array of event types
- `{{LOOKBACK_MINUTES}}` — typically `cron_interval_minutes * 2` (safety overlap)
- `{{HANDLER_INSTRUCTIONS}}` — user-supplied instructions block
- `{{MAX_EVENTS}}` — cap (default `50`) so runs stay bounded

### One-shot create (recommended)

The bundled script `scripts/create_poller.sh` does the substitution and POSTs in one step:

```bash
.agents/skills/github-event-poller/scripts/create_poller.sh \
--repo OpenHands/OpenHands \
--events "issues,pull_request,release" \
--schedule "*/15 * * * *" \
--name "OpenHands repo activity watcher" \
--handler "For each new issue or PR, summarise it in 1 sentence and print it. For each release, print version + URL." \
--host "${OPENHANDS_HOST:-https://app.all-hands.dev}"
```

Required env: `OPENHANDS_API_KEY` (or the local `$OPENHANDS_AUTOMATION_API_KEY` for the in-sandbox automation service — see "Local dev stack" below).

The script prints the new automation's `id` on success.

### Manual create (when the script can't be used)

```bash
PROMPT=$(sed \
-e 's|{{OWNER_REPO}}|OpenHands/OpenHands|g' \
-e 's|{{EVENT_TYPES}}|["issues","pull_request","release"]|g' \
-e 's|{{LOOKBACK_MINUTES}}|30|g' \
-e 's|{{MAX_EVENTS}}|50|g' \
-e 's|{{HANDLER_INSTRUCTIONS}}|For each new issue or PR, summarise it in one sentence.|g' \
.agents/skills/github-event-poller/references/runtime-prompt-template.md)

jq -n --arg name "OpenHands repo activity watcher" --arg prompt "$PROMPT" '{
name: $name,
prompt: $prompt,
trigger: {type:"cron", schedule:"*/15 * * * *", timezone:"UTC"},
timeout: 600,
repos: [{url: "https://github.com/OpenHands/OpenHands", ref: "main"}]
}' | curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/preset/prompt" \
-H "Authorization: Bearer ${OPENHANDS_API_KEY}" \
-H "Content-Type: application/json" \
--data @-
```

## After creating

1. **Verify** the automation appears in `GET /api/automation/v1`.
2. **Dispatch once** to confirm it runs end-to-end:
`POST /api/automation/v1/{id}/dispatch`.
3. **Inspect a run** with `GET /api/automation/v1/{id}/runs?limit=5` and look for `status: COMPLETED`.
4. Show the user the automation `id`, the chosen schedule, and a link to the run in the UI.

## Local dev stack (this sandbox)

If `$OPENHANDS_AUTOMATION_API_KEY` is set, the automation backend is running locally at
`http://host.docker.internal:18001`. Use:

- `Host`: `http://host.docker.internal:18001`
- `Auth header`: `Authorization: Bearer $OPENHANDS_AUTOMATION_API_KEY`
(the `/v1` routes on the local stack do **not** accept `X-API-Key` — only Bearer)

The OpenAPI spec is at `${HOST}/api/automation/openapi.json`. This is useful for testing the skill end-to-end without touching production OpenHands Cloud.

## Files in this skill

- `SKILL.md` — this file. The decision flow / API recipe.
- `references/runtime-prompt-template.md` — the prompt that the **automation's sandbox agent** runs every cron tick. Contains the MCP-vs-token decision logic and the polling/dedup loop.
- `scripts/create_poller.sh` — one-shot wrapper that substitutes the template, builds the JSON body and POSTs it to the preset endpoint.

## Tips & gotchas

- **Rate limits.** Unauthenticated GitHub API = 60 req/h. Authenticated = 5,000 req/h. Always run through MCP or `GITHUB_TOKEN`.
- **Events API window.** `/repos/{owner}/{repo}/events` returns up to ~300 events over ~90 days and is cached for ~60 s. Schedules tighter than 1 min are wasteful.
- **Deduplication.** Polling can re-see the same event when runs overlap. The runtime prompt filters events to `created_at >= now - LOOKBACK_MINUTES`; if you need stronger dedup, instruct the handler to check whether it has already acted (e.g. "before commenting on a PR, search for an existing comment authored by you").
- **Push events** can be huge. If watching `push`, cap the handler with `MAX_EVENTS` and consider filtering to a specific branch in the handler instructions.
- **Don't include secrets in the prompt.** The runtime prompt references `$GITHUB_TOKEN`, which is resolved inside the sandbox — never inline the token value into the automation prompt or JSON body.
7 changes: 7 additions & 0 deletions skills/github-event-poller/commands/github-poller:create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
description: Create an OpenHands automation that polls a GitHub repository on a cron schedule for new events (issues, PRs, releases, comments, push, etc.) and runs user-defined handler instructions on each event. Prefers a GitHub MCP server in the sandbox, falls back to GITHUB_TOKEN.
---

Read and follow the complete instructions in the SKILL.md file located in this skill's directory.

$ARGUMENTS
110 changes: 110 additions & 0 deletions skills/github-event-poller/references/runtime-prompt-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
You are running as a scheduled OpenHands automation that polls a single GitHub
repository for new events and acts on them.

## Configuration (filled in at automation-creation time)

- Target repository: `{{OWNER_REPO}}`
- Event types of interest: `{{EVENT_TYPES}}` (subset of:
issues, pull_request, issue_comment, pull_request_review,
pull_request_review_comment, push, release, create, delete, fork, watch,
member, public)
- Lookback window: `{{LOOKBACK_MINUTES}}` minutes (events older than this are ignored)
- Max events to process this run: `{{MAX_EVENTS}}`

## Step 1 — Choose the GitHub access path

Decide ONCE at the start of the run, then use the same path for every call:

1. Inspect the tools available in this conversation. If you have any tool whose
name begins with `mcp__github` (e.g. `mcp__github__list_issues`,
`mcp__github__list_pulls`, `mcp__github__list_repository_events`), prefer
those — they handle auth, pagination and rate limits for you.

2. Otherwise, check that the environment variable `GITHUB_TOKEN` is set:

test -n "$GITHUB_TOKEN" && echo "GITHUB_TOKEN available" || echo "missing"

If it IS set, use it via curl:

curl -sS \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/{{OWNER_REPO}}/events?per_page=100"

3. If neither is available, STOP the run with a clear error message
("No GitHub MCP server and no GITHUB_TOKEN — cannot poll {{OWNER_REPO}}.")
and do not attempt further work.

Record at the top of your output which path you chose.

## Step 2 — Fetch recent events

Fetch the repo's public event feed:

- MCP path: call the events / activity tool on `{{OWNER_REPO}}`, requesting
the most recent page (per_page=100).
- Token path: curl `GET /repos/{{OWNER_REPO}}/events?per_page=100` as above.

The response is a JSON array of event objects, each with at least:
`id`, `type`, `created_at`, `actor.login`, `repo.name`, `payload`.

## Step 3 — Filter to new + relevant events

Apply these filters, in order:

1. Drop events whose `type` is not in `{{EVENT_TYPES}}` (the GitHub event
type names, e.g. `IssuesEvent`, `PullRequestEvent`, `PushEvent`,
`ReleaseEvent`, `IssueCommentEvent`, `PullRequestReviewEvent`,
`PullRequestReviewCommentEvent`).
Mapping of webhook-style names → REST event-feed names:
issues → IssuesEvent
pull_request → PullRequestEvent
issue_comment → IssueCommentEvent
pull_request_review → PullRequestReviewEvent
pull_request_review_comment → PullRequestReviewCommentEvent
push → PushEvent
release → ReleaseEvent
create → CreateEvent
delete → DeleteEvent
fork → ForkEvent
watch → WatchEvent
member → MemberEvent
public → PublicEvent

2. Drop events older than `now() - {{LOOKBACK_MINUTES}} minutes` based on
`created_at` (UTC ISO-8601).

3. Sort ascending by `created_at` so you process oldest-first.

4. Truncate to the first `{{MAX_EVENTS}}` events.

If zero events survive filtering, log "No new events." and exit 0.

## Step 4 — Per-event handler

For each surviving event, follow the handler instructions below. Print one
clearly-delimited block per event with:

- the event `id`, `type`, `actor.login`, `created_at`
- a short summary derived from `payload`
- any actions taken / output produced

### Handler instructions (user-supplied)

{{HANDLER_INSTRUCTIONS}}

If the handler instructions ask you to comment on or modify GitHub resources,
use the same access path you chose in Step 1 (MCP write tool, or `curl -X
POST/PATCH …` with `$GITHUB_TOKEN`). Before posting any comment, search for an
existing comment by the same automation actor on the same issue/PR and skip if
one already exists (best-effort dedup).

## Step 5 — Wrap up

Print a final one-line summary:

PROCESSED {{OWNER_REPO}} events_seen=<N> events_processed=<M> path=<mcp|token>

That single line lets the user grep through run logs to confirm the poller is
healthy.
Loading
Loading