Skip to content
Back to skills

Github Repo Monitor

BSecurity

This skill should be used when the user asks to "monitor a GitHub repository", "watch GitHub for issues or PRs", "respond to @OpenHands mentions on GitHub", "set up an OpenHands GitHub integration", "trigger OpenHands from a GitHub comment", or "poll a GitHub repo for a trigger phrase". Guides the user through creating a cron automation that polls a single repository and starts an OpenHands conversation whenever a configurable trigger phrase is detected in an issue or PR comment.

  • 151 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 5, 2026
securitypythongoshellbashexpressgitapibackend

Works with

  • vscode
  • cli
  • api

Security analysis

B84/100
  • mediumUses curl or wget to download content
  • criticalDownloads and executes remote scripts — classic supply chain attack

Pro scans all 8 files and shows the line behind each finding

Scanned September 21, 2026

npx -y skills add OpenHands/extensions --skill github-repo-monitor --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Github Repo Monitor?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Github Repo Monitor
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/openhands-github-repo-monitor/badge)](https://www.skillsdirectory.com/skills/openhands-github-repo-monitor)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: github-repo-monitor
description: >
  This skill should be used when the user asks to "monitor a GitHub repository",
  "watch GitHub for issues or PRs", "respond to @OpenHands mentions on GitHub",
  "set up an OpenHands GitHub integration", "trigger OpenHands from a GitHub
  comment", or "poll a GitHub repo for a trigger phrase". Guides the user
  through creating a cron automation that polls a single repository and starts
  an OpenHands conversation whenever a configurable trigger phrase is detected
  in an issue or PR comment.
triggers:
  - /github-monitor:poll
---

# GitHub Repository Monitor

Create a cron automation that polls a single GitHub repository on a
configurable schedule (default: every minute).
Windows PowerShell equivalents for the setup, packaging, upload, and API-check shell snippets are in `references/windows.md`.

When a comment on an issue or PR contains the **trigger phrase**
(default: `@OpenHands`) it:

1. Posts a GitHub comment acknowledging the request with a conversation link.
2. Creates an OpenHands conversation pre-loaded with the issue/PR title, body,
   labels, and recent comment history for full context.
3. Posts a summary GitHub comment when the conversation finishes.

On every subsequent run:
- New trigger comments on an already-tracked issue/PR are forwarded to the
  running conversation (or re-open a previously closed one).
- When a conversation goes idle/finished/error the agent's final response
  is posted back as a GitHub comment.

> **Local mode only.** This automation targets the local OpenHands setup
> (`dev:automation` stack). A cloud/webhook variant is out of scope here.

---

The script imports shared GitHub transport from
`scripts/github_client.py`, installed with this skill. Include it beside
`main.py` when packaging manually, as shown below.

## Prerequisites

### Required secret

Verify that the following secret is set in **OpenHands Settings → Secrets**
before proceeding:

| Secret name | Token type | Minimum permissions |
|---|---|---|
| `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo` (private repos) or `public_repo` (public repos) |
| `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Issues: Read and Write |

Check with:
```bash
curl -s https://api.github.com/user \
  -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('login') or d.get('message'))"
```

If the token is missing, inform the user and stop — the automation cannot
function without GitHub credentials.

### Optional secret

| Secret name | Default | Purpose |
|---|---|---|
| `OPENHANDS_URL` | `http://localhost:8000` | Base URL used to build conversation links in GitHub comments |

---

## Setup Workflow

Follow these steps in order.

### Step 1  -  Verify GITHUB_PERSONAL_ACCESS_TOKEN

Fetch the secret and run the `curl` check above.

- If the secret is absent: tell the user
  *"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in OpenHands Settings → Secrets
  (classic PAT with `repo` or `public_repo` scope, or a fine-grained PAT
  with Issues: Read and Write)."* Then stop.

- If the API returns a non-200 or `{"message": "Bad credentials"}`:
  tell the user the token is invalid and ask them to update it.

### Step 2  -  Collect repository

Ask the user: *"Which GitHub repository should be monitored?
(Format: `owner/repo`, e.g. `microsoft/vscode`)"*

Validate access and write permissions:

```bash
curl -s "https://api.github.com/repos/{owner}/{repo}" \
  -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  | python3 -c "
import json, sys
d = json.load(sys.stdin)
if 'message' in d:
    print('ERROR:', d['message'])
else:
    perms = d.get('permissions', {})
    print(f\"Accessible. Private: {d.get('private')}. Permissions: {perms}\")
"
```

- If `message: Not Found` or `message: Bad credentials` →
  inform the user and ask them to check the repo name and token.
