GitHub Agentic Workflows

AI Engines (aka Coding Agents)

GitHub Agentic Workflows use AI Engines (normally a coding agent) to interpret and execute natural language instructions.

Set engine: in your workflow frontmatter and configure the corresponding secret:

Engineengine: valueRequired Secret
GitHub Copilot CLI (default)copilotCOPILOT_GITHUB_TOKEN
Claude by Anthropic (Claude Code)claudeANTHROPIC_API_KEY
OpenAI CodexcodexOPENAI_API_KEY
Google Gemini CLIgeminiGEMINI_API_KEY
Crush (experimental)crushCOPILOT_GITHUB_TOKEN
OpenCode (experimental)opencodeCOPILOT_GITHUB_TOKEN
Pi (experimental)piCOPILOT_GITHUB_TOKEN (default); switches to provider-specific secret when model: uses provider/model format

Copilot CLI is the default — engine: can be omitted when using Copilot. See the linked authentication docs for secret setup instructions.

Choose the engine that best matches your needs and existing AI account: Copilot supports the broadest gh-aw feature set, including custom agents and autopilot-style continuations; Claude offers stronger control over turn limits (max-turns) for long reasoning sessions; and Gemini or Codex fit well when those models are already part of existing tooling or budget decisions. You can switch later by changing only engine: and the corresponding secret.

Not all features are available across all engines. The table below summarizes per-engine support for commonly used workflow options:

FeatureCopilotClaudeCodexGeminiCrushOpenCodePi
max-runs (AWF invocation cap)
max-turns
max-continuations
tools.web-fetch
tools.web-searchvia MCPvia MCP✓ (opt-in)via MCPvia MCPvia MCPvia MCP
engine.agent (custom agent file)
engine.api-target (custom endpoint)
engine.bare (disable context loading)
engine.harness (custom harness script)
Tools allowlist

max-runs (default 500) and max-effective-tokens (default 25M) are top-level frontmatter fields supported by all engines. max-turns limits Claude iterations per run; max-continuations enables Copilot autopilot mode. Codex web-search is opt-in via tools: web-search:; other engines use a third-party MCP server — see Using Web Search. engine.agent, engine.bare, and engine.harness are described below.

Workflows can specify extended configuration for the coding agent:

engine:
id: copilot
version: latest # defaults to latest
model: gpt-5 # example override; omit to use engine default
command: /usr/local/bin/copilot # custom executable path
args: ["--add-dir", "/workspace"] # custom CLI arguments
agent: agent-id # custom agent file identifier
api-target: api.acme.ghe.com # custom API endpoint hostname (GHEC/GHES)

By default, workflows install the latest available version of each engine CLI. To pin to a specific version, set version to the desired release:

EngineidExample version
GitHub Copilot CLIcopilot"0.0.422"
Claude Codeclaude"2.1.70"
Codexcodex"0.111.0"
Gemini CLIgemini"0.31.0"
Crushcrush"1.2.14"
OpenCodeopencode"0.1.0"
Pipi"0.72.1"
engine:
id: copilot
version: "0.0.422"

Pinning is useful when you need reproducible builds or want to avoid breakage from a new CLI release while testing. Remember to update the pinned version periodically to pick up bug fixes and new features.

version also accepts a GitHub Actions expression string, enabling workflow_call reusable workflows to parameterize the engine version via caller inputs. Expressions are passed injection-safely through an environment variable rather than direct shell interpolation:

on:
workflow_call:
inputs:
engine-version:
type: string
default: latest
---
engine:
id: copilot
version: ${{ inputs.engine-version }}

Use agent to reference a custom agent file in .github/agents/ (omit the .agent.md extension):

engine:
id: copilot
agent: technical-doc-writer # .github/agents/technical-doc-writer.agent.md

See Copilot Agent Files for details.

All engines support custom environment variables through the env field:

engine:
id: copilot
env:
DEBUG_MODE: "true"
AWS_REGION: us-west-2
CUSTOM_API_ENDPOINT: https://api.example.com

Environment variables can also be defined at workflow, job, step, and other scopes. See Environment Variables for complete documentation on precedence and all 13 env scopes.

The api-target field specifies a custom API endpoint hostname for the agentic engine. Use this when running workflows against GitHub Enterprise Cloud (GHEC), GitHub Enterprise Server (GHES), or any custom AI endpoint.

For a complete setup and debugging walkthrough for GHE Cloud with data residency, see Debugging GHE Cloud with Data Residency.

The value must be a hostname only — no protocol or path (e.g., api.acme.ghe.com, not https://api.acme.ghe.com/v1). The field works with any engine.

Example — specify a GHEC or GHES Copilot endpoint (use api.enterprise.githubcopilot.com for GHES):

engine:
id: copilot
api-target: api.acme.ghe.com
network:
allowed:
- defaults
- acme.ghe.com
- api.acme.ghe.com

The specified hostname must also be listed in network.allowed for the firewall to permit outbound requests.

Custom API Endpoints via Environment Variables

Section titled “Custom API Endpoints via Environment Variables”

Set a base URL environment variable in engine.env to route API calls to an internal LLM router, Azure OpenAI deployment, or corporate proxy. AWF automatically extracts the hostname and applies it to the API proxy. The target domain must also appear in network.allowed.

EngineEnvironment variable
codex, crushOPENAI_BASE_URL
claudeANTHROPIC_BASE_URL
copilotGITHUB_COPILOT_BASE_URL
geminiGEMINI_API_BASE_URL
engine:
id: codex
model: gpt-4o
env:
OPENAI_BASE_URL: "https://llm-router.internal.example.com/v1"
OPENAI_API_KEY: ${{ secrets.LLM_ROUTER_KEY }}
network:
allowed:
- github.com
- llm-router.internal.example.com

GITHUB_COPILOT_BASE_URL is a fallback — if both it and engine.api-target are set, engine.api-target takes precedence. Crush uses OpenAI-compatible API format; its model field uses provider/model format (e.g., openai/gpt-4o).

The Copilot engine supports routing requests to an external LLM provider instead of GitHub’s default routing. This is useful when you want to use a different model or provider (e.g., OpenAI, Anthropic, Azure OpenAI, or a local Ollama/vLLM instance) while still using the Copilot CLI tooling.

Set COPILOT_PROVIDER_BASE_URL in engine.env to activate BYOK mode. The credential variables COPILOT_PROVIDER_BASE_URL, COPILOT_PROVIDER_API_KEY, and COPILOT_PROVIDER_BEARER_TOKEN are explicitly allowed to carry ${{ secrets.* }} references in engine.env under strict mode — they are not leaked to the agent container. Other COPILOT_PROVIDER_* variables hold non-sensitive configuration and can be set as plain strings.

VariableRequiredDescription
COPILOT_PROVIDER_BASE_URL✓ for BYOKBase URL of the external provider (e.g. https://api.openai.com/v1)
COPILOT_MODEL✓ for BYOKModel to use (e.g. claude-sonnet-4, gpt-4o); required by most providers
COPILOT_PROVIDER_API_KEYOptionalAPI key for cloud providers (OpenAI, Anthropic, etc.); not needed for local providers
COPILOT_PROVIDER_BEARER_TOKENOptionalBearer token alternative to COPILOT_PROVIDER_API_KEY; takes precedence when set
COPILOT_PROVIDER_TYPEOptionalProvider format: openai (default), azure, or anthropic
COPILOT_PROVIDER_WIRE_APIOptionalWire API variant: completions (default) or responses (for GPT-5 series)
COPILOT_PROVIDER_MODEL_IDOptionalModel ID sent on the wire when it differs from COPILOT_MODEL (e.g. an Azure deployment name)
COPILOT_PROVIDER_WIRE_MODELOptionalAlternative to COPILOT_PROVIDER_MODEL_ID for overriding the wire model
COPILOT_PROVIDER_MAX_PROMPT_TOKENSOptionalOverride the maximum prompt token limit (otherwise resolved from model catalog)
COPILOT_PROVIDER_MAX_OUTPUT_TOKENSOptionalOverride the maximum output token limit

Example:

engine:
id: copilot
env:
COPILOT_PROVIDER_BASE_URL: ${{ secrets.PROVIDER_BASE_URL }} # REQUIRED — activates BYOK
COPILOT_MODEL: claude-sonnet-4 # REQUIRED for most providers
COPILOT_PROVIDER_API_KEY: ${{ secrets.PROVIDER_API_KEY }} # OPTIONAL for local providers
COPILOT_PROVIDER_TYPE: anthropic # OPTIONAL — default: openai
network:
allowed:
- defaults
- your-provider-domain.example.com

[!NOTE] Credentials are kept out of the agent container — only a dummy API key activating the AWF BYOK detection path is visible to the agent process; the real credential is isolated in the AWF API proxy sidecar. See AWF sandbox architecture.

All engines support custom command-line arguments through the args field, injected before the prompt:

engine:
id: copilot
args: ["--add-dir", "/workspace", "--verbose"]

Arguments are added in order and placed before the --prompt flag. Consult the specific engine’s CLI documentation for available flags.

Override the default engine executable using the command field. Useful for testing pre-release versions, custom builds, or non-standard installations. Installation steps are automatically skipped.

engine:
id: copilot
command: /usr/local/bin/copilot-dev # absolute path
args: ["--verbose"]

The harness field lets you replace the built-in Node.js harness wrapper that the Copilot engine uses to launch the CLI. Use this when you need to customize startup behavior, inject pre/post hooks, or test an alternative harness implementation.

engine:
id: copilot
harness: custom_copilot_harness.cjs

The value must be a bare filename — no directory separators, no .., and no shell metacharacters. It must end with .js, .cjs, or .mjs. When harness is set, AWF automatically ensures Node 24 is available in the runner environment.

[!NOTE] engine.harness is currently only applied during Copilot engine execution. Setting it on other engines has no effect.

Validation rules:

RuleValid exampleInvalid example
Bare filename onlymy_harness.cjssubdir/harness.cjs
No path traversalharness.mjs../harness.cjs
Must start with [A-Za-z0-9_]harness.js-harness.cjs
Must end with .js, .cjs, or .mjswrapper.cjsharness.sh

Enable engine.copilot-sdk: true to run Copilot in SDK mode. In this mode, the harness starts a local sidecar and runs the SDK driver process instead of the default CLI-only flow.

Use engine.copilot-sdk-driver to replace the built-in copilot_sdk_driver.cjs implementation:

engine:
id: copilot
copilot-sdk: true
copilot-sdk-driver: .github/drivers/custom-copilot-driver.js

copilot-sdk-driver must be a relative path from the workspace root (no absolute paths, .., backslashes, or shell metacharacters). It supports:

  • script filenames ending with .js, .cjs, .mjs, .py, .ts, .mts, or .rb
  • bare command names without an extension (resolved from PATH)

See Copilot SDK Driver Specification for the full driver contract.

The specification defines the driver environment contract. In SDK mode, gh-aw injects required runtime values:

  • GH_AW_PROMPT
  • COPILOT_SDK_URI
  • COPILOT_CONNECTION_TOKEN

COPILOT_MODEL is required and must be set to the model to use (e.g. gpt-4o, claude-sonnet-4). Drivers MUST fail fast when it is not set.

For runtime controls, the driver should consume:

  • COPILOT_SDK_SEND_TIMEOUT_MS
  • COPILOT_SDK_LOG_LEVEL

In gh-aw, COPILOT_SDK_SEND_TIMEOUT_MS is usually injected automatically from workflow timeout-minutes (via GH_AW_TIMEOUT_MINUTES) with safety headroom. Override it in engine.env only when you need a custom SDK send timeout. COPILOT_SDK_LOG_LEVEL is a host-provided driver control and should be honored when gh-aw passes it to the driver process.

Do not set COPILOT_CONNECTION_TOKEN manually. The harness generates it per run and passes the same token to both the sidecar and driver process.

engine:
id: copilot
copilot-sdk: true
copilot-sdk-driver: .github/drivers/my_driver.ts
model: gpt-5
env:
COPILOT_SDK_SEND_TIMEOUT_MS: "900000"
COPILOT_SDK_LOG_LEVEL: info

Set engine.bare: true to disable automatic loading of context and custom instructions by the engine. Use this when the workflow prompt is fully self-contained and you want to prevent the engine from reading memory files, AGENTS.md, or built-in system prompts that would otherwise be loaded automatically.

engine:
id: claude
bare: true

The underlying mechanism is engine-specific:

EngineEffect
CopilotPasses --no-custom-instructions — suppresses .github/AGENTS.md and user-level custom instructions
ClaudePasses --bare — suppresses CLAUDE.md memory files
CodexPasses --no-system-prompt — suppresses the default system prompt
GeminiSets GEMINI_SYSTEM_MD=/dev/null — overrides the built-in system prompt with an empty file

Defaults to false.

Override the built-in token cost multipliers used when computing Effective Tokens. Useful when your workflow uses a custom model not in the built-in list, or when you want to adjust the relative cost ratios for your use case.

engine:
id: claude
token-weights:
multipliers:
my-custom-model: 2.5 # 2.5x the cost of claude-sonnet-4.5
experimental-llm: 0.8 # Override an existing model's multiplier
token-class-weights:
output: 6.0 # Override output token weight (default: 4.0)
cached-input: 0.05 # Override cached input weight (default: 0.1)

multipliers is a map of model names to numeric multipliers relative to claude-sonnet-4.5 (= 1.0). Keys are case-insensitive and support prefix matching. token-class-weights overrides the per-class weights applied before the model multiplier; the defaults are input: 1.0, cached-input: 0.1, output: 4.0, reasoning: 4.0, cache-write: 1.0.

Custom weights are embedded in the compiled workflow YAML and read by gh aw logs and gh aw audit when analyzing runs.

Repositories with long build or test cycles require careful timeout tuning at multiple levels. This section documents the timeout knobs available for each engine.

timeout-minutes sets the maximum wall-clock time for the entire agent job. This is the primary knob for repositories with long build times. The default is 20 minutes.

timeout-minutes: 60 # allow up to 60 minutes for the agent job

See Long Build Times in the Sandbox reference for recommended values and concrete examples, including a 30-minute C++ workflow.

tools.timeout limits how long any single tool invocation may run, in seconds. Useful when individual bash commands (builds, test suites) take longer than an engine’s default:

tools:
timeout: 300 # 5 minutes per tool call

Defaults: Claude 60s, Codex 120s. Other engines (Copilot, Gemini, Crush) are engine-managed and not enforced by gh-aw. See Tool Timeout Configuration for full documentation including tools.startup-timeout.

KnobCopilotClaudeCodex/Gemini/Crush/OpenCodePurpose
timeout-minutesJob-level wall clock
tools.timeoutPer tool-call limit (seconds)
tools.startup-timeoutMCP server startup limit
max-turnsIteration budget
max-continuationsAutopilot run budget

Copilot uses max-continuations for autopilot runs; Claude uses max-turns to cap iterations. Codex, Gemini, Crush, and OpenCode rely solely on timeout-minutes and tools.timeout.

# Claude — combine iteration cap with per-tool timeout
engine:
id: claude
max-turns: 20
tools:
timeout: 600
timeout-minutes: 60

When max-turns is set in frontmatter, gh-aw passes it to Claude automatically — no need to also set the CLAUDE_CODE_MAX_TURNS env var.

Claude Code accepts a --permission-mode flag that determines whether the declared tools: allowlist is enforced. Set engine.permission-mode to one of auto, acceptEdits, plan, or bypassPermissions:

engine:
id: claude
permission-mode: auto

engine.permission-mode takes precedence over any --permission-mode flag supplied through engine.args. When unset, the default is acceptEdits (or auto when tools.edit: false). gh-aw does not derive bypassPermissions implicitly from unrestricted bash — set it explicitly.

engine.permission-modeEffective mode--allowed-tools enforced?Gateway allowed: enforced?
unset (default)acceptEdits✓ Yes✓ Yes
unset, with tools.edit: falseauto✓ Yes✓ Yes
autoauto✓ Yes✓ Yes
acceptEditsacceptEdits✓ Yes✓ Yes
planplan✓ Yes✓ Yes
bypassPermissionsbypassPermissions✗ No✓ Yes

The MCP gateway’s allowed: filter is the sole effective tool boundary in bypassPermissions mode (and a second layer of enforcement otherwise). Always specify allowed: on each mcp-servers: entry to restrict which MCP tools are reachable:

mcp-servers:
notion:
container: "mcp/notion"
allowed: ["search_pages", "get_page"] # enforced at gateway level

[!WARNING] Do not rely on tools: or mcp-servers: allowed: for security guarantees in bypassPermissions mode. The agent can already run arbitrary shell commands when unrestricted bash is granted, so --allowed-tools provides no meaningful additional boundary.