SideRepoOps: create_pull_request base-branch resolved via GitHub GET /repos 404, then stringified into the ref name → ERR_SYSTEM merge-base failure (live agent)
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 541
- Avg merge
- 5h 48m
- Merged PRs (30d)
- 773
Description
🤖 _This issue was investigated and filed by Claude Code._
## Summary
In a **SideRepoOps** workflow (an agentic workflow running in repo **``** that targets a private side repository **``** via `target-repo:`), the `create_pull_request` safe-output never produces a PR. The MCP `create_pull_request` handler fails during patch generation with:
```
Pinned SHA failed to generate patch: ERR_SYSTEM: No remote refs available for merge-base calculation
```
The agent had correctly implemented and committed its change locally on a feature branch; the failure is entirely in the handler's patch generation, not the agent's git state.
This is the same `ERR_SYSTEM` symptom as #37836, but observed in a **live agent run (not samples mode)**, and the logs reveal a **more specific root cause than the hypothesis in #37836**: the base branch is resolved by a GitHub `GET /repos/{owner}/{repo}` call that returns **`404 Not Found`**, and the 404 error object is then **stringified and used as the base-branch name**.
## Environment
- `github/gh-aw-actions/setup@v0.79.1`
- firewall containers `ghcr.io/github/gh-aw-firewall/{agent,api-proxy,squid}:0.25.68`
- `ghcr.io/github/gh-aw-mcpg:v0.3.25`
- engine: `claude` (Claude Code)
- Pattern: SideRepoOps — workflow repo ``, side repo `` (both **private**)
## Root cause (from the safeoutputs MCP server log)
The handler logs the base branch it will diff against as the **JSON body of a 404 response**:
```
[safeoutputs] Using configured patch_workspace_path for create_pull_request: -> /home/runner/work///
[safeoutputs] Pinned branch '' to SHA
[safeoutputs] Generating patch for create_pull_request with branch: in .../ baseBranch: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/repos/repos#get-a-repository","status":"404"}
[generate_git_patch] Starting patch generation: mode=full, branch=, defaultBranch={"message":"Not Found","documentation_url":"https://docs.github.com/rest/repos/repos#get-a-repository","status":"404"}
[generate_git_patch] Environment: cwd=.../, GITHUB_SHA=
[generate_git_patch] Strategy 1: Using pinned SHA (branch: )
[generate_git_patch] Strategy 1 (full): Computing merge-base with {"message":"Not Found",...,"status":"404"} (ignoring any stale origin/)
[generate_git_patch] Strategy 1 (full): origin/{"message":"Not Found",...,"status":"404"} not present locally and remote fetch failed (likely private repo without credentials in MCP server). Add "{...404 json...}" to checkout.fetch to enable this strategy.
[generate_git_patch] Strategy 1 (full): No remote refs available, falling through to Strategy 2
[generate_git_patch] Strategy 1: Branch '' does not exist locally - ERR_SYSTEM: No remote refs available for merge-base calculation
[safeoutputs] Patch generation failed: Pinned SHA failed to generate patch: ERR_SYSTEM: No remote refs available for merge-base calculation
```
So the failing chain is:
1. The handler resolves the side repo's base/default branch via a GitHub `GET /repos/{owner}/{repo}` API call.
2. That call returns **`404 Not Found`** — the token available to the safeoutputs MCP server cannot see the **private** side repo (404 = no visibility).
3. The 404 is **not detected as an error**. The error JSON object is stringified and used as both `baseBranch` and `defaultBranch`: `{"message":"Not Found",...,"status":"404"}`.
4. `generate_git_patch` then computes a merge-base against the literal ref `origin/{"message":"Not Found",...}`, which of course doesn't exist locally and can't be fetched, and aborts with `ERR_SYSTEM: No remote refs available for merge-base calculation`.
## Why #37836's hypothesis does not fully explain this
#37836 hypothesizes that `origin/` simply isn't fetched into the side-repo checkout. In this run that is **not** the case — the side repo is checked out with full history and all branch refs are fetched:
```yaml
- name: Checkout into
uses: actions/checkout@v6
with:
repository:
ref: master
path:
persist-credentials: false
fetch-depth: 0
- name: Fetch additional refs for
run: git -C .../ fetch origin '+refs/heads/*:refs/remotes/origin/*'
```
The agent's own diagnostics inside that checkout confirmed `origin/master` resolves and 22 `refs/remotes/origin/*` refs are present locally, and `git merge-base HEAD origin/master` succeeds. **A merge-base against `master` would have worked** — the handler just never asks for `master` because the base-branch lookup 404'd and poisoned the ref name.
The base branch is also explicitly configured, so the API lookup shouldn't be needed at all:
```yaml
safe-outputs:
create-pull-request:
target-repo:
allowed-repos: []
allowed-base-branches: [master]
```
## Likely trigger: missing cross-repo token on `create-pull-request`
In our config, `tools.github` is given a cross-repo token, but the `create-pull-request` safe-output has **no `github-token:`** override. The repro in #37836 passes `github-token: ${{ secrets.TEMP_USER_PAT }}` to `create-pull-request`. It looks like the handler's base-branch `GET /repos` call uses a token without read access to the private side repo, producing the 404. (`persist-credentials: false` also means the MCP server has no creds to re-fetch — though that's moot here since the refs are already present locally.)
## Misleading error surface (matches #37836 secondary observation)
The handler returns the failure as `{"result":"error", ...}` inside a JSON-RPC response with `isError: false`, and the `details` field is actively misleading:
```json
{"result":"error","error":"Pinned SHA failed to generate patch: ERR_SYSTEM: No remote refs available for merge-base calculation","details":"No commits were found to create a pull request. Make sure you have committed your changes using git add and git commit before calling create_pull_request."}
```
The `details` told the agent commits were missing (they weren't). In a live agent run this caused the agent to give up on `create_pull_request` and fall back to other outputs — every job still reported success, so the run **looks green but silently produces no PR**. This has recurred on every cross-repo PR attempt in this workflow since ~2026-06-08.
## Suggested fixes
1. **Detect non-2xx responses** from the base-branch / default-branch `GET /repos` lookup. Never stringify an error object into a branch name.
2. **Prefer configured base branch.** When `allowed-base-branches` (or the checkout `ref:`) is set, use it for the merge-base instead of an API round-trip — the local checkout already has `origin/`.
3. **Use the cross-repo token** (the same one resolving `target-repo:` / `allowed-repos:`) for the base-branch lookup so private side repos return 200, or document that `create-pull-request.github-token` is required for SideRepoOps.
4. **Fix the error surface:** map `result: "error"` to `isError: true` (also raised in #37836), and correct the `details` text so it doesn't claim "no commits" when the real failure is base-branch resolution.
## Redaction note
Private repository names, branch names, commit SHAs, and run URLs have been replaced with placeholders (``, ``, ``, ``). The literal `{"message":"Not Found",...,"status":"404"}` strings are reproduced verbatim from the logs because they are the core of the bug. Happy to share additional redacted log context on request.
Related: #37836.
Contributor guide
Assessment
This issue has not been assessed yet.