Skip to content

Add github-app.missing-key ignore mode and guard App token minting across workflow paths#33033

Merged
pelikhan merged 15 commits into
mainfrom
copilot/fix-conclusion-job-error
May 18, 2026
Merged

Add github-app.missing-key ignore mode and guard App token minting across workflow paths#33033
pelikhan merged 15 commits into
mainfrom
copilot/fix-conclusion-job-error

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

conclusion (and other downstream) jobs could hard-fail on fork PRs or other contexts where App secrets are unavailable, because actions/create-github-app-token was invoked unconditionally. This change adds an explicit missing-key behavior on github-app, preserving default fail-fast behavior while allowing opt-in graceful degradation.

  • Config model + schema

    • Added github-app.missing-key with supported values:
      • error (default): current behavior, fail when key inputs are empty.
      • ignore: skip minting when key inputs are empty.
    • Updated main_workflow_schema.json to document and validate the new field.
    • Normalized invalid values to error with warning logging.
  • Token minting guards (all github-app entry points)

    • Added conditional guards to GitHub App token mint steps generated for:
      • safe-outputs
      • activation
      • pre-activation (on.github-app / skip-if checks)
      • checkout/tool paths that reuse shared mint-step builders
    • Guard is implemented via step if + env indirection so empty secrets do not crash the job.
  • Fallback token resolution when minting is skipped

    • Introduced expression-composition helpers to safely build ${{ ... || ... }} chains.
    • For missing-key: ignore, token consumers now fall back to existing non-App token chains instead of requiring the minted App token.
    • Applied to safe-outputs token wiring, activation/pre-activation token resolution, checkout token wiring, and GitHub MCP token env resolution.
  • Illustrative behavior

    safe-outputs:
      github-app:
        client-id: ${{ secrets.GH_AW_APP_ID }}
        private-key: ${{ secrets.GH_AW_APP_PRIVATE_KEY }}
        missing-key: ignore

    Generated mint step is guarded, and downstream token usage resolves like:

    if: ${{ env.GH_AW_APP_CLIENT_ID != '' && env.GH_AW_APP_PRIVATE_KEY != '' }}
    github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}

Copilot AI and others added 2 commits May 18, 2026 13:39
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix conclusion job failure when app-id secret is missing Add github-app.missing-key ignore mode and guard App token minting across workflow paths May 18, 2026
Copilot AI requested a review from pelikhan May 18, 2026 13:45
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot refactor field to "ignore-if-missing: true" instead of enum.

Copilot AI and others added 2 commits May 18, 2026 13:57
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 18, 2026

@copilot refactor field to "ignore-if-missing: true" instead of enum.

Updated in eca53d2 (with a follow-up test clarification in 7742db9). The config is now github-app.ignore-if-missing: true (boolean), with parser/schema/tests refactored accordingly.

@pelikhan pelikhan marked this pull request as ready for review May 18, 2026 14:01
Copilot AI review requested due to automatic review settings May 18, 2026 14:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in mode intended to skip GitHub App token minting when App credentials are unavailable and fall back to non-App tokens.

Changes:

  • Adds a GitHub App missing-secret configuration path and token-expression fallback helpers.
  • Guards App token mint steps across safe-outputs, activation, pre-activation, checkout, and MCP paths.
  • Adds tests for safe-outputs and activation ignore-mode generation.
Show a summary per file
File Description
pkg/workflow/safe_outputs_env.go Adds fallback token wiring for safe-output token consumers.
pkg/workflow/safe_outputs_app_test.go Adds parsing/build tests for safe-output App ignore behavior.
pkg/workflow/safe_outputs_app_config.go Adds App config flag parsing and guarded token minting/fallback resolution.
pkg/workflow/mcp_environment.go Adds MCP GitHub token fallback composition when App minting is skipped.
pkg/workflow/github_token.go Adds token expression composition helpers and checkout fallback wiring.
pkg/workflow/copilot_engine_execution.go Applies MCP GitHub token fallback in Copilot execution env.
pkg/workflow/compiler_pre_activation_job.go Guards pre-activation App token minting and fallback token resolution.
pkg/workflow/checkout_step_generator.go Adds fallback token expressions for App-authenticated checkout/fetch steps.
pkg/workflow/activation_github_token_test.go Adds activation App ignore-mode generation test coverage.
pkg/parser/schemas/main_workflow_schema.json Extends GitHub App schema with the new missing-secret option.