- If the repo is private and `permissions.push` is `false` →
  inform the user the token does not have write access and comments will fail.
- If the check passes, record `REPO = "{owner}/{repo}"`.

### Step 3  -  Collect trigger phrase

Ask the user: *"What trigger phrase should OpenHands respond to?
(Press Enter to use the default: `@OpenHands`)"*

Accepted values: any non-empty string unlikely to appear by accident.

Record as `TRIGGER_PHRASE`. Default: `"@openhands"`.

### Step 4  -  Collect allowed GitHub logins

Ask the user: *"Which GitHub users may trigger this automation?
Press Enter to allow only the authenticated `GITHUB_PERSONAL_ACCESS_TOKEN` owner.
You may also provide comma-separated GitHub logins, or `*` to allow any
non-bot commenter on the monitored repository."*

Map the answer to `ALLOWED_GITHUB_LOGINS`:

| User answer | `ALLOWED_GITHUB_LOGINS` value |
|---|---|
| Empty/default | `["<TOKEN_OWNER>"]` |
| `enyst,tofarr` | `["enyst", "tofarr"]` |
| `*` | `["*"]` |

Default to token-owner-only unless the user explicitly chooses a broader
allowlist. Record as `ALLOWED_GITHUB_LOGINS`.

### Step 5  -  Collect event types

Ask the user: *"Which event types should be monitored?
Choose one or more:*
  *1. Issue and PR comments (default)*
  *2. PR inline review comments*
  *3. Both*
*(Press Enter to accept the default: issue and PR comments.)"*

Map the choice to the `EVENT_TYPES` list:

| Choice | `EVENT_TYPES` value |
|---|---|
| 1 (default) | `["issue_comment"]` |
| 2 | `["pr_review_comment"]` |
| 3 | `["issue_comment", "pr_review_comment"]` |

### Step 6  -  Collect cron schedule

Ask the user: *"How often should the automation poll GitHub?
(Press Enter for the default: every minute.
Use a cron expression for a different interval, e.g.:
`*/5 * * * *` = every 5 minutes,
`0 * * * *` = every hour)"*

Default: `* * * * *` (every minute).

Record as `CRON_SCHEDULE`.

### Step 7  -  Generate the automation script

Read `scripts/main.py` from this skill's directory. Apply exactly five
constant substitutions near the top of the file:

| Placeholder | Replace with |
|---|---|
| `REPO = "owner/repo"` | `REPO = "{owner_repo}"` |
| `TRIGGER_PHRASE = "@openhands"` | `TRIGGER_PHRASE = "{trigger_phrase_lower}"` |
| `EVENT_TYPES = ["issue_comment"]` | `EVENT_TYPES = {event_types_list}` |
| `ALLOWED_GITHUB_LOGINS = ["<TOKEN_OWNER>"]` | `ALLOWED_GITHUB_LOGINS = {allowed_logins_list}` |
| `DEFAULT_OPENHANDS_URL = "http://localhost:8000"` | `DEFAULT_OPENHANDS_URL = "{url}"` (keep default if the user has no preference) |

Run these commands from this skill's directory and write the customised script
to a temporary build directory:
```bash
mkdir -p /tmp/github-monitor-build
cp -L scripts/github_client.py /tmp/github-monitor-build/github_client.py
# (write the customised main.py to /tmp/github-monitor-build/main.py)
```

Validate syntax before packaging:
```bash
python3 -m py_compile /tmp/github-monitor-build/main.py && echo "Syntax OK"
```

Fix any syntax errors before proceeding.

### Step 8  -  Package and upload

Determine the Automation backend URL and auth from the `<RUNTIME_SERVICES>`
block in your system context:
- Use the **Automation backend** `url_from_agent` as `OPENHANDS_HOST`
- Auth: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`

If no Automation backend is listed in `<RUNTIME_SERVICES>`, stop and tell
the user to start the full automation stack.

```bash
tar -czf /tmp/github-monitor.tar.gz -C /tmp/github-monitor-build .

# OPENHANDS_HOST: read from <RUNTIME_SERVICES> Automation backend url_from_agent
OPENHANDS_HOST="<automation-url-from-runtime-services>"

TARBALL_PATH=$(curl -s -X POST \
  "${OPENHANDS_HOST}/api/automation/v1/uploads?name=github-repo-monitor" \
  -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
  -H "Content-Type: application/gzip" \
  --data-binary @/tmp/github-monitor.tar.gz \
  | python3 -c "import json,sys; print(json.load(sys.stdin)['tarball_path'])")

