Skip to content

[ARC/DinD] safe-outputs: dynamic base-branch unresolved — GH_AW_INPUT_* not passed to MCP container #47795

Description

@r-garcia-de-oliveira

Summary

For safe-outputs.create-pull-request with a dynamic base-branch that references a workflow input:

on:
  workflow_dispatch:
    inputs:
      base_branch: { type: string, default: develop }
safe-outputs:
  create-pull-request:
    base-branch: ${{ inputs.base_branch }}

the value never reaches the safe-outputs MCP server, so create_pull_request fails with:

ERR_SYSTEM: No remote refs available for merge-base calculation

and no pull request is created.

The compiler stores the value in config.json as the placeholder "base_branch":"${GH_AW_INPUT_BASE_BRANCH}" and relies on the MCP server resolving it at read-time from process.env.GH_AW_INPUT_BASE_BRANCH. Since the safe-outputs MCP server was moved into a container (#39100), GH_AW_INPUT_* is not in the container's -e env allowlist, so the placeholder is never substituted. The MCP then tries to compute a merge-base against a branch literally named ${GH_AW_INPUT_BASE_BRANCH} and throws.

This is a regression: it worked on v0.79.8 (MCP ran as an HTTP sidecar that inherited the full step env) and broke in v0.80.0 (MCP containerized with a filtered env allowlist).

Impact

  • create_pull_request is broken for any workflow using a dynamic base-branch: ${{ inputs.* }} — no PR is produced, only a report_incomplete/noop.
  • The same read-time-substitution path is used for other input-derived safe-outputs config values (e.g. allowed_base_branches, target-repo inputs), so those are affected too.
  • Compilation succeeds; the failure only surfaces at runtime inside the MCP container, making it hard to diagnose.
  • A static base-branch: develop works (it compiles to a literal, needing no substitution), which can mask the bug.

Environment

  • Reproduced on v0.82.14; re-verified byte-identical on v0.83.1 (latest stable) and v0.83.2 (pre-release).
  • Regression bisected to v0.80.0 (worked on v0.79.8).
  • Engine: copilot. Observed with runner.topology: arc-dind, but the cause (container env allowlist) is topology-independent.

Minimal reproduction

.github/workflows/repro.md:

---
on:
  workflow_dispatch:
    inputs:
      base_branch:
        required: false
        default: develop
        type: string
runs-on: ubuntu-latest
permissions:
  contents: read
engine:
  id: copilot
safe-outputs:
  create-pull-request:
    base-branch: ${{ inputs.base_branch }}
---
# Repro
Make a trivial change and open a PR.

Then gh aw compile repro and inspect repro.lock.yml:

  1. The config is written with the placeholder:
    "create_pull_request":{"base_branch":"${GH_AW_INPUT_BASE_BRANCH}", ...}
    
  2. The step env sets GH_AW_INPUT_BASE_BRANCH: ${{ inputs.base_branch }} (correct).
  3. But the safe-outputs MCP docker run command's -e allowlist contains no GH_AW_INPUT_* entry — so the container process never sees GH_AW_INPUT_BASE_BRANCH.

At runtime the MCP logs (with DEBUG) show the literal placeholder:

[generate_git_patch] Strategy 1 (full): Computing merge-base with ${GH_AW_INPUT_BASE_BRANCH} ...
git show-ref --verify --quiet refs/remotes/origin/${GH_AW_INPUT_BASE_BRANCH}   (fails)
Strategy 1 (full): No remote refs available ... ERR_SYSTEM: No remote refs available for merge-base calculation

Root cause

Permalinks pinned to 6e6860cce13c4eff003828de391f7db980e60920 (main; identical behavior on v0.82.14/v0.83.1/v0.83.2):

  1. By design — inputs are extracted to a GH_AW_INPUT_* step env and written into config.json as a shell-style placeholder:

    • pkg/workflow/secret_extraction.go maps ${{ inputs['base-branch'] }} → env GH_AW_INPUT_BASE_BRANCH + placeholder ${GH_AW_INPUT_BASE_BRANCH} (see the doc comment around L305–L346).
    • pkg/workflow/mcp_setup_generator.go#L283 writes config.json via a single-quoted heredoc (no shell expansion — intended; resolution is deferred to the reader).
    • actions/setup/js/safe_outputs_config.cjs#L20 resolves placeholders at read-time:
      return value.replace(/\$\{([A-Z_][A-Z0-9_]*)\}/g, (match, envName) => process.env[envName] ?? match);
      Note the ?? match: if the env var is absent, the literal placeholder is kept.
  2. The regression — the safe-outputs MCP server was moved into a container in Run safe-outputs MCP in the gh-aw node container #39100 (ce429bfcc3, v0.80.0). The container is launched with an explicit -e VAR allowlist built by pkg/workflow/mcp_setup_generator.go#L783 (appendMCPGatewayBaseEnvFlags) — it passes GITHUB_*, DEFAULT_BRANCH, RUNNER_TEMP, GH_AW_SAFE_OUTPUTS*, etc., but no GH_AW_INPUT_*. So inside the container process.env.GH_AW_INPUT_BASE_BRANCH is undefined, safe_outputs_config.cjs#L20 keeps the literal, and actions/setup/js/generate_git_bundle.cjs#L212 throws No remote refs available for merge-base calculation.

Before #39100 the MCP ran as an HTTP sidecar that inherited the whole step environment (including GH_AW_INPUT_BASE_BRANCH), so the substitution resolved and PRs were created — which is why v0.79.8 works and v0.80.0+ does not.

Regression bisection

  • v0.79.8: MCP = HTTP sidecar, full step env → ${GH_AW_INPUT_BASE_BRANCH} resolves → PR created. ✅
  • v0.80.0 (ce429bfcc3 / Run safe-outputs MCP in the gh-aw node container #39100 "Run safe-outputs MCP in the gh-aw node container"): MCP containerized with filtered -e allowlist omitting GH_AW_INPUT_* → placeholder unresolved → merge-base throw. ❌
  • Still reproduces on v0.83.1 and v0.83.2 (GH_AW_INPUT_* still absent from the -e list).

Implementation plan

  1. Pass GH_AW_INPUT_* into the MCP containerpkg/workflow/mcp_setup_generator.go, appendMCPGatewayBaseEnvFlags (~L783): add the input-derived env vars the safe-outputs config depends on to the -e allowlist. Prefer forwarding all GH_AW_INPUT_* that were emitted for this workflow (they're already computed by secret_extraction.go) rather than hard-coding GH_AW_INPUT_BASE_BRANCH, so allowed_base_branches / target-repo inputs are covered too.
  2. Test — extend the safe-outputs compile tests (e.g. pkg/workflow/safe_outputs_dynamic_allowed_repos_test.go, which already checks GH_AW_INPUT_BASE_BRANCH in the step env and the ${GH_AW_INPUT_BASE_BRANCH} config placeholder): add an assertion that the generated MCP container docker run command includes -e GH_AW_INPUT_BASE_BRANCH (and generally the GH_AW_INPUT_* the config references).
  3. Optional hardening — in safe_outputs_config.cjs, when a ${GH_AW_INPUT_*} placeholder cannot be resolved, emit a clear error (or core.warning) instead of silently passing the literal through to generate_git_bundle.cjs, so the failure mode is diagnosable rather than surfacing later as No remote refs available for merge-base calculation.
  4. Docs/guidelines — follow the console/error-message style guide; run make agent-finish before completing.

Notes

  • Diagnosed with the agentic-workflows dispatcher + source bisection on the local github/gh-aw clone.
  • Workaround for consumers meanwhile: hardcode a literal create-pull-request.base-branch: <branch> (compiles to a literal, needs no substitution).

Activity

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions