Skip to content
GitHub Agentic Workflows

Environment Variables

Environment variables in GitHub Agentic Workflows can be defined at multiple scopes, each serving a specific purpose in the workflow lifecycle. Variables defined at more specific scopes override those at more general scopes, following GitHub Actions conventions while adding AWF-specific contexts.

GitHub Agentic Workflows supports environment variables in 13 distinct contexts:

ScopeSyntaxContextTypical Use
Workflow-levelenv:All jobsShared configuration
Job-leveljobs.<job_id>.envAll steps in jobJob-specific config
Step-levelsteps[*].envSingle stepStep-specific config
Engineengine.envAI engineEngine secrets, timeouts
Containercontainer.envContainer runtimeContainer settings
Servicesservices.<id>.envService containersDatabase credentials
Sandbox Agentsandbox.agent.envSandbox runtimeSandbox configuration
Sandbox MCPsandbox.mcp.envMCP gatewayMCP debugging
MCP Toolstools.<name>.envMCP server processMCP server secrets
Safe Inputssafe-inputs.<name>.envSafe-input executionTool-specific tokens
Safe Outputs Globalsafe-outputs.envAll safe-output jobsShared safe-output config
Safe Outputs Jobsafe-outputs.jobs.<name>.envSpecific safe-output jobJob-specific config
GitHub Actions StepgithubActionsStep.envPre-defined stepsStep configuration

Workflow-level shared configuration:

---
env:
NODE_ENV: production
API_ENDPOINT: https://api.example.com
---

Job-specific overrides:

---
jobs:
validation:
env:
VALIDATION_MODE: strict
steps:
- run: npm run build
env:
BUILD_ENV: production # Overrides job and workflow levels
---

AWF-specific contexts:

---
# Engine configuration
engine:
id: copilot
env:
OPENAI_API_KEY: ${{ secrets.CUSTOM_KEY }}
# MCP server with secrets
tools:
database:
command: npx
args: ["-y", "mcp-server-postgres"]
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
# Safe outputs with custom PAT
safe-outputs:
create-issue:
env:
GITHUB_TOKEN: ${{ secrets.CUSTOM_PAT }}
---

Environment variables follow a most-specific-wins model, consistent with GitHub Actions. Variables at more specific scopes completely override variables with the same name at less specific scopes.

  1. Step-level (steps[*].env, githubActionsStep.env)
  2. Job-level (jobs.<job_id>.env)
  3. Workflow-level (env:)
  1. Job-specific (safe-outputs.jobs.<job_name>.env)
  2. Global (safe-outputs.env)
  3. Workflow-level (env:)

These scopes are independent and operate in different contexts: engine.env, container.env, services.<id>.env, sandbox.agent.env, sandbox.mcp.env, tools.<tool>.env, safe-inputs.<tool>.env.

---
env:
API_KEY: default-key
DEBUG: "false"
jobs:
test:
env:
API_KEY: test-key # Overrides workflow-level
EXTRA: "value"
steps:
- run: |
# API_KEY = "test-key" (job-level override)
# DEBUG = "false" (workflow-level inherited)
# EXTRA = "value" (job-level)
---

Shared configuration with job overrides:

---
env:
NODE_ENV: production
jobs:
test:
env:
NODE_ENV: test # Override for testing
---

Safe outputs with custom PAT:

---
safe-outputs:
create-issue:
env:
GITHUB_TOKEN: ${{ secrets.CUSTOM_PAT }}
---

Engine and MCP configuration:

---
engine:
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_KEY }}
tools:
database:
command: npx
args: ["-y", "mcp-server-postgres"]
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
---

Always use secrets for sensitive data:

# ✅ Correct
env:
API_KEY: ${{ secrets.API_KEY }}
# ❌ Never hardcode secrets
env:
API_KEY: "sk-1234567890abcdef"

Define variables at the narrowest scope needed:

# ✅ Job-specific variable
jobs:
build:
env:
BUILD_MODE: production

Use consistent naming conventions:

  • SCREAMING_SNAKE_CASE format
  • Descriptive names: API_KEY not KEY
  • Service prefixes: POSTGRES_PASSWORD, REDIS_PORT

During compilation, AWF extracts environment variables from frontmatter, preserves GitHub Actions expressions (${{ ... }}), and renders them to the appropriate scope in .lock.yml files. Secret syntax is validated to ensure ${{ secrets.NAME }} format.

Generated lock file structure:

env:
SHARED_VAR: value
jobs:
agent:
env:
GH_AW_SAFE_OUTPUTS: /tmp/gh-aw/safeoutputs/outputs.jsonl
CUSTOM_VAR: ${{ secrets.CUSTOM_SECRET }}
steps:
- name: Execute
env:
STEP_VAR: value

View all available variables:

jobs:
debug:
steps:
- run: env | sort

Test precedence:

---
env:
TEST_VAR: workflow
jobs:
test:
env:
TEST_VAR: job
steps:
- run: echo "TEST_VAR is $TEST_VAR" # Outputs: "job"
---