---
name: github-operations
description: GitHub pull request and repository operations using gh CLI for the quber repository. Use when creating PRs, listing PRs, getting PR status, viewing PR files, posting, listing or editing PR comments, creating or viewing releases, or performing git operations. Automatically detects repository from git remote.
allowed-tools: Bash, Read, Grep, Glob
---

# GitHub Operations Skill

This Skill provides GitHub pull request and repository management capabilities using the GitHub CLI (`gh`) tool.

## Prerequisites

- GitHub CLI (`gh`) must be installed and authenticated
- Repository: `xmandeng/quber` (auto-detected from git remote)
- Environment: `GITHUB_PERSONAL_ACCESS_TOKEN` must be set

## Available Operations

### 1. Create Pull Request

**Script**: `scripts/create-pr.sh`

**Usage**:
```bash
# File-backed body (recommended for any body longer than a few lines)
bash .claude/skills/github-operations/scripts/create-pr.sh \
  "title" \
  --body-file <path-to-markdown-file>

# Inline body (short text only)
bash .claude/skills/github-operations/scripts/create-pr.sh \
  "title" \
  --body "PR body content"
```

**Arguments**:
- `title`: PR title (follows QUE-XXX: format), always the first argument
- `--body <text>` OR `--body-file <path>`: PR description (markdown format).
  Required, mutually exclusive. Prefer `--body-file` — the file goes to gh
  untouched, so shell quoting cannot mangle the markdown.
- `--base <branch>`: target branch (optional, defaults to "main")

**Note**: Head branch is auto-detected from current git branch by gh pr create.
It is never passed as an argument, and the script rejects unknown or extra
arguments rather than ignoring them.

**Built-in guards** (shared with `edit-pr.sh`, implemented in `scripts/lib.sh`):
- The body must contain `## Summary`, `## Related Jira Issue`, and
  `## Test Evidence`, or the script refuses to submit. The full required
  structure is defined in `docs/GITHUB_WORKFLOW_SPEC.md`.
- After submission the PR is read back from GitHub and compared to what was
  submitted. The only tolerated difference is the Jira link definitions the
  Atlassian app appends after the body. Any other mismatch exits nonzero with
  instructions. Never report success when the script exits nonzero.

**Example**:
```bash
bash .claude/skills/github-operations/scripts/create-pr.sh \
  "QUE-123: Add CLI markdown converter" \
  --body-file /tmp/pr123-body.md
```

### 2. List Pull Requests

**Script**: `scripts/list-prs.sh`

**Usage**:
```bash
bash .claude/skills/github-operations/scripts/list-prs.sh [state] [base]
```

**Arguments**:
- `state`: PR state (open, closed, merged, all) - default: open
- `base`: Base branch filter - default: all branches

**Example**:
```bash
# List open PRs
bash .claude/skills/github-operations/scripts/list-prs.sh open main

# List all PRs
bash .claude/skills/github-operations/scripts/list-prs.sh all
```

### 3. Get Pull Request Details

**Script**: `scripts/get-pr.sh`

**Usage**:
```bash
bash .claude/skills/github-operations/scripts/get-pr.sh <pr-number>
```

**Arguments**:
- `pr-number`: Pull request number

**Example**:
```bash
bash .claude/skills/github-operations/scripts/get-pr.sh 45
```

### 4. Get Pull Request Files

**Script**: `scripts/get-pr-files.sh`

**Usage**:
```bash
bash .claude/skills/github-operations/scripts/get-pr-files.sh <pr-number>
```

**Arguments**:
- `pr-number`: Pull request number

**Example**:
```bash
bash .claude/skills/github-operations/scripts/get-pr-files.sh 45
```

### 5. Comment on Pull Request

**Script**: `scripts/pr-comment.sh`

**Usage**:
```bash
# Inline body (short text)
bash .claude/skills/github-operations/scripts/pr-comment.sh <pr-number> --body "<text>"

# File-backed body (recommended for multi-line markdown)
bash .claude/skills/github-operations/scripts/pr-comment.sh <pr-number> --body-file <path>
```

**Arguments**:
- `pr-number`: Pull request number
- `--body <text>` OR `--body-file <path>`: the comment content. Use
  `--body-file` for any body longer than a few lines — passes the file
  directly to gh, avoiding shell-quoting issues with multi-line markdown.

**Example** (file-backed):
```bash
bash .claude/skills/github-operations/scripts/pr-comment.sh 101 \
  --body-file /tmp/pr101-test-instructions.md
```

To find a comment that is already there, or to correct one that landed wrong,
see operations 11 to 13 below. A posted comment is not final.

### 6. Edit Pull Request (title and/or body)

**Script**: `scripts/edit-pr.sh`

**Usage**:
```bash
# Retitle only
bash .claude/skills/github-operations/scripts/edit-pr.sh <pr-number> --title "<new title>"

# Replace the body (file-backed, recommended for a full rewrite)
bash .claude/skills/github-operations/scripts/edit-pr.sh <pr-number> --body-file <path>

# Both at once
bash .claude/skills/github-operations/scripts/edit-pr.sh <pr-number> \
  --title "<new title>" --body-file <path>
```

**Arguments**:
- `pr-number`: Pull request number
- `--title <text>`: new PR title (optional)
- `--body <text>` OR `--body-file <path>`: new PR body; mutually exclusive. Use
  `--body-file` for a full rewrite — passes the file directly to gh, avoiding
  shell-quoting issues with multi-line markdown.
- At least one of `--title`, `--body`, or `--body-file` is required.

A replacement body passes through the same guards as `create-pr.sh`: the
required sections are checked before submission, and the PR is read back and
compared to what was submitted afterward (see "Built-in guards" under Create
Pull Request).

**Example** (retitle + full body rewrite):
```bash
bash .claude/skills/github-operations/scripts/edit-pr.sh 124 \
  --title "QUE-241: Adopt Camelot 2.0 and migrate the pipeline to pydantic-graph" \
  --body-file /tmp/pr124-body.md
```

### 7. List Workflow Runs

**Script**: `scripts/get-runs.sh`

**Usage**:
```bash
# Ten most recent runs across all workflows
bash .claude/skills/github-operations/scripts/get-runs.sh

# One workflow, by filename or display name
bash .claude/skills/github-operations/scripts/get-runs.sh jira-transition.yml

# Narrow further
bash .claude/skills/github-operations/scripts/get-runs.sh deploy.yml 20 failure
```

**Arguments**:
- `workflow` (optional): filename such as `jira-transition.yml`, or the display name
- `limit` (optional): number of runs, default 10
- `status` (optional): `success`, `failure`, `in_progress`, `cancelled`

Returns `databaseId`, which is the run ID `get-run-log.sh` takes.

### 8. Get Workflow Run Log

**Script**: `scripts/get-run-log.sh`

**Usage**:
```bash
# Whole log — long, prefer one of the narrowed forms below
bash .claude/skills/github-operations/scripts/get-run-log.sh <run-id>

# Only the steps that failed
bash .claude/skills/github-operations/scripts/get-run-log.sh <run-id> true

# Only lines matching a pattern, with 3 lines of context
bash .claude/skills/github-operations/scripts/get-run-log.sh <run-id> false "401"
```

**Arguments**:
- `run-id`: from the `databaseId` field of `get-runs.sh`
- `failed-only` (optional): `true` to show only failed steps, default `false`
- `grep-pattern` (optional): case-insensitive filter with 3 lines of context

GitHub masks secret values in logs as `***`, so a log names the steps that used
a secret without revealing it.

### 9. Create Release

**Script**: `scripts/create-release.sh`

Creates a GitHub release for a tag that already exists on the remote.

**Usage**:
```bash
# File-backed notes (recommended for any body longer than a few lines)
bash .claude/skills/github-operations/scripts/create-release.sh <tag> "<title>" \
  --notes-file <path-to-notes-file>

# Inline notes (short text only)
bash .claude/skills/github-operations/scripts/create-release.sh <tag> "<title>" \
  --notes "<release notes>"
```

**Arguments**:
- `tag`: an existing tag on the remote, such as `v0.3.0`
- `title`: release title
- `--notes <text>` OR `--notes-file <path>`: release notes (markdown).
  Required, mutually exclusive. Prefer `--notes-file` — the file goes to gh
  untouched, so shell quoting cannot mangle the markdown.

**Runs**: `gh release create <tag> --verify-tag --title <title> --notes|--notes-file ...`

**Behavior**:
- `--verify-tag` makes gh fail if the tag does not already exist on the
  remote. The script never creates or pushes a tag as a side effect; push the
  tag first with plain git.
- The release is published immediately. There is no draft or prerelease
  option.

**Example**:
```bash
bash .claude/skills/github-operations/scripts/create-release.sh v0.3.0 "v0.3.0" \
  --notes-file /tmp/release-notes-v0.3.0.md
```

### 10. View Release

**Script**: `scripts/view-release.sh`

Read-only. Prints the release attached to a tag, or fails when none exists.
Run it before `create-release.sh` to confirm the tag has no release yet, and
after to confirm what landed.

**Usage**:
```bash
bash .claude/skills/github-operations/scripts/view-release.sh <tag>
```

**Arguments**:
- `tag`: the release tag, such as `v0.3.0`

