git-upstream-pr-diverged

v2026.09.24

Submit a diverged-fork commit to upstream as a clean PR via cherry-pick with re-derive fallback, message scrubbing, and regression checks. Use when direct rebase fails.

GitHub
Install command
npx skhub add laurigates/git-upstream-pr-diverged
Markdown
SKILL.md

/git:upstream-pr-diverged

Submit a single commit from a heavily-diverged fork back to upstream as a clean, regression-free PR. The simpler /git:upstream-pr covers aligned-fork cherry-picks; this skill handles the case where direct rebase fails.

When to Use This Skill

Use this skill when...Use /git:upstream-pr instead when...
Fork has substantially diverged from upstreamFork and upstream are roughly aligned
Cherry-pick may produce many conflicts on shared modulesSingle commit applies cleanly
You need patch-id matching for already-applied contentYou want a quick cherry-pick + cross-fork PR
The change touches files that may be fork-onlyAll touched files exist upstream
The PR needs commit-message scrubbing (fork issue refs, Claude trailers)Commit messages are already upstream-clean
Pre-flight regression check against upstream baseline mattersThe change is trivial enough to skip pre-flight

Configuration

Per-project configuration lives at .claude/upstream-pr.local.md (gitignored). All fields are optional; sensible defaults apply.

---
upstream_remote: upstream
upstream_repo: owner/repo
branch_prefix: pr-upstream/
linter_cmd: uv run ruff check
test_cmd: uv run pytest -q
pr_body_template_path: docs/UPSTREAM_PR_TEMPLATE.md
---

# Notes

Free-form notes — fork drift hotspots, files to never touch upstream, etc.
FieldDefaultUsed by
upstream_remoteupstreamAll scripts
upstream_repoParsed from git remote get-url <upstream_remote>gh pr create --repo
branch_prefixpr-upstream/Branch name in prepare-branch.sh
linter_cmd(empty — pre-flight skipped)Pre-flight stash-roundtrip
test_cmd(empty — pre-flight skipped)Pre-flight stash-roundtrip
pr_body_template_path(empty — built-in template)PR body

