PR CLI Reference
myk-claude-tools pr is the PR-focused part of the CLI used by this repository's review workflows. It gives you three building blocks:
pr difffetches pull request metadata, the full diff, and per-file patch datapr claude-mdloads the target repository'sCLAUDE.mdinstructionspr post-commentposts one structured GitHub review with inline comments
Before You Start
The CLI is exposed as the myk-claude-tools console script:
[project.scripts]
myk-claude-tools = "myk_claude_tools.cli:main"
The repository's own PR workflow checks the toolchain like this:
uv --version
myk-claude-tools --version
If the CLI is not installed, the documented install command is:
uv tool install myk-claude-tools
You also need:
- Python 3.10 or newer
- GitHub CLI (
gh) - Access to the GitHub repository you want to review
Note:
pr diffand remotepr claude-mdcallghdirectly.pr post-commentalso depends ongh, but it does not do its own friendly preflight check first, so install and verifyghbefore using it.
Command Overview
| Command | What it returns | Best used for |
|---|---|---|
myk-claude-tools pr diff |
JSON | Fetching everything you need to review a PR |
myk-claude-tools pr claude-md |
Plain text | Loading repository-specific instructions before reviewing |
myk-claude-tools pr post-comment |
JSON | Posting a single review with multiple inline findings |
A typical workflow looks like this:
myk-claude-tools pr diff $ARGUMENTS
myk-claude-tools pr claude-md $ARGUMENTS
mkdir -p /tmp/claude
myk-claude-tools pr post-comment {owner}/{repo} {pr_number} {head_sha} /tmp/claude/pr-review-comments.json
Tip: Save
metadata.head_shafrompr diffand reuse it as thecommit_shaforpr post-comment. That is the safest way to keep inline comment locations aligned with the exact revision you reviewed.
Shared PR Target Formats
pr diff and pr claude-md share the same input rules. They accept any of these forms:
myk-claude-tools pr diff <owner/repo> <pr_number>
myk-claude-tools pr diff https://github.com/owner/repo/pull/123
myk-claude-tools pr diff <pr_number>
myk-claude-tools pr claude-md <owner/repo> <pr_number>
myk-claude-tools pr claude-md https://github.com/owner/repo/pull/123
myk-claude-tools pr claude-md <pr_number>
When you pass only a PR number, the CLI resolves the repository from the current working directory with:
gh repo view --json owner,name -q '.owner.login + "/" + .name'
A few practical details matter here:
owner/repomust match the normal GitHub formatpr_numbermust be numeric- PR URLs can include an optional protocol and optional trailing path segments
- Wrong argument counts print usage and exit with status
1
Warning: Number-only mode depends on the current repository context. If you are outside the target repository, working across multiple repositories, or dealing with forks, prefer
owner/repo + pr_numberor a full PR URL.
pr diff
What it does
pr diff collects three kinds of data for the target PR:
- PR metadata from
GET /repos/{owner}/{repo}/pulls/{pr_number} - The full unified diff from
gh pr diff - The changed file list from
GET /repos/{owner}/{repo}/pulls/{pr_number}/files
The file list request is paginated, so large PRs are handled across multiple API pages.
Usage
myk-claude-tools pr diff <owner/repo> <pr_number>
myk-claude-tools pr diff https://github.com/owner/repo/pull/123
myk-claude-tools pr diff <pr_number>
Output
pr diff always writes JSON to stdout. Its top-level structure is built like this:
{
"metadata": {
"owner": pr_info.owner,
"repo": pr_info.repo,
"pr_number": pr_info.pr_number,
"head_sha": head_sha,
"base_ref": base_ref,
"title": pr_title,
"state": pr_state
},
"diff": pr_diff,
"files": files
}
Each file entry is normalized to this shape:
{
"path": f["filename"],
"status": f["status"],
"additions": f["additions"],
"deletions": f["deletions"],
"patch": f.get("patch", "")
}
What those fields are most useful for:
metadata.head_shais the commit SHA you should pass topr post-commentmetadata.base_reftells you which branch the PR targetsdiffcontains the full text diff from GitHub CLIfilesgives you a per-file summary plus patch text when GitHub includes it
Note:
files[].patchfalls back to an empty string when GitHub does not include patch text, which can happen for some large or non-text changes.
Failure behavior
pr diff exits with status 1 when it cannot produce a complete result. Important cases include:
ghis not installed- the GitHub API call fails
gh pr difffails- the file list request fails
- the PR metadata does not include
head.sha - the PR metadata does not include
base.ref
Timeouts are also explicit:
- PR metadata fetch: 60 seconds
- Diff fetch: 120 seconds
- File list fetch: 120 seconds
Tip: If you are scripting around
pr diff, treat stdout as machine-readable JSON and stderr as diagnostics.
pr claude-md
What it does
pr claude-md resolves the same PR target formats as pr diff, but its output is plain text instead of JSON. The command tries to find the repository's instructions file in a specific order and stops on the first match.
Usage
myk-claude-tools pr claude-md <owner/repo> <pr_number>
myk-claude-tools pr claude-md https://github.com/owner/repo/pull/123
myk-claude-tools pr claude-md <pr_number>
Lookup order
The command checks these locations in order:
- Local
./CLAUDE.md, but only if the current repo matches the target repo - Local
./.claude/CLAUDE.md, but only if the current repo matches the target repo - Remote
CLAUDE.mdfrom the GitHub Contents API - Remote
.claude/CLAUDE.mdfrom the GitHub Contents API - If nothing is found, it prints an empty line and exits successfully
Local repository matching is based on git remote get-url origin, and it supports both GitHub HTTPS and SSH remote formats.
Note: Local files win over GitHub when the current repository matches the target repository. That makes local testing fast and lets you review unpublished
CLAUDE.mdedits before they exist upstream.Tip: The local match is based on
origin. If your local checkout pointsoriginat a fork instead of the target repository, the command usually falls back to GitHub and fetches the upstream file instead.
Output behavior
pr claude-md prints one of two things:
- The raw contents of the matched
CLAUDE.md - An empty line if no supported file exists
That empty output is intentional.
Warning: A missing
CLAUDE.mdis not treated as an error. If your automation needs to distinguish "file not found" from "file exists but is empty," you need to handle that outside this command.
Failure behavior
You will get a non-zero exit when:
- argument parsing fails
- the current directory cannot resolve the repo and you only passed a PR number
- local matching does not apply and
ghis not installed
Remote fetch failures are intentionally quiet. If GitHub does not return the file, the command keeps trying the next location and eventually prints empty output.
pr post-comment
What it does
pr post-comment takes a list of inline review comments and posts them to GitHub as one pull request review. It does not create one review per finding. Instead, it sends:
- one review summary body
- one
commentsarray containing all inline comments
That makes it a good fit for "review first, then post a selected set of findings" workflows.
Usage
myk-claude-tools pr post-comment <owner/repo> <pr_number> <commit_sha> <json_file>
myk-claude-tools pr post-comment <owner/repo> <pr_number> <commit_sha> - # stdin
Required input
The final argument is either:
- a path to a JSON file
-to read JSON from stdin
The expected JSON format is an array of comment objects:
[
{
"path": "src/main.py",
"line": 42,
"body": "### [CRITICAL] SQL Injection\n\nDescription..."
},
{
"path": "src/utils.py",
"line": 15,
"body": "### [WARNING] Missing error handling\n\nDescription..."
}
]
Each object must contain:
pathlinebody
A few small but useful details:
lineis converted withint(...), so numeric strings work too- missing fields cause an immediate error and exit
- invalid JSON causes an immediate error and exit
- if extra shell or hook output appears before the JSON array, the loader looks for the first line starting with
[and tries to parse from there
Tip: Stdin mode is handy when another step in your pipeline produces the JSON array dynamically and you do not want to write an intermediate file.
Severity markers and summary generation
The first line of each comment body controls how the review summary is grouped. Supported markers are:
### [CRITICAL] Title
### [WARNING] Title
### [SUGGESTION] Title
If no marker is present, the comment is treated as a suggestion.
The command uses those markers to generate a review body with:
- a
## Code Reviewheading - a total issue count
- grouped sections for critical issues, warnings, and suggestions
- Markdown tables listing file, line, and issue title
- a closing footer:
*Review generated by Claude Code*
The issue title comes from the first line of the comment body after removing the severity marker. It is truncated to 80 characters for the summary table.
What gets posted to GitHub
The review payload is built like this:
{
"commit_id": commit_sha,
"body": review_body,
"event": "COMMENT",
"comments": [
{
"path": c.path,
"line": c.line,
"body": c.body,
"side": "RIGHT"
}
]
}
Important implications:
- the review event is always
"COMMENT" - every inline comment is posted on the
"RIGHT"side - the
commit_shaneeds to match the PR revision GitHub expects for inline comments
Warning: Inline comments only work for lines GitHub considers part of the PR diff for that commit. If the line is outside the diff, the file path does not exist at that revision, or the SHA is stale, GitHub will reject the review.
Note: The module defines a
validate_commit_sha()helper, but the command does not currently call it before posting. In practice, you should pass the exactmetadata.head_shareturned bypr diffinstead of inventing or reusing an older SHA.
Output
When you pass one or more comments, the command:
- prints a progress message to stderr
- posts the review
- prints a JSON result to stdout
If the comment list is empty, it skips the GitHub API call and immediately prints a success object with zero counts:
{"status": "success", "comment_count": 0, "posted": [], "failed": []}
For normal success and failure cases, the output shape is:
{
"status": result.status,
"comment_count": result.comment_count,
"posted": result.posted,
"failed": result.failed
}
On failure, the command also includes error in the JSON output and prints common troubleshooting hints to stderr:
- line numbers might not be part of the diff
- file paths might not exist in the named commit
- the commit SHA might not be the PR head
Putting The Commands Together
The repository's own PR review command uses these subcommands in sequence:
myk-claude-tools pr diff {pr_number}
myk-claude-tools pr claude-md {pr_number}
When arguments are passed through directly, it uses:
myk-claude-tools pr diff $ARGUMENTS
myk-claude-tools pr claude-md $ARGUMENTS
After analysis, it posts the selected findings with:
myk-claude-tools pr post-comment {owner}/{repo} {pr_number} {head_sha} /tmp/claude/pr-review-comments.json
In practice, the flow is:
- Run
pr diffand keep the JSON output - Read
metadata.head_shafrom that output - Run
pr claude-mdto load repository-specific review instructions - Generate a JSON array of findings
- Run
pr post-commentwith the same PR and thehead_shafrom step 2
Tip: Keep
pr diffandpr post-commenttied to the same review session. Re-fetch the diff if the PR head changes before you post comments.
Configuration Snippet For Claude Code Plugins
If you are wrapping these commands in a Claude Code plugin or slash command, this repository's own PR review command uses this frontmatter:
---
description: Review a GitHub PR and post inline comments on selected findings
argument-hint: [PR_NUMBER|PR_URL]
allowed-tools: Bash(myk-claude-tools:*), Bash(uv:*), Bash(git:*), Bash(gh:*), AskUserQuestion, Task
---
That configuration captures the two important runtime requirements for this CLI:
- allow the plugin to call
myk-claude-tools - allow the plugin to call
gh
If you are automating around these commands, it is also useful to remember the output contract:
pr diffreturns JSONpr claude-mdreturns plain textpr post-commentreturns JSON, but also writes progress and troubleshooting output to stderr