Skip to content

feat: support env field in shared imports#26113

Merged
pelikhan merged 8 commits intomainfrom
copilot/support-env-field-in-shared-imports
Apr 14, 2026
Merged

feat: support env field in shared imports#26113
pelikhan merged 8 commits intomainfrom
copilot/support-env-field-in-shared-imports

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

The env field was explicitly forbidden in shared workflow files, causing it to be silently ignored with a warning. This adds full support for env in shared imports, merging imported env vars into the compiled lock file with main workflow values taking precedence.

Changes

  • pkg/constants/constants.go — Remove "env" from SharedWorkflowForbiddenFields
  • pkg/parser/import_processor.go — Add MergedEnv string and MergedEnvSources map[string]string to ImportsResult
  • pkg/parser/import_field_extractor.go — Extract env field from each imported file; detect and error on import-import conflicts
  • pkg/parser/include_processor.go — Add "env" to the valid non-workflow frontmatter fields (suppresses spurious warning)
  • pkg/workflow/imports.go — Add mergeEnv(): main workflow env vars override imports; conflicts between imports are caught before merging
  • pkg/workflow/compiler_types.go — Add EnvSources map[string]string to WorkflowData for source attribution
  • pkg/workflow/compiler_orchestrator_workflow.go — Wire up env merging and source attribution after YAML section extraction
  • pkg/workflow/compiler_yaml.go — Add frontmatter env vars listing to lock file header with source attribution
  • docs/src/content/docs/reference/imports.md — Document env: as an allowed import field with merge semantics

Merge Semantics

  • Main workflow takes precedence: main workflow env vars override any imported value, no error raised
  • Import-import conflicts are errors: if two imports define the same env var, compilation fails with an actionable message — only the main workflow may override imported env vars
  • Lock file header lists frontmatter env vars with their source ((main workflow) or the import file path); programmatically-injected vars (e.g. OTEL_*) are not listed

Example

# shared/target.md
---
env:
  TARGET_REPOSITORY: owner/repo
  SHARED_CONFIG: shared-value
---
# my-workflow.md
---
on: issues
engine: copilot
env:
  TARGET_REPOSITORY: main-override  # takes precedence
imports:
  - shared/target.md
---

Compiled env block:

env:
  SHARED_CONFIG: shared-value      # merged from import
  TARGET_REPOSITORY: main-override # main workflow wins

Lock file header:

# Frontmatter env variables:
#   - SHARED_CONFIG: shared/target.md
#   - TARGET_REPOSITORY: (main workflow)

If two imports define the same env var, compilation fails:

env variable "SHARED_KEY" is defined in multiple imports: "shared/first.md" and "shared/second.md";
remove the duplicate definition from one of the imports, or move it to the main workflow to override imported values

Copilot AI linked an issue Apr 14, 2026 that may be closed by this pull request
Copilot AI and others added 2 commits April 14, 2026 00:25
Copilot AI changed the title [WIP] Add support for env field in shared import files feat: support env field in shared imports Apr 14, 2026
Copilot AI requested a review from pelikhan April 14, 2026 00:27
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot lock file header should list the env variable and their source (main vs imported). If an imported works flows tries to override another env, raise error. Only main workflow is allowed to override env

…on in lock file header

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d60942a3-f7ad-40af-99a5-dad4c23d4919

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan marked this pull request as ready for review April 14, 2026 00:49
Copilot AI review requested due to automatic review settings April 14, 2026 00:49
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 14, 2026

@copilot lock file header should list the env variable and their source (main vs imported). If an imported works flows tries to override another env, raise error. Only main workflow is allowed to over...

Done in commit f35d77d:

  1. Lock file header now lists all env variables with their source:

    # Env variables:
    #   - SHARED_CONFIG: .github/shared/target.md
    #   - TARGET_REPOSITORY: (main workflow)
    
  2. Import-import conflicts raise an error — if two imported workflows both define the same env var, compilation fails with an actionable message:

    env variable "SHARED_KEY" is defined in multiple imports: "shared/first.md" and "shared/second.md"; remove the duplicate definition from one of the imports, or move it to the main workflow to override imported values
    
  3. Only the main workflow may override — the main workflow's env takes precedence over any imported value, no error raised.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw -buildtags /usr/bin/git git rev-�� --show-toplevel git /usr/bin/git /tmp/test-env-imnode rev-parse /home/REDACTED/worprettier git (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw -buildtags /usr/bin/git git rev-�� --show-toplevel git /usr/bin/git /tmp/test-env-imnode remote /home/REDACTED/.loprettier git (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha -t security /usr/bin/git OUTPUT -d 168.63.129.16 git rev-�� --show-toplevel conntrack 1/x64/bin/node INVALID,NEW -j DROP 1/x64/bin/node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha HEAD git /usr/bin/git te &#39;**/*.cjs&#39; &#39;*git git rgo/bin/sh git rev-�� --show-toplevel node /usr/bin/git ithub/workflows git /opt/hostedtoolc--show-toplevel git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha se 246794/b119/vet.cfg ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha xterm-color nflict detection between imports and source attr-j 1/x64/bin/node --show-toplevel git 1/x64/bin/node infocmp -has�� vaScript926824183/001/test-inlined-imports-enabled-with-env-template-expressions-in-body.md git cfg --show-toplevel git /usr/bin/git /usr/bin/gh (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha } 64/pkg/tool/linux_amd64/vet /usr/bin/git get cfg 64/pkg/tool/linu--show-toplevel git rev-�� --show-toplevel 64/pkg/tool/linux_amd64/vet /usr/bin/git --local user.name ache/go/1.25.8/x--show-toplevel git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha --show-toplevel x_amd64/vet /opt/hostedtoolcache/node/24.14.1/x64/bin/node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha --show-toplevel node /opt/hostedtoolcache/node/24.14.1/x64/bin/node aml_helpers_testcut **/*.cjs /home/node_modules/.bin/sh node /tmp�� /home/REDACTED/work/gh-aw/gh-aw/.github/workflows/api-consumption-report.md sh /usr/bin/git k/gh-aw/gh-aw/.ggit git me: String!) { --show-toplevel git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v9
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq .object.sha -bool -buildtags ache/node/24.14.1/x64/bin/node -errorsas -ifaceassert -nilfunc git t-25�� bility_SameInputSameOutput4274912835/001/stability-test.md remote.origin.url /opt/hostedtoolcache/node/24.14.1/x64/bin/bash (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq .object.sha sistency_GoAndJavaScript640014931/001/test-inlined-imports-enabled-with-body-content.md -buildtags /usr/local/.ghcup/bin/bash -errorsas -ifaceassert -d bash --no�� --noprofile -tests /usr/bin/gcc (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq .object.sha ckSm/n7IxLYVN3Za8AzFHckSm -buildtags ache/node/24.14.1/x64/bin/node -errorsas -ifaceassert -nilfunc 999272/b373/importcfg t-19�� sistency_WithImports3720060048/001/main.md k/gh-aw/gh-aw/pkg/constants/engine_constants.go /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha -bool -buildtags /home/REDACTED/.cargo/bin/bash -errorsas -ifaceassert -nilfunc bash --no�� --noprofile -tests /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/asm (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha 2ea60061ce15cbc4cc3a7bc596c3a0469437ec0e:.github/workflows/contribution-check.lock.yml git /usr/sbin/git --show-toplevel /opt/hostedtoolcrev-parse .lock.yml git show�� 2ea60061ce15cbc4cc3a7bc596c3a0469437ec0e:pkg/workflow/compiler_yaml.go git /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/cgo go git repository(owne--show-toplevel /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/cgo (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha -bool -buildtags /home/REDACTED/.local/bin/bash -errorsas -ifaceassert -nilfunc bash --no�� --noprofile -tests /usr/bin/tail (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha 2ea60061ce15cbc4cc3a7bc596c3a0469437ec0e:.github/workflows/contribution-check.lock.yml git /usr/local/sbin/git --show-toplevel git son git show�� 2ea60061ce15cbc4cc3a7bc596c3a0469437ec0e:pkg/workflow/compiler_yaml.go git /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile go git /opt/hostedtoolc--show-toplevel /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha --local pull.rebase ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha 9437ec0e:pkg/parser/import_field_extractor_test.go rev-parse sh --show-toplevel git _modules/.bin/sh--show-toplevel git -C /home/REDACTED/work/gh-aw/gh-aw show 1/x64/bin/node on&#39; --ignore-pat/opt/hostedtoolcache/node/24.14.1/x64/bin/node git r: $owner, name:github.repository e/git-remote-https (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha ed-imports-enabled-with-env-template-expressions-in-body.md (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha --get-regexp --global x_amd64/link (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha h ../../../.prettierignore /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile /usr/bin/git sistency_Inlinednode -tests /usr/bin/git git rev-�� w/js/**/*.json&#39; --ignore-path git /usr/bin/git --show-toplevel (http block)
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha h ../../../.prettierignore /tmp/go-build632846902/b001/workflow.test /usr/bin/git it/ref/tags/v7 -test.timeout=10/home/REDACTED/work/gh-aw/gh-aw/actions/setup/js/node_modules/.bin/pre�� /opt/hostedtoolc--write git rev-�� w/js/**/*.json&#39; --ignore-path node /usr/bin/git /tmp/TestHashConinfocmp (http block)
  • https://api.github.com/repos/docker/build-push-action/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/docker/build-push-action/git/ref/tags/v7 --jq .object.sha th .prettierigno-f git _modules/.bin/no-f --show-toplevel x_amd64/vet /usr/bin/git git ode_�� --show-toplevel git de_modules/.bin/-f --show-toplevel x_amd64/vet /usr/bin/git git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/docker/build-push-action/git/ref/tags/v7 --jq .object.sha th .prettierignore --log-level=error git 1/x64/bin/node 0/001/go/1.25.0/git x_amd64/vet /usr/bin/git git ode_�� --show-toplevel git /usr/bin/git --show-toplevel x_amd64/vet /usr/bin/git git (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0.1.2
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq .object.sha -bool -buildtags /usr/bin/git -errorsas -ifaceassert -nilfunc git rev-�� --show-toplevel -tests 1/x64/bin/node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq .object.sha 2ea60061ce15cbc4cc3a7bc596c3a0469437ec0e:.github/workflows/contribution-check.lock.yml git /opt/hostedtoolcache/go/1.25.8/x64/bin/git *.json&#39; &#39;!../../git git bin/node git show�� 2ea60061ce15cbc4cc3a7bc596c3a0469437ec0e:pkg/workflow/compiler_yaml.go git /home/REDACTED/.cargo/bin/bash ithub/workflows git es/.bin/sh bash (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha --local committer.name ndor/bin/bash (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha 9437ec0e:pkg/parser/import_processor.go show .1/x64/codeql/tools/linux64/java/lib/jspawnhelper --show-toplevel 1/x64/bin/node 1/x64/bin/node .1/x64/codeql/tools/linux64/java/lib/jspawnhelper -C rd config /usr/bin/git remote.origin.urgit git $name) { has--show-toplevel git (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha --local committer.name ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha 9437ec0e:pkg/parser/import_processor.go -f .1/x64/codeql/tools/linux64/java/lib/jspawnhelper l owner=github DiscussionsEnabled } } .1/x64/codeql/tools/linux64/java/lib/jspawnhelper -C rd show p/bin/git on&#39; --ignore-patgit git es/.bin/node git (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha --show-toplevel (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha --show-toplevel sh /usr/bin/git _extractor.go git 64/bin/node git rev-�� --show-toplevel sh /usr/bin/git v_test.go git /home/REDACTED/worgit-upload-pack &#39;origin&#39; git (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha mit} (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha rite &#39;../../../**/*.json&#39; &#39;!../../../pkg/workflo-f git me: String!) { repository(owner: $owner, name:-f --show-toplevel 64/pkg/tool/linushow ed } } ache/go/1.25.8/x64/pkg/tool/linurev-parse js/f�� ithub/workflows git (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/-
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/- --jq .object.sha js/**/*.json&#39; --ignore-path ../../../.prettierignore --auto /usr/bin/git --detach (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha ACCEPT (http block)
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha rite &#39;../../../**/*.json&#39; &#39;!../.remote.origin.url git repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } --show-toplevel 64/pkg/tool/linushow /usr/bin/git node js/f�� ithub/workflows git /home/REDACTED/work/gh-aw/gh-aw/actions/setup/node_modules/.bin/sh ./../.prettieriggit 1XIqsXqk3EbPwP-Qrev-parse ed } } sh (http block)

If you need me to access, download, or install something from one of these locations, you can either:

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 first-class support for env: in shared imported workflows, merges imported env vars into compiled output with main-workflow precedence, and surfaces env provenance in lock file headers.

Changes:

  • Allow and extract env: from shared imports, carrying merged env + per-key source attribution through the import pipeline.
  • Merge imported env into the workflow’s env: block during compilation and emit an “Env variables” section in the generated lock header.
  • Update docs and add unit tests validating env extraction/merging and header rendering.
Show a summary per file
File Description
pkg/constants/constants.go Removes env from the shared-workflow forbidden field list.
pkg/parser/import_processor.go Extends ImportsResult to include merged env data and env source attribution.
pkg/parser/import_field_extractor.go Extracts env from imports into an accumulator + tracks per-key sources.
pkg/parser/include_processor.go Treats env as a valid relaxed frontmatter field (suppresses warnings).
pkg/parser/import_field_extractor_test.go Adds tests for env extraction and import/import env-key conflict behavior.
pkg/workflow/imports.go Adds mergeEnv to combine imported env JSON with top-level env.
pkg/workflow/compiler_orchestrator_workflow.go Wires env merging and populates WorkflowData.EnvSources.
pkg/workflow/compiler_types.go Adds EnvSources to WorkflowData for header rendering.
pkg/workflow/compiler_yaml.go Emits a lock-header “Env variables” section based on EnvSources.
pkg/workflow/compiler_yaml_helpers_test.go Tests deterministic header output including env sources.
pkg/workflow/imports_env_test.go Adds unit tests for mergeEnv.
docs/src/content/docs/reference/imports.md Documents env: as an allowed import field and its merge semantics.
.github/workflows/stale-repo-identifier.lock.yml Regenerated lock header to include env variables section.
.github/workflows/contribution-check.lock.yml Regenerated lock header to include env variables section.

Copilot's findings

Tip

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

  • Files reviewed: 14/14 changed files
  • Comments generated: 6

Comment on lines +247 to +289
// TestEnvFieldConflictBetweenImports verifies that defining the same env var in two different
// imports produces a compilation error.
func TestEnvFieldConflictBetweenImports(t *testing.T) {
tmpDir := t.TempDir()

sharedDir := filepath.Join(tmpDir, "shared")
require.NoError(t, os.MkdirAll(sharedDir, 0755), "Failed to create shared dir")

// First import defines SHARED_KEY
require.NoError(t, os.WriteFile(filepath.Join(sharedDir, "first.md"), []byte(`---
env:
SHARED_KEY: value-from-first
---

# First shared workflow
`), 0644))

// Second import also defines SHARED_KEY (conflict)
require.NoError(t, os.WriteFile(filepath.Join(sharedDir, "second.md"), []byte(`---
env:
SHARED_KEY: value-from-second
---

# Second shared workflow
`), 0644))

mainContent := `---
name: Main Workflow
on: issue_comment
imports:
- shared/first.md
- shared/second.md
---

# Main Workflow
`
result, err := ExtractFrontmatterFromContent(mainContent)
require.NoError(t, err, "ExtractFrontmatterFromContent should succeed")

_, err = ProcessImportsFromFrontmatterWithSource(result.Frontmatter, tmpDir, nil, "", "")
require.Error(t, err, "Should error when two imports define the same env var")
assert.Contains(t, err.Error(), "SHARED_KEY", "Error should mention the conflicting variable name")
}
Comment thread pkg/workflow/imports.go Outdated
Comment on lines +698 to +703
// For conflicts between imports, later imports take precedence.
func mergeEnv(topEnv map[string]any, importedEnvJSON string) (map[string]any, error) {
importsLog.Printf("Merging env: topEnv=%d", len(topEnv))
result := make(map[string]any)

// Merge imported env vars first (newline-separated JSON objects, later overrides earlier)
| `steps:` | Imported steps prepended to main; concatenated in import order. |
| `jobs:` | Not merged — define only in the main workflow. Use `safe-outputs.jobs` for importable jobs. |
| `safe-outputs.jobs` | Names must be unique; duplicates fail. Order determined by `needs:` dependencies. |
| `env:` | Main workflow env vars take precedence. Imported values fill in unspecified keys. Later imports override earlier for conflicts. |
Comment on lines +52 to +55
// Two imports with distinct keys — import-to-import conflicts are caught earlier in the
// importAccumulator before mergeEnv is called (see TestEnvFieldConflictBetweenImports in
// pkg/parser/import_field_extractor_test.go). The JSON passed to mergeEnv is therefore
// guaranteed to have no duplicate keys across different import lines.
Comment thread pkg/workflow/compiler_yaml.go Outdated
Comment on lines +196 to +209
// Add env vars listing with source attribution when any env var is present
if len(data.EnvSources) > 0 {
yaml.WriteString("#\n")
yaml.WriteString("# Env variables:\n")
// Sort keys for deterministic output
keys := make([]string, 0, len(data.EnvSources))
for k := range data.EnvSources {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Fprintf(yaml, "# - %s: %s\n", k, data.EnvSources[k])
}
}
Comment on lines +346 to +354
// Conflicts between two imports are disallowed — only the main workflow may override imported vars.
envContent, err := extractFieldJSONFromMap(fm, "env", "{}")
if err == nil && envContent != "" && envContent != "{}" {
var envMap map[string]any
if jsonErr := json.Unmarshal([]byte(envContent), &envMap); jsonErr == nil {
for key := range envMap {
if existingSource, exists := acc.envSources[key]; exists {
return fmt.Errorf("env variable %q is defined in multiple imports: %q and %q; remove the duplicate definition from one of the imports, or move it to the main workflow to override imported values", key, existingSource, item.importPath)
}
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 77/100

⚠️ Acceptable, with suggestions

Metric Value
New/modified tests analyzed 9
✅ Design tests (behavioral contracts) 9 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 5 (56%)
Duplicate test clusters 0
Test inflation detected Yes (2 files exceed 2:1 ratio — see note)
🚨 Coding-guideline violations None

Test Classification Details

All 9 tests analyzed
Test File Classification Issues Detected
TestEnvFieldExtractedFromMdImport pkg/parser/import_field_extractor_test.go ✅ Design Happy path only; no edge cases for the extractor itself
TestEnvFieldConflictBetweenImports pkg/parser/import_field_extractor_test.go ✅ Design Error case covered ✅
TestGenerateWorkflowHeader (+new row) pkg/workflow/compiler_yaml_helpers_test.go ✅ Design None
TestMergeEnvWithNoImports pkg/workflow/imports_env_test.go ✅ Design Edge case (empty) ✅
TestMergeEnvWithImportedEnvVars pkg/workflow/imports_env_test.go ✅ Design Happy path
TestMergeEnvTopLevelTakesPrecedence pkg/workflow/imports_env_test.go ✅ Design Override edge case ✅
TestMergeEnvWithMultipleImports pkg/workflow/imports_env_test.go ✅ Design Multi-import scenario ✅
TestMergeEnvWithNilTopLevel pkg/workflow/imports_env_test.go ✅ Design Nil input edge case ✅
TestMergeEnvWithInvalidJSON pkg/workflow/imports_env_test.go ✅ Design Error case ✅

Test Inflation Note

Two test files show a line-ratio above the 2:1 threshold versus their primary corresponding production file:

  • import_field_extractor_test.go (+86) vs import_field_extractor.go (+23) → ratio 3.7:1
  • imports_env_test.go (+81) vs imports.go (+32) → ratio 2.5:1

i️ This is likely a false positive. The feature spans multiple production files (import_processor.go, imports.go, compiler_yaml.go, compiler_orchestrator_workflow.go), so the total production change is significantly larger than any single file. No actual test inflation is observed — the tests are well-targeted and non-redundant.


Suggestions (non-blocking)

TestEnvFieldExtractedFromMdImport

Suggestion: Consider adding a sub-case where the imported file has an empty env: block (env: {}). This would verify the extractor doesn't treat an empty env as an error and that MergedEnv is not populated in that case.

TestMergeEnvWithImportedEnvVars / TestMergeEnvWithMultipleImports

Suggestion: A test where the same key appears in two JSON lines passed to mergeEnv (last-writer-wins vs. first-writer-wins) would document whether the within-JSON-stream merge is intentionally ordered. The comment in TestMergeEnvWithMultipleImports explains this is guaranteed not to happen, but a brief comment in mergeEnv itself or a t.Skipf note would make the invariant more visible.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 9 tests — all unit (//go:build !integration) ✅

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All 9 tests verify observable behavioral contracts. Build tags are present on all new/modified test files. No mock libraries used.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

🧪 Test quality analysis by Test Quality Sentinel · ● 694.7K ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 77/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%). All 9 new/modified tests verify behavioral contracts. Build tags present on all files. No mock-library violations.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot conflicting imports are errors, only main can override.

Review all comments.

Generated by the Design Decision Gate workflow to capture the architectural
decisions behind lifting the env restriction from shared workflow imports,
the conflict-error semantics for import-import collisions, and the lock file
header source-attribution mechanism.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Commit pushed: a51e3b2

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (317 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

AI has analyzed the PR diff and generated a draft ADR to help you get started:

📄 Draft ADR: docs/adr/26113-env-field-support-in-shared-workflow-imports.md

The draft covers the three key design decisions captured in this PR:

  • Lifting the env restriction from shared workflow imports (vs. keeping it forbidden or using silent merge)
  • Conflict-error semantics for import-import collisions (vs. last-write-wins or first-write-wins)
  • Source attribution in lock file headers (vs. merging without traceability)

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 — verify the alternatives list reflects options you actually considered, refine the decision rationale, and add any context the AI couldn't infer
  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-26113: env Field Support in Shared Workflow Imports

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

Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

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 (e.g., 26113-env-field-support-in-shared-workflow-imports.md for PR #26113).

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

Note

🔒 Integrity filter blocked 1 item

The following item were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · ● 185.2K ·

… for env imports

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/a430bbb1-ce07-4954-acfe-71113f298028

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

Copilot AI commented Apr 14, 2026

@copilot conflicting imports are errors, only main can override.

Review all comments.

Done in commit b0b856d. Addressed all reviewer comments:

  • imports.go: Updated mergeEnv comment — removed stale "later imports take precedence" language; conflicts are caught in the accumulator before mergeEnv is called.
  • docs/reference/imports.md: Updated env: row to say duplicate keys across imports fail compilation (not "later wins").
  • compiler_yaml.go: Renamed header section from "Env variables:" to "Frontmatter env variables:" with a comment clarifying that programmatically-injected vars (e.g. OTEL_* from OTLP config) are not listed.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw -pack /home/REDACTED/work/gh-aw/gh-aw/cmd/gh-aw/main.go x_amd64/vet /pre�� (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw /lcs (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha ignore-path ../../../.prettierignore (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha mit} (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha h ../../../.prettierignore (http block)
  • https://api.github.com/repos/docker/build-push-action/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/docker/build-push-action/git/ref/tags/v7 --jq .object.sha th .prettierigno-f fg 64/pkg/tool/linu-f http.https://git/usr/bin/gh (http block)
    • Triggering command: /usr/bin/gh gh api /repos/docker/build-push-action/git/ref/tags/v7 --jq .object.sha th .prettierignore --log-level=error --global 64/pkg/tool/linux_amd64/vet http.https://gitgit (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/-
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/- --jq .object.sha js/**/*.json&#39; --ignore-path ../../../.prettierignore red-imports 64/pkg/tool/linux_amd64/vet ame (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot merge main and recompile

…ield-in-shared-imports

# Conflicts:
#	docs/src/content/docs/reference/imports.md

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

Copilot AI commented Apr 14, 2026

@copilot merge main and recompile

Done in commit 281b74c. Merged main (resolved a single conflict in docs/reference/imports.md — kept both the new env: bullet and the github-app: bullet from main), rebuilt the binary, and recompiled all 191 workflows successfully.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw -pack /home/REDACTED/work/gh-aw/gh-aw/cmd/gh-aw/main.go (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw eoloL4p/zt0TeUGj-C (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha mit} (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha k/_temp/copilot-developer-actionremote.origin.url (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha (http block)
  • https://api.github.com/repos/docker/build-push-action/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/docker/build-push-action/git/ref/tags/v7 --jq .object.sha (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/-
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/- --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan pelikhan merged commit be7ffcc into main Apr 14, 2026
54 of 55 checks passed
@pelikhan pelikhan deleted the copilot/support-env-field-in-shared-imports branch April 14, 2026 01:15
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.

Support env field in shared imports

3 participants