Add .claude/*.local.md to .gitignore.

Context

  • Current branch: !git branch --show-current
  • Working tree: !git status --porcelain=v2 --branch
  • Remotes: !git remote -v
  • Config file: !find . -path '*/.claude/*' -maxdepth 2 -name 'upstream-pr.local.md'

Parameters

Parse from $ARGUMENTS:

ParameterRequiredDescription
<sha>YesCommit SHA on the fork to send upstream
--topic <slug>NoTopic slug for branch name; auto-derived from commit subject if omitted

Execution

Execute this upstream-PR workflow:

Step 1: Check eligibility

Run the eligibility check before touching any branch:

bash "${CLAUDE_SKILL_DIR}/scripts/check-eligibility.sh" <sha>

Exit codes drive next steps:

ExitMeaningAction
0Every touched file exists upstream and content is novelProceed to Step 2
1At least one fork-only fileStop. Tell the user the change isn't standalone-PR-able; predecessor feature must land first
3Content already applied upstream under a different SHAStop. Report the matching upstream commit; nothing to PR

The patch-id check (git patch-id --stable) catches re-applied content from maintainer re-merges or squashes — even when the SHA differs.

Step 2: Prepare the branch

Derive a topic slug from the commit subject if --topic was not provided. Then:

bash "${CLAUDE_SKILL_DIR}/scripts/prepare-branch.sh" <topic-slug> <sha>

The script:

  1. Verifies a clean working tree
  2. Re-runs the eligibility check
  3. Fetches the upstream remote
  4. Creates <branch_prefix><topic-slug> from <upstream_remote>/main (or /master)
  5. Cherry-picks <sha>
  6. Reports any conflict files

Step 3: Resolve conflicts (if any)

When the cherry-pick produces conflicts, preserve upstream's surrounding shape, not the fork's. The goal is the smallest readable diff against upstream — a maintainer must see the change make sense in upstream's current code, not in the fork's.

When to abort and re-derive

If the cherry-pick produces dozens of conflict blocks across multiple files (typical when upstream and fork have drifted heavily on shared modules), abort and re-derive instead of fighting hunks:

git cherry-pick --abort
git checkout -b <branch> <upstream_remote>/main
# Re-apply the change against upstream's actual current files.

Re-derive is the right call when:

  • The original commit is a mechanical, re-applicable transform (e.g. print() → logging, deprecation rename, lint-rule auto-fixes) — the rules transfer cleanly even if line numbers don't.
  • Upstream's version of the file has additional lines the fork removed — re-derive lets you cover them with the same heuristic rather than rationalizing missing hunks.
  • The cherry-pick conflict count exceeds roughly 20 blocks across >2 files.

Heuristic: extract the original commit's before → after map (e.g. git show <sha> | grep -E '^[-+].*pattern') and use it as the rulebook when re-applying against upstream. The PR body should disclose the re-derive (see template below).

Step 4: Pre-flight regression check

For refactor / cleanup PRs, verify lint and test parity against the pristine upstream baseline, not against the fork. This catches stray formatter touches, accidental import reordering, and indentation drift from Edit-tool replacements.

If linter_cmd and test_cmd are configured, run a stash roundtrip:

# Baseline (pristine upstream):
git stash push -- <changed-files>
<linter_cmd> <changed-files> 2>&1 | tail -1   # error count
<test_cmd> 2>&1 | tail -1                     # pass count
git stash pop

# After (with your changes): run the same two commands and compare.

Both numbers must match (or improve). Quote both in the PR body's "Testing Performed" section.

For Python files, also run python3 -c "import ast; ast.parse(open(F).read())" on every edited file — catches indentation breaks that ruff might miss on already-warning-laden upstream code.

Step 5: Scrub the commit message

Run the scrub helper:

bash "${CLAUDE_SKILL_DIR}/scripts/scrub-commit.sh" --check

If violations are reported, run without --check to amend interactively:

bash "${CLAUDE_SKILL_DIR}/scripts/scrub-commit.sh"

The scrub rules:

  • Strip local issue references — Closes #74, Addresses #19, etc. point at the fork's tracker, not upstream's.
  • Strip Claude trailers — Co-authored-by: Claude ..., Generated with [Claude Code]. Upstream doesn't follow that convention.
  • Soften fork-specific tooling — if the body cites a tool upstream doesn't run (bandit B607, ty, vulture), describe the underlying problem instead.
  • Keep the conventional-commit prefix — fix(security):, feat(parser):, etc.

The amend wraps git commit --amend with PRE_COMMIT_ALLOW_NO_CONFIG=1 because upstream may have no .pre-commit-config.yaml and a locally-installed pre-commit hook would otherwise refuse the commit.

Step 6: Verify diff hygiene

Before pushing, verify with:

git diff <upstream_remote>/main..HEAD

Every hunk should be defensible to a maintainer who has never seen the fork.

  • Don't bundle formatter cleanups with the fix. Keep upstream's existing import order even if the fork's linter would reformat. Upstream may have unusual indentation (e.g. 21-space rather than 20-space blocks) — preserve it; Edit-tool replacements that change leading whitespace by even one character will break the parse.
  • One commit per PR. If the cherry-pick produced multiple commits, squash before pushing.

Step 7: Push and open the PR

git push origin <branch>

gh pr create --repo <upstream_repo> --base main \
  --head <fork-owner>:<branch> \
  --title "<conventional-commit subject>" \
  --body "<see body template below>"

If pr_body_template_path is configured, use that template; otherwise use the built-in template:

## Description

<one or two paragraphs explaining the problem and the fix from the
maintainer's perspective — not from the fork's perspective>

## Related Issue(s)

None.  <or upstream issue numbers only>

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Testing Performed

- [x] `<linter_cmd> <changed-files>` — N issues (matches upstream baseline)
- [x] `<test_cmd>` — M passed (matches upstream baseline)

## Notes

Originally authored on a downstream fork; the branch was cut from
`<upstream_repo>:main` and a single commit cherry-picked onto it so
it applies cleanly.

<If re-derived: "The fork's version of this change collided heavily with
upstream's current code; the diff was re-derived against upstream's files
using the original commit's transformation rules.">

Agentic Optimizations

ContextCommand
Eligibility checkbash check-eligibility.sh <sha> (exit 0/1/3)
Prepare branchbash prepare-branch.sh <topic> <sha>
Scrub check (CI mode)bash scrub-commit.sh --check
Diff against upstreamgit diff <upstream_remote>/main..HEAD --stat
Cross-fork PRgh pr create --repo <upstream_repo> --head <fork-owner>:<branch>
Patch-id of HEADgit show HEAD | git patch-id --stable

Related Skills

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

git-plugin/skills/git-upstream-pr-diverged

Default branch

main

Latest commit

1668324

Tree SHA

b2d4cc3