Copilot's findings

Tip

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

Comments suppressed due to low confidence (3)

pkg/workflow/safe_outputs_app_config.go:471

  • This uses step-local env values in the same step's if condition. Those values are not available when GitHub Actions evaluates the condition, so activation App token minting is skipped even when the credentials are set, and activation falls back to the default token instead of using the configured App.
	if app.shouldIgnoreMissingKey() {
		steps = append(steps, "        if: ${{ env.GH_AW_APP_CLIENT_ID != '' && env.GH_AW_APP_PRIVATE_KEY != '' }}\n")
		steps = append(steps, "        env:\n")
		steps = append(steps, fmt.Sprintf("          GH_AW_APP_CLIENT_ID: %s\n", app.AppID))
		steps = append(steps, fmt.Sprintf("          GH_AW_APP_PRIVATE_KEY: %s\n", app.PrivateKey))

pkg/workflow/safe_outputs_app_config.go:519

  • When App minting is skipped, this fallback ignores data.ActivationGitHubToken. Workflows that configure both an activation github-token fallback and an App in ignore mode will still use only secrets.GITHUB_TOKEN, regressing cross-repo or higher-permission activation paths that already rely on on.github-token.
		if data.ActivationGitHubApp.shouldIgnoreMissingKey() {
			return combineTokenExpressions("${{ steps.activation-app-token.outputs.token }}", "${{ secrets.GITHUB_TOKEN }}")
		}

pkg/workflow/compiler_pre_activation_job.go:702

  • The ignore-mode fallback for pre-activation skip-if checks also drops data.ActivationGitHubToken. If users have an on.github-token for authenticated search queries, missing App credentials will fall back to only GITHUB_TOKEN, which can make existing cross-repo or elevated-permission skip checks fail.
		if data.ActivationGitHubApp.shouldIgnoreMissingKey() {
			return combineTokenExpressions(
				fmt.Sprintf("${{ steps.%s.outputs.token }}", constants.PreActivationAppTokenStepID),
				"${{ secrets.GITHUB_TOKEN }}",
			)
  • Files reviewed: 10/10 changed files
  • Comments generated: 6

Comment on lines +10973 to +10976
"ignore-if-missing": {
"type": "boolean",
"description": "If true, skip token minting when client-id/private-key resolve to empty strings at runtime. Defaults to false."
},
Comment thread pkg/workflow/safe_outputs_app_config.go Outdated
Comment on lines +173 to +177
if app.shouldIgnoreMissingKey() {
steps = append(steps, " if: ${{ env.GH_AW_APP_CLIENT_ID != '' && env.GH_AW_APP_PRIVATE_KEY != '' }}\n")
steps = append(steps, " env:\n")
steps = append(steps, fmt.Sprintf(" GH_AW_APP_CLIENT_ID: %s\n", app.AppID))
steps = append(steps, fmt.Sprintf(" GH_AW_APP_PRIVATE_KEY: %s\n", app.PrivateKey))
Comment on lines +658 to +662
if app.shouldIgnoreMissingKey() {
steps = append(steps, " if: ${{ env.GH_AW_APP_CLIENT_ID != '' && env.GH_AW_APP_PRIVATE_KEY != '' }}\n")
steps = append(steps, " env:\n")
steps = append(steps, fmt.Sprintf(" GH_AW_APP_CLIENT_ID: %s\n", app.AppID))
steps = append(steps, fmt.Sprintf(" GH_AW_APP_PRIVATE_KEY: %s\n", app.PrivateKey))
Comment on lines +21 to +26
AppID string `yaml:"client-id,omitempty"` // GitHub App client ID (or legacy app ID) (e.g., "${{ vars.APP_ID }}")
PrivateKey string `yaml:"private-key,omitempty"` // GitHub App private key (e.g., "${{ secrets.APP_PRIVATE_KEY }}")
IgnoreIfMissing bool `yaml:"ignore-if-missing,omitempty"` // If true, skip token minting when client-id/private-key resolve empty
Owner string `yaml:"owner,omitempty"` // Optional: owner of the GitHub App installation (defaults to current repository owner)
Repositories []string `yaml:"repositories,omitempty"` // Optional: comma or newline-separated list of repositories to grant access to
Permissions map[string]string `yaml:"permissions,omitempty"` // Optional: extra permission-* fields to merge into the minted token (nested wins over job-level)
Permissions map[string]string `yaml:"permissions,omitempty"` // Optional: extra permission-* fields to merge into the minted token (nested wins over job-level)
AppID string `yaml:"client-id,omitempty"` // GitHub App client ID (or legacy app ID) (e.g., "${{ vars.APP_ID }}")
PrivateKey string `yaml:"private-key,omitempty"` // GitHub App private key (e.g., "${{ secrets.APP_PRIVATE_KEY }}")
IgnoreIfMissing bool `yaml:"ignore-if-missing,omitempty"` // If true, skip token minting when client-id/private-key resolve empty
Comment on lines +57 to +63
// Parse ignore-if-missing behavior (optional): true to skip minting when key inputs are empty
if ignoreIfMissing, exists := appMap["ignore-if-missing"]; exists {
if ignore, ok := ignoreIfMissing.(bool); ok {
appConfig.IgnoreIfMissing = ignore
} else {
safeOutputsAppLog.Printf("Ignoring github-app.ignore-if-missing: expected boolean, got %T", ignoreIfMissing)
}
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments, update pr title and description

Copilot AI and others added 2 commits May 18, 2026 14:21
…ation

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot use expression builder to create expressions

Copilot AI and others added 3 commits May 18, 2026 14:25
…d wrappers

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 18, 2026

@copilot review all comments, update pr title and description

Addressed in decad20 (plus follow-up commits on this branch).

Updated PR title:
Fix \ignore-if-missing` GitHub App guards and preserve fallback behavior`

Updated PR description:

  • Fixes guard evaluation by removing step-local env dependencies in App token mint steps (safe-outputs, activation, pre-activation skip-if).
  • Preserves ignore-if-missing when top-level github-app fallback is propagated to tools.github.github-app.
  • Adds ignore-if-missing to on.github-app nested frontmatter filtering.
  • Adds regression tests for guard rendering, pre-activation fallback path, fallback propagation, and nested-field recognition.
  • Validated with go test ./pkg/workflow -count=1.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Comment thread pkg/workflow/github_token.go Outdated
Comment on lines +12 to +22
func unwrapGitHubExpression(expression string) string {
trimmed := strings.TrimSpace(expression)
if strings.HasPrefix(trimmed, "${{") && strings.HasSuffix(trimmed, "}}") {
return strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(trimmed, "${{"), "}}"))
}
return trimmed
}

func combineTokenExpressions(primaryExpression, fallbackExpression string) string {
return fmt.Sprintf("${{ %s || %s }}", unwrapGitHubExpression(primaryExpression), unwrapGitHubExpression(fallbackExpression))
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot review expression builder and reuse helpers

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.

Updated in b793455: token expression composition now reuses shared helpers. combineTokenExpressions uses stripExpressionWrapper + RenderCondition, and buildIgnoreIfMissingCondition also renders through RenderCondition for consistent expression-builder usage.

Copilot AI and others added 2 commits May 18, 2026 14:41
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 18, 2026

@copilot use expression builder to create expressions

Updated in f81213e (with regression coverage in 5e828c6). Guard and token expressions now use the expression builder path instead of ad-hoc string construction.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan merged commit b7208d9 into main May 18, 2026
9 of 12 checks passed
@pelikhan pelikhan deleted the copilot/fix-conclusion-job-error branch May 18, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

conclusion job's App-token mint step fails hard when GH_AW_APP_ID isn't available and previous step are canceled

3 participants