Skip to content

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)Β #39404

Description

@yskopets

πŸ€– This issue was investigated and filed by Claude Code.

Summary

In a SideRepoOps workflow (an agentic workflow running in repo <workflow-repo> that targets a private side repository <side-repo> 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 <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 <workflow-repo>, side 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: <side-repo-path> -> /home/runner/work/<workflow-repo>/<workflow-repo>/<side-repo-path>
[safeoutputs] Pinned branch '<feature-branch>' to SHA <sha>
[safeoutputs] Generating patch for create_pull_request with branch: <feature-branch> in .../<side-repo-path> 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=<feature-branch>, defaultBranch={"message":"Not Found","documentation_url":"https://docs.github.com/rest/repos/repos#get-a-repository","status":"404"}
[generate_git_patch] Environment: cwd=.../<side-repo-path>, GITHUB_SHA=<workflow-repo-sha>
[generate_git_patch] Strategy 1: Using pinned SHA <sha> (branch: <feature-branch>)
[generate_git_patch] Strategy 1 (full): Computing merge-base with {"message":"Not Found",...,"status":"404"} (ignoring any stale origin/<feature-branch>)
[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 '<feature-branch>' does not exist locally - ERR_SYSTEM: No remote refs available for merge-base calculation
[safeoutputs] Patch generation failed: Pinned SHA <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/<baseBranch> 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:

- name: Checkout <side-repo> into <side-repo-path>
  uses: actions/checkout@v6
  with:
    repository: <side-repo>
    ref: master
    path: <side-repo-path>
    persist-credentials: false
    fetch-depth: 0
- name: Fetch additional refs for <side-repo>
  run: git -C .../<side-repo-path> 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:

safe-outputs:
  create-pull-request:
    target-repo: <side-repo>
    allowed-repos: [<side-repo>]
    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:

{"result":"error","error":"Pinned SHA <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/<base>.
  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 samples mode: create_pull_request fails with ERR_SYSTEM merge-base for siderepo workflowsΒ #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 (<workflow-repo>, <side-repo>, <feature-branch>, <sha>). 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.

Activity

  1. locked and limited conversation to collaborators on Jun 15, 2026
  2. unlocked this conversation on Jun 15, 2026
  3. Tarekchehahde commented on Jul 11, 2026

    @Tarekchehahde

    Fork PR: Tarekchehahde#9

    Upstream compare: main...Tarekchehahde:gh-aw:fix/39404-base-branch-404-validation

    Approach: Validate branch names (reject 404 JSON bodies), honor sole allowed-base-branches entry without API, fail closed with actionable errors, fix misleading "no commits" details on merge-base failures.

    Local tests: vitest run git_patch_utils.test.cjs create_pull_request_helpers.test.cjs get_base_branch.test.cjs β€” 111/111 pass

  4. locked and limited conversation to collaborators on Jul 11, 2026
  5. unlocked this conversation on Jul 11, 2026
  6. bjartenilsen commented on Sep 11, 2026

    @bjartenilsen

    Hitting the same class of bug on GitHub Enterprise Server (not github.com), with a 401 instead of 404, so leaving this here rather than filing a duplicate.

    Setup: gh aw CLI v0.88.7, dnb.ghe.com, create_pull_request safe-output targeting a different repo (target-repo: cross-repo, public within the enterprise β€” not private), Copilot engine.

    Symptom: intermittent β€” 2 failures out of 5 consecutive live-mode runs of the same workflow against the same target repo, same checkout config (fetch-depth: 0, plus a custom step re-fetching refs/heads/* after checkout). create_pull_request fails with:

    {"result":"error","error":"Pinned SHA <sha> failed to generate patch: ERR_SYSTEM: No remote refs available for merge-base calculation", ...}
    

    ...even though git merge-base, git log, and calling generateGitPatch() directly all succeed against the exact same checkout moments later in the same job.

    Root cause found in job logs: the build_checkout_manifest step (which runs right after the actions/checkout steps, before our custom "fetch additional refs" step populates refs/remotes/origin/*) tries to resolve the target repo's default branch two ways, and both fail:

    [debug] build_checkout_manifest: git default branch lookup failed for <target-repo>: Failed to run git symbolic-ref --short refs/remotes/origin/HEAD
    fatal: ref refs/remotes/origin/HEAD is not a symbolic ref
    
    [debug] build_checkout_manifest: gh api default branch lookup failed for <target-repo>: Failed to run gh api repos/<target-repo> --jq .default_branch
    gh: Bad credentials (HTTP 401)
    
    [info] checkout-manifest: <target-repo> -> path= default_branch=<unresolved>
    [info] checkout-manifest: <target-repo> -> path=target default_branch=<unresolved>
    

    So instead of a stringified 404 body becoming the ref name (as in the original report), here it resolves to the literal string <unresolved>, which presumably also fails to resolve to a real ref later.

    Two distinct issues stacked here, same as the original report's diagnosis:

    1. git symbolic-ref refs/remotes/origin/HEAD is dead-end β€” actions/checkout's sparse/manual checkout mode never sets that ref, so this lookup always fails first.
    2. The gh api repos/<target-repo> fallback doesn't appear to use the same token that successfully performed the checkout of that repo a few steps earlier β€” it gets 401 Bad credentials even though the checkout itself (using GH_AW_GITHUB_TOKEN) worked fine for the same repo.

    This intermittently poisons later merge-base computation depending on timing between this early manifest-building step and the later "fetch additional refs" step. When it fails 3 retries in the same run, our workflow gracefully falls back to filing an issue with a ready-to-apply patch instead of crashing, so the impact is "no unattended PR" rather than data loss β€” but it does defeat unattended create_pull_request for cross-repo/live-mode workflows on GHES.

    Happy to share full anonymized job logs if useful β€” this reproduced reliably enough (2/5 runs) that I could probably get a fresh repro on request.

  7. locked and limited conversation to collaborators on Sep 11, 2026
  8. unlocked this conversation on Sep 11, 2026
  9. pelikhan commented on Oct 7, 2026

    @pelikhan
    Collaborator

    Status (2026-10-07; Contributor proposal): A July 11 fork proposal would reject API error bodies used as branch names; a September 11 GHES report describes an intermittent 401 variant of the merge-base failure. The next step is to assess that proposal against both 404 and 401 cases and verify that failures produce actionable errors rather than invalid refs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions