Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/stale-repo-identifier.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 53 additions & 24 deletions docs/src/content/docs/reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ GitHub Agentic Workflows supports environment variables in 13 distinct contexts:

| Scope | Syntax | Context | Typical Use |
|-------|--------|---------|-------------|
| **Workflow-level** | `env:` | All jobs | Shared configuration |
| **Frontmatter `env:`** | `env:` | Agent job only | Agent job configuration |
| **Job-level** | `jobs.<job_id>.env` | All steps in job | Job-specific config |
| **Step-level** | `steps[*].env` | Single step | Step-specific config |
| **Engine** | `engine.env` | AI engine | Engine secrets, timeouts |
Expand All @@ -29,7 +29,7 @@ GitHub Agentic Workflows supports environment variables in 13 distinct contexts:

### Example Configurations

**Workflow-level shared configuration:**
**Frontmatter `env:` (agent job only):**
```yaml wrap
---
env:
Expand All @@ -38,6 +38,9 @@ env:
---
```

> [!NOTE]
> The frontmatter `env:` section applies **only to the agent job**, not to all jobs in the workflow. To set environment variables for custom jobs, use `jobs.<job_id>.env`. To set environment variables for safe-output jobs, use `safe-outputs.env` or `safe-outputs.jobs.<job_name>.env`.

**Job-specific overrides:**
```yaml wrap
---
Expand Down Expand Up @@ -85,13 +88,16 @@ Environment variables follow a **most-specific-wins** model, consistent with Git

1. **Step-level** (`steps[*].env`, `githubActionsStep.env`)
2. **Job-level** (`jobs.<job_id>.env`)
3. **Workflow-level** (`env:`)
3. **Frontmatter `env:`** (applies to agent job only)

> [!NOTE]
> The frontmatter `env:` section is **not** workflow-level. It applies only to the agent job. Custom jobs must define their own environment variables using `jobs.<job_id>.env`.

### Safe Outputs Precedence

1. **Job-specific** (`safe-outputs.jobs.<job_name>.env`)
2. **Global** (`safe-outputs.env`)
3. **Workflow-level** (`env:`)
3. **Frontmatter `env:`** (if the safe-output job inherits from agent job)

### Context-Specific Scopes

Expand All @@ -102,33 +108,47 @@ These scopes are independent and operate in different contexts: `engine.env`, `c
```yaml wrap
---
env:
API_KEY: default-key
API_KEY: agent-key # Applied to agent job only
DEBUG: "false"

jobs:
test:
env:
API_KEY: test-key # Overrides workflow-level
API_KEY: test-key # Custom job defines its own env
EXTRA: "value"
steps:
- run: |
# API_KEY = "test-key" (job-level override)
# DEBUG = "false" (workflow-level inherited)
# In 'test' job:
# API_KEY = "test-key" (job-level)
# EXTRA = "value" (job-level)
# DEBUG is NOT available (frontmatter env not inherited)

# In agent job:
# API_KEY = "agent-key" (from frontmatter env)
# DEBUG = "false" (from frontmatter env)
---
```

## Common Patterns

**Shared configuration with job overrides:**
**Agent job configuration:**
```yaml wrap
---
env:
NODE_ENV: production
NODE_ENV: production # Applied to agent job only
---
```

**Custom jobs define their own env:**
```yaml wrap
---
env:
AGENT_VAR: value # Agent job only

jobs:
test:
env:
NODE_ENV: test # Override for testing
NODE_ENV: test # Test job environment
---
```

Expand Down Expand Up @@ -191,41 +211,50 @@ During compilation, AWF extracts environment variables from frontmatter, preserv

**Generated lock file structure:**
```yaml
env:
SHARED_VAR: value
# No workflow-level env section

jobs:
agent:
env:
GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
# Frontmatter env variables merged with system env
CUSTOM_VAR: ${{ secrets.CUSTOM_SECRET }}
GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
GH_AW_WORKFLOW_ID_SANITIZED: myworkflow
steps:
- name: Execute
env:
STEP_VAR: value
```

> [!NOTE]
> The frontmatter `env:` variables are merged into the agent job's `env:` section alongside system-generated variables like `GH_AW_SAFE_OUTPUTS` and `GH_AW_WORKFLOW_ID_SANITIZED`.

## Debugging Environment Variables

**View all available variables:**
**View all available variables in agent job:**
```yaml wrap
jobs:
debug:
steps:
- run: env | sort
---
env:
TEST_VAR: agent-value
---

# Add this to your workflow markdown to debug
```

**Test precedence:**
The agent job will have access to `TEST_VAR` along with system variables.

**Custom jobs define their own env:**
```yaml wrap
---
env:
TEST_VAR: workflow
AGENT_VAR: agent-value # Only in agent job

jobs:
test:
debug:
env:
TEST_VAR: job
DEBUG_VAR: custom-value # Only in debug job
steps:
- run: echo "TEST_VAR is $TEST_VAR" # Outputs: "job"
- run: env | sort
---
```

Expand Down
8 changes: 6 additions & 2 deletions docs/src/content/docs/reference/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,15 +449,19 @@ Automatically generates concurrency policies for the agent job. See [Concurrency

## Environment Variables (`env:`)

Standard GitHub Actions `env:` syntax for workflow-level environment variables:
Define environment variables scoped to the agent job using standard GitHub Actions `env:` syntax:

```yaml wrap
env:
CUSTOM_VAR: "value"
SECRET_VAR: ${{ secrets.MY_SECRET }}
```

Environment variables can be defined at multiple scopes (workflow, job, step, engine, safe-outputs, etc.) with clear precedence rules. See [Environment Variables](/gh-aw/reference/environment-variables/) for complete documentation on all 13 env scopes and precedence order.
**Scope**: Environment variables defined in the frontmatter `env:` section are applied **only to the agent job**, not to all jobs in the workflow. This provides better isolation and follows best practices for environment variable scoping.

For custom jobs, define environment variables directly in the job configuration using `jobs.<job_id>.env`. For safe-output jobs, use `safe-outputs.env` or `safe-outputs.jobs.<job_name>.env`.

Environment variables can be defined at multiple scopes (job, step, engine, safe-outputs, etc.) with clear precedence rules. See [Environment Variables](/gh-aw/reference/environment-variables/) for complete documentation on all env scopes and precedence order.

## Secrets (`secrets:`)

Expand Down
28 changes: 14 additions & 14 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1876,7 +1876,7 @@
},
"env": {
"$comment": "See environment variable precedence documentation: https://github.github.com/gh-aw/reference/environment-variables/",
"description": "Environment variables for the workflow",
"description": "Environment variables scoped to the agent job (not workflow-level)",
"oneOf": [
{
"type": "object",
Expand Down Expand Up @@ -2513,7 +2513,7 @@
]
},
"plugins": {
"description": "\u26a0\ufe0f EXPERIMENTAL: Plugin configuration for installing plugins before workflow execution. Supports array format (list of repos/plugin configs) and object format (repos + custom token). Note: Plugin support is experimental and may change in future releases.",
"description": "⚠️ EXPERIMENTAL: Plugin configuration for installing plugins before workflow execution. Supports array format (list of repos/plugin configs) and object format (repos + custom token). Note: Plugin support is experimental and may change in future releases.",
"examples": [
["github/copilot-plugin", "acme/custom-tools"],
[
Expand Down Expand Up @@ -2710,7 +2710,7 @@
[
{
"name": "Verify Post-Steps Execution",
"run": "echo \"\u2705 Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n"
"run": "echo \" Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n"
},
{
"name": "Upload Test Results",
Expand Down Expand Up @@ -5099,7 +5099,7 @@
"oneOf": [
{
"type": "object",
"description": "Configuration for resolving review threads on pull requests. Resolution is scoped to the triggering PR only \u2014 threads on other PRs cannot be resolved.",
"description": "Configuration for resolving review threads on pull requests. Resolution is scoped to the triggering PR only threads on other PRs cannot be resolved.",
"properties": {
"max": {
"type": "integer",
Expand Down Expand Up @@ -6238,8 +6238,8 @@
},
"staged-title": {
"type": "string",
"description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '\ud83c\udfad Preview: {operation}'",
"examples": ["\ud83c\udfad Preview: {operation}", "## Staged Mode: {operation}"]
"description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '🎭 Preview: {operation}'",
"examples": ["🎭 Preview: {operation}", "## Staged Mode: {operation}"]
},
"staged-description": {
"type": "string",
Expand All @@ -6253,18 +6253,18 @@
},
"run-success": {
"type": "string",
"description": "Custom message template for successful workflow completion. Available placeholders: {workflow_name}, {run_url}. Default: '\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.'",
"examples": ["\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.", "\u2705 [{workflow_name}]({run_url}) finished."]
"description": "Custom message template for successful workflow completion. Available placeholders: {workflow_name}, {run_url}. Default: ' Agentic [{workflow_name}]({run_url}) completed successfully.'",
"examples": [" Agentic [{workflow_name}]({run_url}) completed successfully.", " [{workflow_name}]({run_url}) finished."]
},
"run-failure": {
"type": "string",
"description": "Custom message template for failed workflow. Available placeholders: {workflow_name}, {run_url}, {status}. Default: '\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.'",
"examples": ["\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", "\u274c [{workflow_name}]({run_url}) {status}."]
"description": "Custom message template for failed workflow. Available placeholders: {workflow_name}, {run_url}, {status}. Default: ' Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.'",
"examples": [" Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", " [{workflow_name}]({run_url}) {status}."]
},
"detection-failure": {
"type": "string",
"description": "Custom message template for detection job failure. Available placeholders: {workflow_name}, {run_url}. Default: '\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.'",
"examples": ["\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", "\u26a0\ufe0f Detection job failed in [{workflow_name}]({run_url})."]
"description": "Custom message template for detection job failure. Available placeholders: {workflow_name}, {run_url}. Default: '⚠️ Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.'",
"examples": ["⚠️ Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", "⚠️ Detection job failed in [{workflow_name}]({run_url})."]
},
"append-only-comments": {
"type": "boolean",
Expand Down Expand Up @@ -6350,12 +6350,12 @@
"additionalProperties": false
},
"roles": {
"description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (\u26a0\ufe0f security consideration).",
"description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (⚠️ security consideration).",
"oneOf": [
{
"type": "string",
"enum": ["all"],
"description": "Allow any authenticated user to trigger the workflow (\u26a0\ufe0f disables permission checking entirely - use with caution)"
"description": "Allow any authenticated user to trigger the workflow (⚠️ disables permission checking entirely - use with caution)"
},
{
"type": "array",
Expand Down
13 changes: 13 additions & 0 deletions pkg/workflow/compiler_activation_jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,19 @@ func (c *Compiler) buildMainJob(data *WorkflowData, activationJobCreated bool) (
env["GH_AW_WORKFLOW_ID_SANITIZED"] = sanitizedID
}

// Add frontmatter env variables to the agent job
// These are user-defined environment variables that should only be applied to the agent job
// Reserved variable names are validated at compile time in compiler_orchestrator_workflow.go
if len(data.EnvMap) > 0 {
compilerActivationJobsLog.Printf("Adding %d frontmatter env variables to agent job", len(data.EnvMap))
if env == nil {
env = make(map[string]string)
}
for key, value := range data.EnvMap {
env[key] = value
}
}

// Generate agent concurrency configuration
agentConcurrency := GenerateJobConcurrencyConfig(data)

Expand Down
30 changes: 28 additions & 2 deletions pkg/workflow/compiler_orchestrator_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ func (c *Compiler) ParseWorkflowFile(markdownPath string) (*WorkflowData, error)
workflowData.ActionPinWarnings = c.actionPinWarnings

// Extract YAML configuration sections from frontmatter
c.extractYAMLSections(result.Frontmatter, workflowData)
if err := c.extractYAMLSections(result.Frontmatter, workflowData); err != nil {
return nil, fmt.Errorf("failed to extract YAML sections: %w", err)
}

// Merge features from imports
if len(engineSetup.importsResult.MergedFeatures) > 0 {
Expand Down Expand Up @@ -162,7 +164,7 @@ func (c *Compiler) buildInitialWorkflowData(
}

// extractYAMLSections extracts YAML configuration sections from frontmatter
func (c *Compiler) extractYAMLSections(frontmatter map[string]any, workflowData *WorkflowData) {
func (c *Compiler) extractYAMLSections(frontmatter map[string]any, workflowData *WorkflowData) error {
orchestratorWorkflowLog.Print("Extracting YAML sections from frontmatter")

workflowData.On = c.extractTopLevelYAMLSection(frontmatter, "on")
Expand All @@ -171,6 +173,28 @@ func (c *Compiler) extractYAMLSections(frontmatter map[string]any, workflowData
workflowData.Concurrency = c.extractTopLevelYAMLSection(frontmatter, "concurrency")
workflowData.RunName = c.extractTopLevelYAMLSection(frontmatter, "run-name")
workflowData.Env = c.extractTopLevelYAMLSection(frontmatter, "env")

// Extract env as map for agent job-level rendering
// This allows env variables to be scoped to the agent job instead of globally
if envValue, exists := frontmatter["env"]; exists {
if envMap, ok := envValue.(map[string]any); ok {
workflowData.EnvMap = make(map[string]string)
for key, value := range envMap {
// Validate that env variable names don't use reserved prefixes
if strings.HasPrefix(key, "GH_AW_") {
return fmt.Errorf("env variable %q uses reserved prefix 'GH_AW_' which is reserved for system use", key)
}
if key == "DEFAULT_BRANCH" {
return fmt.Errorf("env variable 'DEFAULT_BRANCH' is reserved for system use")
}

// Convert all value types to strings (handles strings, numbers, booleans, etc.)
// This matches GitHub Actions behavior where env values are always strings
workflowData.EnvMap[key] = fmt.Sprintf("%v", value)
}
}
}

workflowData.Features = c.extractFeatures(frontmatter)
workflowData.If = c.extractIfCondition(frontmatter)

Expand All @@ -181,6 +205,8 @@ func (c *Compiler) extractYAMLSections(frontmatter map[string]any, workflowData
workflowData.Environment = c.extractTopLevelYAMLSection(frontmatter, "environment")
workflowData.Container = c.extractTopLevelYAMLSection(frontmatter, "container")
workflowData.Cache = c.extractTopLevelYAMLSection(frontmatter, "cache")

return nil
}

// processAndMergeSteps handles the merging of imported steps with main workflow steps
Expand Down
Loading
Loading