Problem
GitHubPRForker#user_fork_of_repo finds "the user's fork" by listing the direct forks of the target repo and matching on owner. That works for normal simp/* repos, but not for repos that are themselves forks, like simp/pupmod-voxpupuli-selinux (a fork of voxpupuli/puppet-selinux, default branch simp-master).
Concretely: silug/puppet-selinux is a fork of the network root voxpupuli/puppet-selinux, which makes it a sibling of simp/pupmod-voxpupuli-selinux, not a direct fork of it — so user_fork_of_repo('simp/pupmod-voxpupuli-selinux', 'silug') returns nil even though the user has a perfectly usable fork in the network.
From there, ensure_fork POSTs /repos/simp/pupmod-voxpupuli-selinux/forks and one of two things happens:
- GitHub creates a brand-new direct fork (
silug/pupmod-voxpupuli-selinux — the modern fork-a-fork behavior when the name is free). The pipeline then works end-to-end, but through a redundant second fork instead of the user's existing one. There's also a first-run race: fork creation is async, and with github_api_delay_seconds: 1 the push/PR stages can hit the fork before it exists (heals on re-run).
- GitHub returns the existing sibling fork (the legacy one-fork-per-network dedup). The push then lands in
silug/puppet-selinux, but ensure_github_pr fails: existing_pr dereferences the nil fork lookup (repo_fork.full_name → NoMethodError) when the target repo has open PRs, and create_pr raises ERROR: no fork of '...' found for user ... otherwise.
Either way, the sync never uses the user's existing fork of the upstream, and in case 2 the PR stage crashes.
Proposed fix
Make user_fork_of_repo fork-network-aware. After the direct-forks check misses:
- Resolve the network root:
upstream_repo.fork ? upstream_repo.source : upstream_repo (e.g. voxpupuli/puppet-selinux).
- Probe the user's candidate repos by name —
#{fork_user}/#{upstream_repo.name} and #{fork_user}/#{root.name} (e.g. silug/puppet-selinux).
- Accept a candidate iff it's a fork whose
source is the same network root.
With that, ensure_fork returns the existing sibling fork without creating anything, the feature branch is pushed there, and create_pull_request opens the PR with head user:branch against the simp repo's base branch — GitHub allows PRs between any repos in the same fork network, and the base branch is already correct (the dynamic inventory records the repo's real default branch, simp-master).
existing_pr should also be hardened against a nil fork lookup regardless (it currently NoMethodErrors instead of reporting a useful failure).
Context
Found while rolling out the 20260811-reference-md session (#58): pupmod-voxpupuli-selinux is in the dynamic inventory via the include_forks allow-list, so its GitHub stages will exercise this path as soon as the repo-side CI problems are sorted out.
Problem
GitHubPRForker#user_fork_of_repofinds "the user's fork" by listing the direct forks of the target repo and matching on owner. That works for normalsimp/*repos, but not for repos that are themselves forks, likesimp/pupmod-voxpupuli-selinux(a fork ofvoxpupuli/puppet-selinux, default branchsimp-master).Concretely:
silug/puppet-selinuxis a fork of the network rootvoxpupuli/puppet-selinux, which makes it a sibling ofsimp/pupmod-voxpupuli-selinux, not a direct fork of it — souser_fork_of_repo('simp/pupmod-voxpupuli-selinux', 'silug')returns nil even though the user has a perfectly usable fork in the network.From there,
ensure_forkPOSTs/repos/simp/pupmod-voxpupuli-selinux/forksand one of two things happens:silug/pupmod-voxpupuli-selinux— the modern fork-a-fork behavior when the name is free). The pipeline then works end-to-end, but through a redundant second fork instead of the user's existing one. There's also a first-run race: fork creation is async, and withgithub_api_delay_seconds: 1the push/PR stages can hit the fork before it exists (heals on re-run).silug/puppet-selinux, butensure_github_prfails:existing_prdereferences the nil fork lookup (repo_fork.full_name→NoMethodError) when the target repo has open PRs, andcreate_prraisesERROR: no fork of '...' found for user ...otherwise.Either way, the sync never uses the user's existing fork of the upstream, and in case 2 the PR stage crashes.
Proposed fix
Make
user_fork_of_repofork-network-aware. After the direct-forks check misses:upstream_repo.fork ? upstream_repo.source : upstream_repo(e.g.voxpupuli/puppet-selinux).#{fork_user}/#{upstream_repo.name}and#{fork_user}/#{root.name}(e.g.silug/puppet-selinux).sourceis the same network root.With that,
ensure_forkreturns the existing sibling fork without creating anything, the feature branch is pushed there, andcreate_pull_requestopens the PR with headuser:branchagainst the simp repo's base branch — GitHub allows PRs between any repos in the same fork network, and the base branch is already correct (the dynamic inventory records the repo's real default branch,simp-master).existing_prshould also be hardened against a nil fork lookup regardless (it currently NoMethodErrors instead of reporting a useful failure).Context
Found while rolling out the 20260811-reference-md session (#58):
pupmod-voxpupuli-selinuxis in the dynamic inventory via theinclude_forksallow-list, so its GitHub stages will exercise this path as soon as the repo-side CI problems are sorted out.