Skip to content

fix: forward GH_AW_INPUT_* to MCP container env for dynamic safe-outputs config#48099

Open
pelikhan with Copilot wants to merge 8 commits into
mainfrom
copilot/resolve-dynamic-base-branch-issue
Open

fix: forward GH_AW_INPUT_* to MCP container env for dynamic safe-outputs config#48099
pelikhan with Copilot wants to merge 8 commits into
mainfrom
copilot/resolve-dynamic-base-branch-issue

Conversation

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Since v0.80.0, the safe-outputs MCP server runs in a Docker container with a filtered -e allowlist. GH_AW_INPUT_* vars were never added to that allowlist, so ${GH_AW_INPUT_BASE_BRANCH}-style placeholders in config.json remain unresolved inside the container — causing create_pull_request to fail with No remote refs available for merge-base calculation when using any dynamic safe-outputs field like base-branch: ${{ inputs.base_branch }}.

Changes

pkg/workflow/mcp_setup_generator.go

  • New extractSafeOutputsInputEnvVars(safeOutputConfig) — extracts all GH_AW_INPUT_* name→expression pairs referenced by the safe-outputs config and passes them to generateMCPGatewaySetup.

pkg/workflow/mcp_setup_gateway.go

  • writeMCPGatewayStepEnv now also emits GH_AW_INPUT_*: ${{ inputs.* }} in the Start MCP Gateway step env: block, so the runner process holds the values when docker run is invoked.
  • New appendMCPGatewaySafeOutputsInputEnvFlags appends -e GH_AW_INPUT_* to the docker run command so the container inherits those values.

The compiled output now looks like:

# Start MCP Gateway step
env:
  ...
  GH_AW_INPUT_BASE_BRANCH: ${{ inputs.base_branch }}   # ← new

# docker run command
... -e GH_AW_INPUT_BASE_BRANCH ...                     # ← new

actions/setup/js/safe_outputs_config.cjs

  • New collectUnresolvedInputPlaceholders() detects and logs any ${GH_AW_INPUT_*} that remains unresolved at load-time, so failures surface with a clear message instead of a cryptic merge-base error.

pkg/workflow/safe_outputs_dynamic_allowed_repos_test.go

  • Existing test updated: asserts GH_AW_INPUT_* appears in both the Generate Safe Outputs Config and Start MCP Gateway step env blocks, and that -e GH_AW_INPUT_* is present in the docker run command.
  • New TestSafeOutputsDynamicBaseBranchPassedToMCPContainer regression test for the exact issue scenario (base-branch: ${{ inputs.base_branch }}).

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 22.4 AIC · ⌖ 9.38 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 26, 2026 03:31
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…uts config

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix dynamic base-branch unresolved for safe-outputs fix: forward GH_AW_INPUT_* to MCP container env for dynamic safe-outputs config Jul 26, 2026
Copilot AI requested a review from pelikhan July 26, 2026 03:49
@pelikhan
pelikhan marked this pull request as ready for review July 26, 2026 03:49
Copilot AI review requested due to automatic review settings July 26, 2026 03:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes dynamic safe-output inputs by forwarding GH_AW_INPUT_* variables toward the MCP runtime and improving unresolved-placeholder diagnostics.

Changes:

  • Extracts and forwards input-derived safe-output environment variables.
  • Adds runtime diagnostics and regression coverage.
  • Updates release metadata and workflow skill references.
Show a summary per file
File Description
pkg/workflow/mcp_setup_generator.go Extracts safe-output input variables.
pkg/workflow/mcp_setup_gateway.go Forwards variables to the outer gateway container.
actions/setup/js/safe_outputs_config.cjs Logs unresolved input placeholders.
pkg/workflow/safe_outputs_dynamic_allowed_repos_test.go Adds compilation regression assertions.
.github/skills/agentic-workflows/SKILL.md Adds the release-workflow reference.
.changeset/fix-safe-outputs-dynamic-input-mcp-container.md Documents the patch.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Medium

