This document provides context for Claude Code when continuing development on ghpr.
ghpr (GitHub PR) is a CLI tool for managing GitHub PRs and Issues locally with bidirectional sync and gist mirroring.
PyPI Package: ghpr-py (published)
Repository: https://github.com/runsascoded/ghpr
Command: ghpr
GitHub Issue/PR ↔ Local Clone → Gist (read replica/mirror)
The gist is a read replica of the local clone. push/pull operations sync between local and GitHub, and the gist is automatically updated to mirror local state.
- ✅ Basic clone/push/pull/diff commands
- ✅ Comment support (fetch, diff, push comments)
- ✅ Gist mirroring
- ✅ Issue and PR support
- ✅ Comments default enabled (with
--no-commentsopt-out) - ✅ Directory structure:
gh/{num}/for both PRs and issues - ✅ Comment filename format:
z{id}-{author}.md - ✅ Draft comment workflow:
new*.md→ post → rename - ✅ Unified diff display between
diffandpush -n - ✅ Image upload command using
utz.git.gist - ✅ Shell completion (Click-powered, subcommands + flags/options on bare
<tab>) - ✅ Shell integration with aliases (
ghprc,ghprd,ghprp,ghia, etc.) - ✅ PyPI package
ghpr-pypublished - ✅ Repository created with filtered history
- ✅ Modular package structure (commands in separate modules)
- ✅ Using published
utz>=0.21.3for git utilities
~/c/ghpr/
├── pyproject.toml # Package metadata
├── README.md
├── CLAUDE.md # This file
├── ghpr.py # Standalone uv run script
├── tests/ # pytest test suite
└── src/ghpr/
├── __init__.py
├── cli.py # Main CLI entry point, registers commands
├── api.py # GitHub API helpers
├── gist.py # Gist operations
├── comments.py # Comment file read/write
├── files.py # Description file operations
├── config.py # Git config helpers
├── patterns.py # Regex patterns
├── render.py # Diff rendering utilities
├── shell/ # Shell integration scripts (bash, fish)
└── commands/ # Modular command implementations
├── clone.py
├── create.py # Also contains `init` command
├── diff.py
├── ingest_attachments.py
├── open.py
├── pull.py
├── push.py
├── shell_integration.py
├── show.py
└── upload.py
Shell Completion (latest):
- Click-powered tab completion for subcommands, flags, and options
- Patched
Command.shell_completeandArgument.shell_completeincli.pyto suggest options on bare<tab>(Click only does this when user types-) - Completion script generated inline by
shell_integration.py(avoids extra Python invocation) - Click's Bash version warning suppressed (macOS system bash 3.2 triggers it)
ghiaalias added forghpr ingest-attachments
Comment Ownership Warnings:
ghpr diffandghpr push -nwarn when showing diffs for comments authored by othersghpr pushskips others' comments by default, with clear summary message- Use
-C(--force-others) to attempt pushing edits to others' comments
Trailing Newline Handling:
write_description_with_link_refensures files always end with a newline- Fixes diff thrashing when GitHub strips trailing newlines from PR descriptions
render_unified_diffonly shows "No newline" indicator when sides actually differ
Draft Comment Workflow:
- Create files starting with
newand ending in.md(e.g.,new.md,new-feature.md) - Commit them to git
ghpr pushautomatically:- Posts them as comments to GitHub
- Creates a commit renaming
new*.md→z{comment_id}-{author}.md - Syncs to gist
Image Upload:
ghpr upload <file>uploads to gist and returns markdown URLs- Uses
utz.git.gistmodule for shared functionality - Auto-formats as markdown for images, URL for other files
- Gist as read replica: Gist always mirrors local state, never the source of truth
- Comments by default: Comment operations are core functionality, not optional
- Fail fast: Better to error on ambiguity than guess wrong
- Git as storage: Use git commits for versioning, leverage existing git workflows
- Prefer existing tools: Use
ghCLI for API operations,gitfor VCS
if not all([owner, repo, number]):
err("Error: Could not determine PR/Issue from directory")
exit(1)proc.run('git', 'config', 'pr.owner', owner, log=None)
item_type = proc.line('git', 'config', 'pr.type', err_ok=True, log=None)<!-- author: ryan-williams -->
<!-- created_at: 2025-10-15T04:38:13Z -->
<!-- updated_at: 2025-10-15T04:38:13Z -->
Comment body here...# Fetch comments
comments = proc.json('gh', 'api', f'repos/{owner}/{repo}/issues/{number}/comments', log=False)
# Post comment (use -F with body=@file to read from file)
result = proc.json(
'gh', 'api',
'-X', 'POST',
f'repos/{owner}/{repo}/issues/{number}/comments',
'-F', f'body=@{temp_file}',
log=False
)Test with the example issue:
cd ~/c/oa/marin/issue1773 # Existing test case
# Or clone fresh:
ghpr clone https://github.com/marin-community/marin/issues/1773utzlibrary: Used forproc(subprocess),err(stderr output),cd(context manager), and git utilities
dependencies = [
"click>=8.0", # CLI framework
"utz>=0.21.3", # Subprocess and git utility helpers
]- Original author email:
ryan@runsascoded.com - History preserved from
ryan-williams/git-helpersrepo - The
zprefix on comment files ensures they sort after the main description