A modern Python tool for automatically fixing pull request titles and bodies across GitHub organizations. Scans for blocked PRs and updates them based on commit messages.
- π Organization Scanning: Scan entire GitHub organizations for blocked pull requests
- βοΈ Title Fixing: Set PR titles to match the first commit's subject line
- π Body Fixing: Set PR descriptions to match commit message bodies (excluding trailers)
- π File Fixing: Apply regex-based search/replace to files in PRs (clones, modifies, amends commit, and force-pushes changes)
- π« Blocked PR Filtering: Option to process PRs that cannot merge (failing checks, conflicts, etc.)
- π Parallel Processing: Process PRs concurrently for performance
- π Dry Run Mode: Preview changes before applying them
- π Progress Tracking: Real-time progress updates during scanning
- π― Smart Parsing: Automatically removes Git trailers (Signed-off-by, etc.)
- π¬ PR Comments: Automatically adds a comment to PRs explaining the changes made
pip install pull-request-fixerOr with uv:
uv pip install pull-request-fixer# Set your GitHub token
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxx
# Fix PR titles in an organization
pull-request-fixer lfreleng-actions --fix-title
# Fix both titles and bodies
pull-request-fixer lfreleng-actions --fix-title --fix-body
# Show help (includes version)
pull-request-fixer --help
# Fix files in a specific PR using regex
pull-request-fixer https://github.com/owner/repo/pull/123 \
--fix-files \
--file-pattern './action.yaml' \
--search-pattern '^\s+type:\s+\S' \
--remove-lines \
--context-start 'inputs:' \
--context-end 'runs:' \
--dry-run \
--show-diff
# Preview changes without applying (dry run)
pull-request-fixer lfreleng-actions --fix-title --fix-body --dry-runScan and fix an organization:
pull-request-fixer ORGANIZATION [OPTIONS]Fix a specific PR:
pull-request-fixer PR_URL [OPTIONS]You can specify the target as:
- Organization name:
myorg - GitHub URL:
https://github.com/myorg - GitHub URL with path:
https://github.com/myorg/ - Specific PR URL:
https://github.com/owner/repo/pull/123
Updates the PR title to match the first line (subject) of the first commit message.
Example:
If the first commit message is:
Fix authentication bug in login handler
This commit addresses an issue where users couldn't
log in with special characters in passwords.
Signed-off-by: John Doe <john@example.com>
This sets the PR title to:
Fix authentication bug in login handler
Updates the PR description to match the commit message body, excluding trailers.
Using the same commit message above, this sets the PR body to:
This commit addresses an issue where users couldn't
log in with special characters in passwords.
The Signed-off-by: trailer is automatically removed.
Fixes files in pull requests using regex-based search and replace. This feature:
- Clones the PR branch
- Finds files matching
--file-pattern(regex) - Applies search/replace using
--search-patternand--replacement - Amends the last commit with the changes
- Force-pushes the updated commit back to the PR
Required options when using --fix-files:
--file-pattern: Regex to match file paths (e.g.,'./action.yaml'or'.*\.yaml$')--search-pattern: Regex pattern to find in matched files- Either
--replacement(text to replace matches) or--remove-lines(to delete matching lines)
Optional context options (for line removal):
--context-start: Regex to define where the removal context begins (e.g.,'inputs:')--context-end: Regex to define where the removal context ends (e.g.,'runs:')
Optional display options:
--show-diff: Show unified diff output for file changes
The tool supports two methods for applying file fixes:
API Method (default) - Uses GitHub API to create new commits:
pull-request-fixer https://github.com/owner/repo/pull/123 \
--fix-files \
--file-pattern './action.yaml' \
--search-pattern 'pattern'- Creates new commits via GitHub API
- Shows as "Verified" by GitHub
- No Git operations required
- Faster and simpler
- Default method for ease of use
Git Method - Clones repo, amends commit, force-pushes:
pull-request-fixer https://github.com/owner/repo/pull/123 \
--fix-files \
--update-method git \
--file-pattern './action.yaml' \
--search-pattern 'pattern'- Respects your local Git signing configuration
- Amends the existing commit (preserves commit history)
- Requires Git operations (clone, amend, push)
- Use when you need to amend commits or use your own signature
Example - Remove type definitions from GitHub Actions:
pull-request-fixer https://github.com/owner/repo/pull/123 \
--fix-files \
--file-pattern './action.yaml' \
--search-pattern '^\s+type:\s+\S' \
--remove-lines \
--context-start 'inputs:' \
--context-end 'runs:' \
--dry-run \
--show-diffThis will remove lines containing type: that appear between inputs: and runs:
sections in action.yaml files.
Example - Replace text with regex:
pull-request-fixer https://github.com/owner/repo/pull/456 \
--fix-files \
--file-pattern '.*\.py$' \
--search-pattern 'old_function_name' \
--replacement 'new_function_name'Fix titles:
pull-request-fixer myorg --fix-titleFix both titles and bodies:
pull-request-fixer myorg --fix-title --fix-bodyPreview changes (dry run):
pull-request-fixer myorg --fix-title --fix-body --dry-runInclude draft PRs:
pull-request-fixer myorg --fix-title --include-draftsUse more workers for large organizations:
pull-request-fixer myorg --fix-title --workers 16Quiet mode for automation:
pull-request-fixer myorg --fix-title --quietVerbose mode for debugging:
pull-request-fixer myorg --fix-files --file-pattern '*.yaml' \
--search-pattern '^\s+type:\s+\S' --remove-lines \
--verbose # Shows detailed DEBUG logs including file operationsProcess blocked PRs:
pull-request-fixer myorg --fix-title --blocked-onlyFix files across PRs:
pull-request-fixer myorg \
--fix-files \
--file-pattern './action.yaml' \
--search-pattern '^\s+type:\s+\S' \
--remove-lines \
--context-start 'inputs:' \
--context-end 'runs:' \
--blocked-only \
--dry-runWhen the tool applies fixes (not in dry-run mode), it automatically adds a comment to the PR explaining the changes. This provides transparency and helps PR authors understand the automated modifications.
Example comment:
## π οΈ Pull Request Fixer
Automatically fixed pull request metadata:
- **Pull request title** updated to match first commit
- **Pull request body** updated to match commit message
---
*This fix was automatically applied by [pull-request-fixer](https://github.com/lfreleng-actions/pull-request-fixer)*The comment includes the items that changed. For example, if the title changed, that line will appear in the comment.
| Flag | Short | Default | Description |
|---|---|---|---|
--help |
-h |
Show help message and exit (displays version) | |
--token |
-t |
$GITHUB_TOKEN |
GitHub personal access token |
--fix-title |
false |
Fix PR title to match first commit subject | |
--fix-body |
false |
Fix PR body to match commit message body | |
--fix-files |
false |
Fix files in PR using regex search/replace | |
--file-pattern |
Regex to match file paths (required w/ --fix-files) |
||
--search-pattern |
Regex to search in files (required w/ --fix-files) |
||
--replacement |
Replacement string for matched patterns | ||
--remove-lines |
false |
Remove matching lines instead of replacing | |
--context-start |
Regex pattern for context start (for line removal) | ||
--context-end |
Regex pattern for context end (for line removal) | ||
--show-diff |
false |
Show unified diff output for file changes | |
--include-drafts |
false |
Include draft PRs in scan | |
--blocked-only |
false |
Process PRs that cannot merge | |
--dry-run |
false |
Preview changes without applying them | |
--workers |
-j |
4 |
Number of parallel workers (1-32) |
--verbose |
-v |
false |
Enable verbose output (DEBUG logs) |
--quiet |
-q |
false |
Suppress output except errors |
--log-level |
INFO |
Set logging level | |
--version |
Show version and exit |
- Scan Organization: Uses GitHub's GraphQL API to efficiently find blocked pull requests
- Fetch Commits: Retrieves the first commit from each PR using the REST API
- Parse Messages: Extracts commit subject and body, removing trailers
- Apply Changes: Updates PR titles and/or bodies in parallel
- Report Results: Shows summary of changes made
The following Git trailer patterns are automatically removed from PR bodies:
Signed-off-by:Co-authored-by:Reviewed-by:Tested-by:Acked-by:Cc:Reported-by:Suggested-by:Fixes:See-also:Link:Bug:Change-Id:
You need a GitHub personal access token with appropriate permissions:
- Go to GitHub Settings β Developer settings β Personal access tokens
- Generate a new token with
reposcope (orpublic_repofor public repos) - Set the token as an environment variable:
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxOr pass it via the --token flag:
pull-request-fixer myorg --fix-title --token ghp_xxxxxxxxxxxxxpull-request-fixer lfreleng-actions --fix-titleOutput:
π Scanning organization: lfreleng-actions
π§ Will fix: titles
π Found 15 blocked PRs to process
π Blocked PRs:
β’ lfreleng-actions/repo1#123: Update docs
β’ lfreleng-actions/repo2#456: Fix bug
...
π Processing: lfreleng-actions/repo1#123
β
Updated title: docs: Add usage examples for CLI
π Processing: lfreleng-actions/repo2#456
β
Updated title: fix: Resolve authentication timeout issue
β
Fixed 15 PR(s)
pull-request-fixer myorg --fix-title --fix-body --dry-runOutput:
π Scanning organization: myorg
π§ Will fix: titles, bodies
π Dry run mode: no changes made
π Found 5 blocked PRs to process
π Processing: myorg/repo#123
Would update title:
From: Update documentation
To: docs: Add usage examples for CLI
Would update body
Length: 245 chars
β
[DRY RUN] Would fix 5 PR(s)
For large organizations, use more workers:
pull-request-fixer bigorg --fix-title --fix-body --workers 16 --verbose- Parallel Processing: PRs processed concurrently for speed
- Efficient Queries: GraphQL for scanning, REST for updates
- Memory Efficient: Streaming results, no need to load all PRs
- Typical Speed: 2-5 seconds per repository
Example timing for 100 repositories with 50 blocked PRs using 8 workers:
- Organization scan: ~30-60 seconds
- PR processing: ~20-30 seconds
- Total: ~50-90 seconds
This example removes invalid type: definitions from a GitHub composite action:
pull-request-fixer https://github.com/lfreleng-actions/make-action/pull/40 \
--fix-files \
--file-pattern './action.yaml' \
--search-pattern '^\s+type:\s+\S' \
--remove-lines \
--context-start 'inputs:' \
--context-end 'runs:' \
--dry-run \
--show-diffOutput:
π Processing PR: https://github.com/lfreleng-actions/make-action/pull/40
π§ Will fix: files
π Dry run mode: the tool will not apply changes
π Fixing files in PR...
β Would fix 1 file π action.yaml --- action.yaml +++ action.yaml @@ -10,7 +10,6 @@ repository: description: 'Remote Git repository URL' required: false
- type: 'string' debug: description: 'Enable debug mode' required: false
- type: 'boolean'
Dry-run completed!
Without `--dry-run`, the output would show:
```text
β
Updated 1 file
π action.yaml
--- action.yaml
+++ action.yaml
@@ -10,7 +10,6 @@
repository:
description: 'Remote Git repository URL'
required: false
- type: 'string'
debug:
description: 'Enable debug mode'
required: false
- type: 'boolean'
This would:
- Clone the PR branch
- Remove all lines containing
type:from theinputs:section ofaction.yaml - Amend the last commit with the changes
- Force-push the updated commit
- Add a comment to the PR explaining the fix
If the tool reports "No blocked PRs found", this could mean:
- The organization truly has no blocked PRs
- You may need to adjust the scanner's definition of "blocked"
If you see authentication errors:
Make sure your GITHUB_TOKEN environment variable contains a valid token
- Verify the token has
repoorpublic_reposcope - Check that the token hasn't expired
If you hit rate limits:
- Reduce the number of workers:
--workers 2 - Wait for the rate limit to reset (shown in error message)
- Use a token with higher rate limits
If updates fail:
- Ensure your token has write access to the repositories
- Check that you're not trying to update PRs in archived repos
- Verify the PRs are not locked
git clone https://github.com/lfreleng-actions/pull-request-fixer.git
cd pull-request-fixer
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"pytestpre-commit install
pre-commit run --all-filesThe project uses:
rufffor linting and formattingmypyfor type checkingpytestfor testing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Apache-2.0
- Issues: https://github.com/lfreleng-actions/pull-request-fixer/issues
- Documentation: https://github.com/lfreleng-actions/pull-request-fixer/blob/main/IMPLEMENTATION.md
- Changelog: https://github.com/lfreleng-actions/pull-request-fixer/blob/main/CHANGELOG.md
- dependamerge - Automatically merge automation PRs
- markdown-table-fixer - Fix markdown table formatting
This project uses patterns from:
- dependamerge for efficient GitHub organization scanning
- markdown-table-fixer for the initial codebase structure