**Runs**: `gh release view <tag> --json tagName,name,url,isDraft,isPrerelease,publishedAt,body`

When the tag has no release, gh prints `release not found` on stderr and the
script exits 1.

**Example**:
```bash
bash .claude/skills/github-operations/scripts/view-release.sh v0.3.0
```

### 11. List Pull Request Comments

**Script**: `scripts/list-pr-comments.sh`

Read-only. Lists the conversation comments on a PR with the id of each. The id
is the point of the script: it is the only way to address a comment, and it is
the first step of the get, edit, put sequence below.

**Usage**:
```bash
# One line per comment, body truncated to a preview
bash .claude/skills/github-operations/scripts/list-pr-comments.sh <pr-number>

# Every body printed whole
bash .claude/skills/github-operations/scripts/list-pr-comments.sh <pr-number> --full
```

**Arguments**:
- `pr-number`: Pull request number
- `--full` (optional): print each comment body in full instead of a preview

**Runs**: `gh api --paginate repos/{owner}/{repo}/issues/<pr-number>/comments`

**Behavior**:
- Prints id, author login, creation timestamp and a one-line preview per comment.
- `--full` prints the bodies from the same response, for reading a whole thread
  at once. To correct a comment, start from `get-pr-comment.sh` instead: it
  prints one body raw, which is what `--body-file` takes back.
- A PR with no comments prints `PR #N has no comments.` and exits 0.
- Diff-line review comments are a different endpoint and are not listed.

**Example**:
```bash
bash .claude/skills/github-operations/scripts/list-pr-comments.sh 212
```
```
PR #212 has 2 comment(s).

ID            AUTHOR              CREATED               PREVIEW
5652355571    xmandeng            2026-09-13T09:04:42Z  # Evidence completing AC2 and AC4 The PR body marks ...
5652360015    xmandeng            2026-09-13T09:05:55Z  # Correction to the AC4 evidence above, plus two add...
```

### 12. Get Pull Request Comment

**Script**: `scripts/get-pr-comment.sh`

Read-only. Prints one comment's body to stdout, raw, with nothing added around
it, so it can be redirected straight to a file and fed back to `--body-file`.

`edit-pr-comment.sh` replaces a body whole, so a caller must hold the current
body before editing or the edit destroys whatever it did not retype. The
listing's preview is truncated and raw `gh` is denied, so without this script
the only comment you could safely edit is one you wrote yourself in the same
session.

**Usage**:
```bash
bash .claude/skills/github-operations/scripts/get-pr-comment.sh <comment-id>
bash .claude/skills/github-operations/scripts/get-pr-comment.sh <comment-id> > /tmp/comment.md
```

**Arguments**:
- `comment-id`: numeric comment id from `list-pr-comments.sh`, not a PR number

**Runs**: `gh api repos/{owner}/{repo}/issues/comments/<comment-id> --jq .body`

**Behavior**:
- Prints the body and nothing else. No header, no id, no framing.
- An id that does not exist, or belongs to another repository, exits 1 with gh's
  404 and writes nothing to stdout, so a redirect cannot leave an error document
  in the file you are about to submit.

**Example**:
```bash
bash .claude/skills/github-operations/scripts/get-pr-comment.sh 5652355571 | head -3
```
```
# Evidence completing AC2 and AC4

The PR body marks AC2 and AC4 as partially evidenced. This comment supplies the missing parts. Every row was read from the working tree on 13 September 2026.
```

### 13. Edit Pull Request Comment

**Script**: `scripts/edit-pr-comment.sh`

Corrects a comment in place, addressed by the id from operation 11. Before this
existed a posted comment was final: on PR #212 a comment carried a wrong figure
and had to be answered with a second comment, leaving the wrong number standing
above the correction.

**Usage**:
```bash
# Inline body (short text)
bash .claude/skills/github-operations/scripts/edit-pr-comment.sh <comment-id> --body "<text>"

# File-backed body (recommended for multi-line markdown)
bash .claude/skills/github-operations/scripts/edit-pr-comment.sh <comment-id> --body-file <path>
```

**Arguments**:
- `comment-id`: numeric comment id from `list-pr-comments.sh`, not a PR number
- `--body <text>` OR `--body-file <path>`: the replacement body; required,
  mutually exclusive. Prefer `--body-file` — the file is sent as JSON, so shell
  quoting cannot mangle the markdown.

**Runs**: `gh api --method PATCH repos/{owner}/{repo}/issues/comments/<comment-id>`

**Behavior**:
- The body replaces the comment whole. There is no append mode; start from
  `get-pr-comment.sh <comment-id>` to keep the text you are not changing.
