SideRepoOps: create_pull_request bases PR on repo default branch, not the checked-out ref (breaks non-default/release-branch checkouts)
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 541
- Avg merge
- 5h 46m
- Merged PRs (30d)
- 760
Description
🤖 _This issue was investigated and filed by Claude Code._
## Summary
In a **SideRepoOps** workflow that checks out a target/side repository at a **non-default ref** (`checkout: [{ repository: …, ref: }]`), `create_pull_request` and `push_to_pull_request_branch` base the PR on the side repo's **default branch**, not on the **ref that was actually checked out**. The agent commits on top of ``, but the patch's merge-base is computed against the default branch — producing a PR with the wrong base (and a diff that drags in the entire release-branch divergence).
This is related to but distinct from the credential/404 failure in #39404. Here the concern is *which* branch is chosen as the base, even when resolution succeeds.
## Where the base branch comes from
For `create_pull_request`, `safe_outputs_handlers.cjs` resolves the base branch in this order:
```js
if (prConfig.base_branch) baseBranch = prConfig.base_branch; // ① explicit config
else if (manifestEntry && manifestEntry.default_branch) baseBranch = …; // ② checkout manifest
else baseBranch = await getBaseBranch(...); // ③ repos.get() default_branch
```
Both ② and ③ resolve to the **repository default branch**:
- The compiler-emitted *"Build checkout manifest for safe-outputs handlers"* step computes `default_branch` via `git symbolic-ref --short refs/remotes/origin/HEAD` (→ the repo's default) or `gh api repos/ --jq .default_branch` (→ the repo's default).
- `get_base_branch.cjs` falls back to `github.rest.repos.get().default_branch` (→ the repo's default).
Nothing in the chain consults the **`ref` the workflow checked out**. So when a workflow does:
```yaml
checkout:
- repository: /
ref: # e.g. a maintenance/release branch, NOT the default
path:
```
…the agent works on top of ``, but the PR ends up based on ``.
## Impact
- The PR targets the wrong base branch.
- `generate_git_patch` computes `merge-base(feature, origin/)`, which lies far behind ``; the resulting patch/diff includes the whole divergence between the release branch and the default branch, not just the agent's change.
- Only single-branch repos (where default == the checked-out ref) are unaffected, which masks the bug.
## Expected
When a checkout pins a non-default `ref`, that ref should be the default base for `create_pull_request` / `push_to_pull_request_branch` — i.e. the base should track the checked-out branch, not the repository's default branch. Concretely, the checkout manifest could record the checked-out ref (e.g. `git rev-parse --abbrev-ref HEAD` of the checkout) rather than `origin/HEAD`'s default, or the handler could prefer the checkout's upstream branch.
## Current workaround
Set `base_branch` explicitly (path ①) to the checked-out ref:
```yaml
safe-outputs:
create-pull-request:
base-branch: # must be kept in lockstep with checkout.ref
```
This works but duplicates the ref and is easy to let drift from `checkout.ref`. Deriving the base from the checkout would remove that footgun.
## Redaction note
Private repository names, branch names, and run URLs are replaced with placeholders (`/`, ``, ``). Related: #39404.
Contributor guide
Assessment
This issue has not been assessed yet.