echo "Uploaded: $TARBALL_PATH"
```

### Step 9  -  Create the automation

```bash
curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1" \
  -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"GitHub Monitor: {owner}/{repo}\",
    \"trigger\": {\"type\": \"cron\", \"schedule\": \"{cron_schedule}\"},
    \"tarball_path\": \"$TARBALL_PATH\",
    \"entrypoint\": \"python3 main.py\",
    \"timeout\": 55
  }" | python3 -m json.tool
```

Record the returned `id`.

### Step 10  -  Confirm

Tell the user:

> ✅ **GitHub Repository Monitor** is running!
>
> - Automation ID: `{id}`
> - Repository: `{owner}/{repo}`
> - Trigger phrase: `{phrase}`
> - Event types: `{event_types}`
> - Allowed GitHub logins: `{allowed_logins}`
> - Polling schedule: `{cron_schedule}`
> - State file: `~/.openhands/workspaces/automation-state/github_poller_{id}.json`
>
> From an allowed GitHub login, post a comment containing `{phrase}` on any
> issue or PR in `{owner}/{repo}` to test it. OpenHands will acknowledge with
> a comment and a link to the new conversation.

---

## Runtime Behaviour (per poll)

Each cron run executes `main.py`, which:

1. **Loads state** from the JSON file (see `references/state-schema.md`).
2. **Resolves and validates GITHUB_PERSONAL_ACCESS_TOKEN** — aborts immediately if absent or invalid.
3. **Polls for new events** since the previous `last_poll` timestamp:
   - `GET /repos/{owner}/{repo}/issues/comments?since=…` for `issue_comment`
   - `GET /repos/{owner}/{repo}/pulls/comments?since=…` for `pr_review_comment`
4. **Processes matching comments** in chronological order:
   - Skips bot accounts (login ending in `[bot]`) to avoid feedback loops.
   - Skips already-processed comment IDs.
   - Skips comments from logins outside `ALLOWED_GITHUB_LOGINS`.
   - Checks body for the trigger phrase (case-insensitive).
   - Extracts the issue/PR number from the comment URL.
5. **For each trigger comment**, per issue/PR:
   - **Active conversation** → forwards the new comment directly.
   - **Closed conversation** → tries to re-open it; falls back to creating
     a new conversation if the old one is unreachable.
   - **No conversation** → fetches full context (title, body, labels, last
     10 comments) and creates a new conversation with a detailed prompt.
   - Posts a GitHub comment: *"🤖 OpenHands is on it! View progress: {url}"*
6. **Checks active conversations** for completion:
   - If `status ∈ {idle, finished, error, stuck}` and enough time has passed
     since creation (debounce), fetches the agent's final response and posts
     it as a GitHub comment. Marks the conversation `closed`.
7. **Saves state** and fires the completion callback.

---

## Additional Resources

### Reference Files

- **`references/state-schema.md`**  -  State JSON schema, field definitions,
  and conversation lifecycle diagram.
- **`references/github-api.md`**  -  GitHub API endpoint reference, token
  scopes, rate limits, and common error codes.

### Script Template

- **`scripts/main.py`**  -  The complete automation script. Customise the four
  constants at the top (`REPO`, `TRIGGER_PHRASE`, `EVENT_TYPES`,
  `DEFAULT_OPENHANDS_URL`) before packaging.

---

## Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| Bot doesn't respond to comments | `GITHUB_PERSONAL_ACCESS_TOKEN` missing or wrong scopes | Verify token with `curl /user`; check scopes in Step 1 |
| "Bad credentials" in run logs | Token expired | Rotate token and update the secret in Settings |
| 404 on repo access | Repo name wrong or token has no access | Re-check `owner/repo` spelling; add token as collaborator |
| Comments posted but no conversation created | Agent server URL wrong | Check `OPENHANDS_URL` secret and `AGENT_SERVER_URL` env var |
| Same comment processed twice | `processed_comment_ids` cleared | State file was deleted; harmless but duplicate comment may appear |
| Summary never posted | Conversation stuck in `running` | Open the conversation in the OpenHands UI; agent may need input |
| No events detected after first run | `last_poll` in the future | Delete the state file to reset; it will be recreated on next run |

Files in this skill

  • .plugin/plugin.json778 B
  • README.md2.6 KB
  • SKILL.md11.4 KB
  • commands/github-monitor:poll.md656 B
  • references/github-api.md6.1 KB
  • references/state-schema.md5.4 KB
  • scripts/main.py35 KB
  • tests/test_main.py16.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.