- After the write the comment is read back from GitHub and compared to what was
  submitted, by `verify_pr_comment_content` in `scripts/lib.sh`. The landed body
  must begin with the submitted body byte for byte; the only tolerated
  difference is the Jira link definitions the Atlassian app appends. Any other
  mismatch exits nonzero with instructions. Never report success when the script
  exits nonzero.
- Unlike a PR body, a comment body has no required sections, so nothing is
  checked before submission.

**Example**:
```bash
bash .claude/skills/github-operations/scripts/get-pr-comment.sh 5652360015 > /tmp/comment.md
# change /tmp/comment.md
bash .claude/skills/github-operations/scripts/edit-pr-comment.sh 5652360015 \
  --body-file /tmp/comment.md
```
```
https://github.com/xmandeng/quber/pull/212#issuecomment-5652360015
Read-back verified: comment 5652360015 on GitHub matches the submitted body.
```

### Correcting a comment: get, edit, put

The three comment operations are one sequence, and the edit is only safe as its
last step. Find the id with `list-pr-comments.sh <pr-number>`. Write the current
body to a file with `get-pr-comment.sh <comment-id> > /tmp/comment.md`. Change
that file, leaving everything you are not correcting untouched. Submit it with
`edit-pr-comment.sh <comment-id> --body-file /tmp/comment.md`, which reads the
comment back and fails if what landed is not what was sent.

Reaching for the edit script alone means retyping a body from memory or from a
rendered page, and a body replaces the comment whole, so anything not retyped is
destroyed and any figure retyped wrong is published as fact. Edit the file the
get step produced.

## Extending This Skill

When an operation you need is not wrapped here, add a script rather than
working around its absence. The `github-workflow` agent owns these scripts and
is the only one that writes them. The main agent does not author GitHub
scripts; if it finds an operation missing, it delegates the work of adding it
along with the work of running it.

Adding a script grants a capability that `permissions.deny` in
`.claude/settings.json` would otherwise block, because the deny matches on the
`gh` command prefix and these are invoked through `bash`. That is the intended
route, established when the deny and this Skill were introduced together, so
treat a new script as a deliberate widening of what agents may do:

- **Surface every new script before writing it.** This includes read-only ones.
  Say which operation is missing, what the wrapper would run, and why the
  existing scripts do not cover it, then stop and wait for an answer. Never add
  one on your own judgment mid-task.
- Anything that writes to GitHub, and anything destructive in particular
  (deleting labels, closing or merging PRs, changing secrets, dispatching
  workflows), needs agreement to that specific operation, not a general nod.
- If the answer is no, report what you could not determine and why, rather than
  reaching for an unwrapped command or inferring the result some other way.
- Follow the shape of the existing scripts: `set -e`, argument validation with
  a usage line on stderr, the `command -v gh` check, and `jq '.'` on JSON
  output.
- `chmod +x` the file, run it once to confirm it works, and document it above
  with usage, arguments, and an example.

## PR Conventions for quber

**IMPORTANT**: All PR standards are defined in `docs/GITHUB_WORKFLOW_SPEC.md`. This Skill follows those specifications.

### Quick Reference

**Title Format**: `QUE-XXX: Brief description of changes`
- Example: `QUE-149: Refactor github-workflow agent to reference GITHUB_WORKFLOW_SPEC.md`

**Branch Naming**: `<type>/QUE-XXX-brief-description`
- Example: `feature/QUE-149-refactor-github-agent-specs`
- Example: `bugfix/QUE-131-fix-jira-exit-error`

**PR Body**: Must include Summary, Related Jira Issue, Acceptance Criteria with Functional Evidence, Test Evidence, Changes Made

See `docs/GITHUB_WORKFLOW_SPEC.md` for complete PR standards, functional evidence requirements, and quality gates.

## Error Handling

All scripts return:
- **Exit code 0**: Success
- **Exit code 1**: Error (check stderr for details)

## Integration with Agents

This Skill is designed for use by the `github-workflow` agent. When the agent needs to perform GitHub operations:

1. Agent determines operation needed
2. Agent calls appropriate script via Bash tool
3. Script executes using `gh` CLI
4. Results returned to agent
5. Agent formats response for user

## Repository Auto-Detection

The GitHub CLI (`gh`) automatically detects the repository from `git config --get remote.origin.url`. Scripts do not need to specify `owner` and `repo` parameters.

That detection is what makes the current directory matter: run from outside a
checkout, gh cannot tell which repository is meant and the call fails. The
comment scripts, `list-prs.sh`, `get-runs.sh` and `get-run-log.sh` call
`require_repo_context` from `scripts/lib.sh` and stop with a message naming the
cause. Run every script from inside the repository or one of its worktrees.

To verify repository:
```bash
git remote -v
```

Should show: `origin  git@github.com:xmandeng/quber.git`
