| name | bug-triage |
| description | Decide whether open Bug PRs target the correct branch. A bug must be fixed on the lowest maintained branch where it exists, then merged up. Use when the user says "triage bug PRs", "which PRs need retargeting", or "retarget triage".
|
Symfony Bug-PR Retarget Triage
Symfony fixes bugs on the lowest maintained branch where the bug exists, then
merges up. This skill produces a retarget recommendation per PR. The final call
belongs to a maintainer; never act without explicit confirmation.
Steps
Step 0 — Scope
Maintained branches:
curl -s https://symfony.com/releases.json
Resolve the upstream remote (not always origin):
REMOTE=$(git remote -v | awk '/[:\/]symfony\/symfony(\.git)?[[:space:]]+\(fetch\)/{print $1; exit}')
Fetch all maintained branches, then collect open Bug PRs:
git fetch "$REMOTE" <each maintained branch>
gh pr list --repo symfony/symfony --label Bug --state open --limit 300 \
--json number,title,baseRefName,milestone,isDraft
Step 1 — Investigate each PR (one subagent per PR, run concurrently, batches of 8-10)
Each subagent is read-only and returns a structured verdict.
1a. Fetch PR data:
gh pr view <N> --repo symfony/symfony \
--json number,title,baseRefName,milestone,body,files,labels,closingIssuesReferences,url
gh pr diff <N> --repo symfony/symfony
gh pr view <N> --repo symfony/symfony --comments
From the body Q&A table, read Branch?, Bug fix?, New feature?, Deprecations?.
1b. Bug fix or feature? (objective check)
The Bug fix? field is self-declared and can be wrong. The real check: does the
production diff add public/protected API surface?
gh pr diff <N> --repo symfony/symfony \
| awk '/^diff --git /{skip=($0 ~ /\/Tests\//)} !skip' \
| grep -nE '^\+\s*(public|protected)\s+(function|const|readonly|static|\??[A-Za-z\\]+\s+\$)|^\+\s*(final\s+)?(class|interface|trait|enum)\s'
If the diff grows the surface → treat as feature (target DEV, never retarget down).
Two false positives to discount: code merely moved also shows as + lines, and
adding to an @internal/@experimental class is not a BC extension.
Exception: implementing a marker interface (e.g. ResetInterface) to fix a state
leak is still a bug fix; weigh intent in those cases.
1c. Read linked issues:
gh issue view <ISSUE> --repo symfony/symfony --json title,body,labels,comments
1d. Probe lower branches (for each branch below the PR's base, oldest first):
git cat-file -e "$REMOTE/<branch>:<path>" && echo present || echo absent
git show "$REMOTE/<branch>:<path>"
git log -S'<symbol>' --oneline "$REMOTE/<branch>" -- <path>
1e. (Optional) Reproduce on the candidate branch:
git worktree add --detach ../symfony-sf-triage-<N> "$REMOTE/<branch>"
cd ../symfony-sf-triage-<N> && ./phpunit src/Symfony/Component/<Name>
cd - && git worktree remove --force ../symfony-sf-triage-<N>
Subagent verdict: PR number, component, current base, recommended base,
confidence (high/medium/low), deciding evidence, whether the fix needs adaptation.
Step 2 — Decision tree (first match wins)
Keep on current base if any hold:
- Not a bug fix (feature, deprecation, or grows the public/protected API surface).
- The touched code does not exist on any lower branch.
- The regression was introduced on the current base or later (lower branches never had it).
- Fixing lower would change frozen/contractual behaviour (flag for maintainer).
Retarget DOWN to branch T if all hold:
- It is a bug fix (fixes wrong behaviour, doesn't add capability).
T is the oldest maintained branch where the bug exists, bounded by the
regression floor and never below LOWEST.
- The fix applies on
T (directly or with a documented adaptation).
Raise the base (rare): the PR targets a branch where the code doesn't exist yet,
or an unmaintained branch → recommend the lowest maintained branch that has the code.
Step 3 — Aggregate report
Collect verdicts into a table ordered by action then component:
PR Component Base → Recommend Conf Why
#64613 Validator 7.4 6.4 high issue reports 6.4; method present & broken on 6.4
#64589 ObjectMapper 8.1 8.1 (keep) high component added in 8.1; absent below
#64576 Serializer 8.1 7.4 med regression introduced in 7.4 (commit abc123)
Routing (each is outward, so wait for confirmation before doing anything):
- Retarget: draft a short factual comment citing the evidence.
- Keep: record the reason so the PR is not re-triaged.
- Needs human judgement: low-confidence or behaviour-change cases → state the open question.
Step 4 — Execute (only on explicit user authorization)
When to comment-only vs. rebase+push:
- Comment-only when:
maintainerCanModify is false; the fix needs real code
adaptation; or the PR is already approved (a force-push dismisses reviews).
- Rebase+push when commits cherry-pick cleanly onto the target.
Rebase mechanics:
git fetch <fork-url> <headRef>; OLD=$(git rev-parse FETCH_HEAD)
git checkout -B retarget-<N> $REMOTE/<target>
git cherry-pick <oid>...
./phpunit src/Symfony/Component/<Name>
git push <fork-url> HEAD:<headRef> --force-with-lease=<headRef>:$OLD
gh pr edit <N> --repo <repo> --base <target> --milestone <target>
git checkout <dev-branch> && git branch -D retarget-<N>
After retargeting, also update the body's | Branch? | value and remove any
labels that contradict the new disposition (e.g. Feature on a confirmed bug fix).
Quick reference table
| PR shape | Recommendation | Deciding factor |
|---|
| Bug on 8.1; issue reproduced on 6.4; method present & broken on 6.4 | Retarget to 6.4 | bug reaches the floor |
| Bug on 8.2; offending line introduced in a commit first shipped in 8.0 | Retarget to 8.0 | regression floor is 8.0 |
| Bug on 8.1 in a component that didn't exist before 8.1 | Keep on 8.1 | code absent on lower branches |
New feature? yes opened on 8.1 | Raise to DEV | features target the dev branch |
| Maintainer commented "please rebase on 7.4" | Retarget to 7.4 (high) | explicit instruction overrides inference |
| Early "rebase on 6.4", later "this is a feature" | Follow the latest: keep on DEV | most recent instruction wins |
| PR already approved; bug reaches lower branches | Comment-only, ask author | force-push dismisses reviews |
| Fix uses 8.x-only API but bug exists on 6.4 | Retarget to 6.4, adapt the fix | don't leave 6.4 broken |