Skip to content
Agent Orchestration
Skill

/github-issue-to-pr

Create an automation that implements GitHub issues when a configurable trigger label is applied. Polls one or more repositories deterministically, clones the default branch, starts one OpenHands conversation per label event, then commits, pushes, and opens the pull request

BOOST
From plugin
extensions
17185 skills4 commands
Install
$ npx -y skills add openhands/extensions --skill github-issue-to-pr --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/github-issue-to-pr

Context preview

The summary Claude sees to decide when to auto-load this skill.

Create an automation that implements GitHub issues when a configurable trigger label is applied. Polls one or more repositories deterministically, clones the default branch, starts one OpenHands conversation per label event, then commits, pushes, and opens the pull request

SKILL.md

github-issue-to-pr.SKILL.md
name: github-issue-to-pr
description: >
  Create an automation that implements GitHub issues when a configurable
  trigger label is applied. Polls one or more repositories deterministically,
  clones the default branch, starts one OpenHands conversation per label event,
  then commits, pushes, and opens the pull request itself.
triggers:
  - /issue-to-pr:setup

GitHub Issue to PR Automation

Agent Canvas catalog

For new Agent Canvas installations, use the **GitHub issue to PR** catalog entry. Its deterministic `worker.py` scanner delegates each eligible issue to a stable conversation using the selected agent profile. The manual upload flow below remains for existing deployments and is deprecated for new installations.

Create a cron automation that watches one or more GitHub repositories for issues with a trigger label, starts an OpenHands conversation once per label event with the repository's default branch already checked out, and opens a pull request with whatever the agent produced.

The automation script is deterministic: issue discovery, label-event tracking, state persistence, the clone, the branch, the commit, the push, the pull request, the issue comments, and the clone's removal are all handled in Python. The LLM is invoked only to write the code.

The agent is told **which** issue to implement, not what it says. It fetches the description, the discussion, and whatever they link to itself, so nothing in the prompt goes stale between dispatch and the moment the agent reads it.

That needs read access, so the conversation is handed exactly one secret, `GITHUB_PERSONAL_ACCESS_TOKEN`, and no MCP servers. `AGENT_SECRET_NAMES` stays an allow-list: the rest of the deployment's secret store is not reachable from a conversation whose instructions came from an issue.

The agent also finishes the job: it commits, pushes its branch, and opens the pull request, so the pull request appears when the agent stops rather than on the next poll. The script does not trust that it happened - when the conversation ends it asks GitHub whether the pull request exists, and opens it itself when it does not. `origin` still carries no credential, so every GitHub command the agent runs has to name `GITHUB_PERSONAL_ACCESS_TOKEN`; the SDK only puts a secret in the environment of a command that mentions it, and masks it in the output.

---

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; catalog bundles include it automatically.

Prerequisites

Required secret

Verify that the following secret is set in **OpenHands Settings -> Secrets**:

| Secret name | Token type | Minimum permissions | |---|---|---| | `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo`, plus `workflow` | | `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Contents: **Read and write**, Metadata: Read, Issues: **Read and write**, Pull requests: **Read and write**, Workflows: **Read and write** |

The workflow scope is not optional in practice. An issue asking for a CI change is a normal issue, and GitHub rejects the whole push when a token without it touches `.github/workflows/`: *"refusing to allow a Personal Access Token to create or update workflow ... without `workflow` scope"*. The branch is rejected in full, so the pull request never opens.

Contents write access is required because the script pushes the branch, and pull request write because it opens the pull request. A read-only token will poll happily and then fail at the point of pushing.

When several repositories are monitored, the token must cover all of them.

Check with:

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

If the token is missing or invalid, inform the user and stop.

---

Setup Workflow

Follow these steps in order.

Step 1 - Verify `GITHUB_PERSONAL_ACCESS_TOKEN`

Run the `curl` check above.

  • If absent: *"GITHUB_PERSONAL_ACCESS_TOKEN is not set. Please add it in

OpenHands Settings -> Secrets."* Stop.

  • If the API returns `{"message": "Bad credentials"}`: tell the user the token is

invalid and ask them to update it. Stop.

Step 2 - Collect repositories

Ask: *"Which GitHub repositories should be watched? (Format: `owner/repo`, e.g. `myorg/backend`. List several separated by commas to serve them all from one automation.)"*

Validate access to **each** repository, and confirm the token can push:

curl -s "https://api.github.com/repos/{owner}/{repo}" \
  -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
  | 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. Default branch: {d.get('default_branch')}. Push: {perms.get('push')}\")
"

Record every accepted repository into `REPOS = ["{owner}/{repo}", ...]`. If one repository fails the check, say which and ask whether to continue without it. If `Push: False`, say that the automation cannot open pull requests there and ask for a token with write access.

Each repository is polled independently and keeps its own state, so issue numbers never collide between them. The trigger label, branch prefix, and schedule are shared; a repository needing different settings wants its own automation.

Step 3 - Collect trigger label

Ask: *"Which issue label should trigger an implementation? (Press Enter for the default: `openhands`.)"*

Record the answer as `TRIGGER_LABEL`. If the label does not exist yet, tell the user that GitHub will still record the event once the label is created and applied to an issue.

The automation works an issue when it sees the latest matching `labeled` event for that label. To ask for another attempt later, remove and re-apply the label - th

Read more
Ships withextensions

This repository is the public extensions registry for OpenHands. It contains reusable, shareable skills, plugins, and Agent Canvas Apps that customize agent behavior and interfaces. Skills overview docs: SDK skill guide:

Get the whole plugin

Other skills on extensions.