containerCmd.WriteString(" -v ${DOCKER_SOCK_PATH}:/var/run/docker.sock")
appendMCPGatewayBaseEnvFlags(&containerCmd, payloadPathPrefix)
appendMCPGatewayConditionalEnvFlags(&containerCmd, workflowData, engine, hasGitHub, githubTool, tools)
appendMCPGatewaySafeOutputsInputEnvFlags(&containerCmd, safeOutputsInputEnvVars)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was addressed in commit 276a6bd. Both safe-outputs renderer formats now dynamically append GH_AW_INPUT_* entries:

  • TOML (renderSafeOutputsTOML): appends names from workflowData.SafeOutputsInputEnvVars to the env_vars list (see mcp_renderer_builtin.go:112-114)
  • JSON (renderSafeOutputsMCPConfigWithOptions): appends {name, name, false} entries to the envVars slice (see mcp_renderer_builtin.go:297-305)

The nested safe-outputs server container now receives the GH_AW_INPUT_* values and can resolve ${GH_AW_INPUT_…} placeholders at runtime.

Comment on lines +73 to +79
const unresolvedInputs = collectUnresolvedInputPlaceholders(configFileContent);
if (unresolvedInputs.length > 0) {
const varList = unresolvedInputs.join(", ");
server.debug(
`ERR_CONFIG: Unresolved workflow input placeholder(s) in safe-outputs config: ${varList}. The values were not passed to the MCP container. Verify that the workflow was compiled with a version that forwards GH_AW_INPUT_* to the container env.`
);
console.error(`[safe_outputs_config] ERR_CONFIG: Unresolved workflow input placeholder(s): ${varList}`);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage was added in commit ec6280d. The test file now includes two cases for collectUnresolvedInputPlaceholders:

  • Unresolved + duplicated: GH_AW_INPUT_FOO appears twice but the env var is not set — verifies exactly one console.error and one server.error call (deduplication check)
  • Resolved: GH_AW_INPUT_BASE_BRANCH is set in the env — verifies no diagnostic is emitted

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 3 test(s): 3 design, 0 implementation, 0 violation(s).

📊 Metrics (3 tests)
Metric Value
Analyzed 3 (Go: 3, JS: 0)
✅ Design 3 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 3 (100%)
Duplicate clusters 0
Inflation No
🚨 Violations 0
Test File Classification Issues
TestSafeOutputsConfigUsesWorkflowInputEnvVarsForDynamicAllowedRepos safe_outputs_dynamic_allowed_repos_test.go design_test / behavioral_contract None
TestSafeOutputsConfigPreservesSecretPlaceholdersOnDisk safe_outputs_dynamic_allowed_repos_test.go design_test / behavioral_contract None
TestSafeOutputsDynamicBaseBranchPassedToMCPContainer safe_outputs_dynamic_allowed_repos_test.go design_test / behavioral_contract None

Verdict

passed. 0% implementation tests (threshold: 30%). All three tests enforce end-to-end behavioral contracts: correct env var injection into compiled YAML, single-quoted heredoc quoting to prevent shell expansion, and -e forwarding to the MCP gateway container. Each test includes both positive and negative regex assertions with descriptive failure messages.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 22.3 AIC · ⌖ 7.9 AIC · ⊞ 8.1K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 100/100. 0% implementation tests (threshold: 30%).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is correct and well-implemented. The root cause (GH_AW_INPUT_* vars missing from the docker -e allowlist) is clearly identified and addressed at both the compiler level (Go) and with appropriate diagnostic logging (JS). Tests are updated and a focused regression test added.

Two pre-existing review comments cover the remaining gaps:

  • Nested-container forwarding (inner containers do not inherit -e vars from the outer MCP gateway)
  • Missing JS unit-test coverage for the new unresolved-placeholder warning path

No additional blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 21.4 AIC · ⌖ 4.51 AIC · ⊞ 5K

@github-actions

Copy link
Copy Markdown
Contributor

Design Decision Gate - ADR Required

This PR makes significant changes to core business logic (144 new lines in pkg/workflow/ and actions/setup/js/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/48099-forward-gh-aw-input-vars-to-mcp-gateway-container.md -- review and complete it before merging.

This PR cannot merge until an ADR is linked in the PR body.

What to do next
  1. Review the draft ADR committed to your branch -- it was generated from the PR diff

  2. Complete the missing sections -- add context the AI could not infer, refine the decision rationale, and list real alternatives you considered

  3. Commit the finalized ADR to docs/adr/ on your branch

  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-48099: Forward GH_AW_INPUT_* Vars to MCP Gateway Container

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context -- What is the problem? What forces are at play?
  • Decision -- What did you decide? Why?
  • Alternatives Considered -- What else could have been done?
  • Consequences -- What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 56.8 AIC · ⌖ 13 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — one medium correctness issue must be fixed before merging.

Blocking issue

The new collectUnresolvedInputPlaceholders diagnostic in safe_outputs_config.cjs only logs and continues. Since resolveEnvPlaceholders uses ?? match as its fallback, an unresolved ${GH_AW_INPUT_BASE_BRANCH} is preserved verbatim in the parsed config. The safe-outputs MCP server then receives the literal placeholder as the base_branch value — the same broken behavior the PR is fixing — just with a warning attached. The check needs to throw after logging so the failure is explicit rather than silent.

Other observations (non-blocking)
  • The Go changes are correct: -e VARNAME (without =value) is the standard Docker pattern for inheriting a value from the host process environment, and the step env: block supplies that value on the runner. The approach is sound.
  • extractSafeOutputsInputEnvVars correctly filters to GH_AW_INPUT_* keys; the nil-on-empty return is consistent with the rest of the codebase.
  • The regression test TestSafeOutputsDynamicBaseBranchPassedToMCPContainer is well-structured and covers the exact failure scenario.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 · 52.1 AIC · ⌖ 4.74 AIC · ⊞ 5.7K
Comment /review to run again

// Warn about any GH_AW_INPUT_* placeholders that cannot be resolved before substitution.
// This should not happen when the workflow is compiled correctly (the compiler ensures these
// env vars are in the MCP gateway step env and the docker -e allowlist), but if it does the
// error will surface later as a cryptic "No remote refs available for merge-base calculation"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent continuation after detecting unresolved placeholders: logging the error but not halting means resolveEnvPlaceholders will still embed the literal ${GH_AW_INPUT_*} string as the field value — reproducing the exact broken behavior this code is meant to diagnose.

💡 Suggested fix

resolveEnvPlaceholders falls back via ?? match, so when the env var is absent it preserves the raw ${GH_AW_INPUT_BASE_BRANCH} string in the config. The safe-outputs MCP server then receives that literal placeholder as base_branch, which causes the "No remote refs" error — same as before, just with a warning in the log.

The diagnostic is useful but the code should also throw:

if (unresolvedInputs.length > 0) {
  const varList = unresolvedInputs.join(", ");
  const msg = `ERR_CONFIG: Unresolved workflow input placeholder(s) in safe-outputs config: ${varList}. The values were not passed to the MCP container.`;
  server.debug(msg);
  console.error(`[safe_outputs_config] ${msg}`);
  throw new Error(msg); // surface the root cause immediately
}

This makes failures loud and attributable rather than silently producing a wrong config.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 40cc205. The unresolved placeholder detection now throws before calling resolveEnvPlaceholders, so the literal ${GH_AW_INPUT_*} string is never embedded as a config value. The throw is caught by the outer try-catch which falls back to an empty config — the MCP server starts with no tools enabled rather than silently using a broken config that produces a cryptic downstream error.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — the fix is well-targeted and accompanied by a solid regression test. One issue worth addressing before merge.

📋 Key Themes & Highlights

Key Themes

  • Diagnostic severity: the new collectUnresolvedInputPlaceholders warning is emitted at server.debug level, which is suppressed in normal runs — it won't help anyone diagnose a misconfigured deployment where this code path is actually reached
  • Test coverage gap (noted in existing review): safe_outputs_config.test.cjs has no coverage for the new collectUnresolvedInputPlaceholders path
  • Container scope concern (noted in existing review): the MCP gateway container forwarding only helps the outer gateway; worth confirming the safe-outputs server actually reads env vars from the container context it runs in

Positive Highlights

  • ✅ Clean extraction of extractSafeOutputsInputEnvVars — single responsibility, easy to test
  • ✅ Regression test TestSafeOutputsDynamicBaseBranchPassedToMCPContainer covers the exact failure scenario end-to-end
  • ✅ Updated test assertions now verify both step env AND docker -e flags — the right invariants to protect
  • ✅ Good inline comments explaining why each piece is necessary
  • ✅ Changeset entry accurately describes the impact

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 30.8 AIC · ⌖ 4.7 AIC · ⊞ 6.7K
Comment /matt to run again

// error will surface later as a cryptic "No remote refs available for merge-base calculation"
// rather than pointing at the root cause.
const unresolvedInputs = collectUnresolvedInputPlaceholders(configFileContent);
if (unresolvedInputs.length > 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] The unresolved-placeholder diagnostic is emitted at server.debug level, which is suppressed in normal runs — defeating the goal of surfacing failures with a clear message instead of a cryptic error.

💡 Suggested fix

Change server.debug to server.error (or at minimum server.warn) so the message appears unconditionally:

server.error(
  `ERR_CONFIG: Unresolved workflow input placeholder(s) in safe-outputs config: ${varList}. The values were not passed to the MCP container.`
);

The console.error call on the next line is a good fallback, but server.error/server.warn matches the structured logging used elsewhere in this module.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 40cc205. Changed server.debugserver.error so the unresolved placeholder diagnostic is emitted unconditionally (not suppressed by debug log filtering). Both server.error and console.error are called before throwing, ensuring the root cause is visible in the MCP server logs and on stderr.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage — §30192186368

Field Value
Category bug
Risk 🔴 high
Score 82 / 100 (impact 48 + urgency 22 + quality 12)
Action ⚡ fast_track

Why: MCP container env vars — GH_AW_INPUT_* — were never forwarded to the Docker container, silently breaking all dynamic safe-outputs fields (e.g. base-branch: ${{ inputs.base_branch }}). High-impact correctness bug affecting every workflow using dynamic safe-outputs config. CI status unknown; awaits full run.

Next: Expedite human review. Verify CI passes before merge.

Generated by 🔧 PR Triage Agent · sonnet46 · 48.8 AIC · ⌖ 8.75 AIC · ⊞ 5.7K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the blocking review feedback, and confirm the ADR/body requirements are fully handled before maintainers re-check merge readiness.

Unresolved review feedback:

Please make the next actionable blocker explicit in your reply.

Generated by 👨🍳 PR Sous Chef · gpt54 ·
Comment /souschef to run again

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 21.7 AIC · ⌖ 8.89 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 26, 2026 09:54
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…est.cjs

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address the blocking review feedback, and confirm the ADR/body requirements are fully handled before maintainers re-check merge readiness.

Unresolved revi...

Addressed the blocking correctness feedback. The previous fix only forwarded GH_AW_INPUT_* to the outer MCP gateway container — the nested safe-outputs server container was never receiving them because its env_vars/env allowlist in mcp_renderer_builtin.go was fixed and excluded these names.

Changes in this update:

  • Added SafeOutputsInputEnvVars map[string]string to WorkflowData; set it in generateMCPSetup so all downstream renderers can access it
  • renderSafeOutputsTOML: dynamically appends GH_AW_INPUT_* names to the TOML env_vars list → nested container now receives the values
  • renderSafeOutputsMCPConfigWithOptions: appends GH_AW_INPUT_* as "\${VAR}"-style entries to the JSON env block → same fix for JSON engines
  • Updated Go tests to assert the vars appear in the nested container config, not just the outer gateway command
  • Added safe_outputs_config.test.cjs tests for the collectUnresolvedInputPlaceholders diagnostic path (unresolved+deduplication, and resolved emits nothing)

The code reviewer flagged using name vs workflowData.SafeOutputsInputEnvVars[name] for the value field — this is intentional. The value field feeds the \${VAR_NAME} shell template in the JSON env block; the gateway resolves it from its own env at runtime. Using the GitHub Actions expression ${{ inputs.base_branch }} there would be incorrect.

Copilot AI requested a review from gh-aw-bot July 26, 2026 09:57
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved blocking review threads, and confirm whether any ADR/body follow-up is still required before maintainers re-check merge readiness.

Unresolved review feedback:

Please make the next actionable blocker explicit in your reply.

Generated by 👨🍳 PR Sous Chef
Comment /souschef to run again

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 22.4 AIC · ⌖ 9.38 AIC · ⊞ 7.1K ·
Comment /souschef to run again

…olders

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants