GitHub Agentic Workflows

Templating

Agentic workflows support four simple templating/substitution mechanisms:

  • GitHub Actions expressions in frontmatter or markdown
  • Conditional Templating blocks in markdown
  • Imports in frontmatter or markdown (compile-time)
  • Runtime imports in markdown (runtime file/URL inclusion)

Agentic workflows restrict expressions in markdown content to prevent security vulnerabilities from exposing secrets or environment variables to the LLM.

Note: These restrictions apply only to markdown content. YAML frontmatter can use secrets and environment variables for workflow configuration.

Permitted expressions in markdown include:

  • Event properties: github.event.* (issue/PR numbers, titles, states, SHAs, IDs, etc.)
  • Repository context: github.actor, github.owner, github.repository, github.server_url, github.workspace
  • Run metadata: github.run_id, github.run_number, github.job, github.workflow
  • Pattern expressions: needs.*, steps.*, github.event.inputs.*

Use steps.sanitized.outputs.text/title/body in your markdown prompts to access sanitized event content:

  • steps.sanitized.outputs.text — sanitized full context (title + body for issues/PRs, body for comments)
  • steps.sanitized.outputs.title — sanitized title of the triggering issue or PR
  • steps.sanitized.outputs.body — sanitized body of the triggering issue or PR

Other activation outputs like comment_id, comment_repo, and slash_command are available as needs.activation.outputs.* in downstream jobs (not in the markdown prompt itself).

All other expressions are disallowed, including secrets.*, env.*, vars.*, and complex functions like toJson() or fromJson().

Expression safety is validated during compilation. Unauthorized expressions produce errors like:

error: unauthorized expressions: [secrets.TOKEN, env.MY_VAR].
allowed: [github.repository, github.actor, github.workflow, ...]

Include or exclude prompt sections based on boolean expressions using {{#if ...}} ... {{/if}} blocks.

{{#if expression}}
Content to include if expression is truthy
{{/if}}

The compiler automatically wraps expressions with ${{ }} for GitHub Actions evaluation. For example, {{#if github.event.issue.number}} becomes {{#if ${{ github.event.issue.number }} }}.

Falsy values: false, 0, null, undefined, "" (empty string) Truthy values: Everything else

---
on:
issues:
types: [opened]
---
# Issue Analysis
Analyze issue #${{ github.event.issue.number }}.
{{#if github.event.issue.number}}
## Issue-Specific Analysis
You are analyzing issue #${{ github.event.issue.number }}.
{{/if}}
{{#if github.event.pull_request.number}}
## Pull Request Analysis
You are analyzing PR #${{ github.event.pull_request.number }}.
{{/if}}

The template system supports only basic conditionals - no nesting, else clauses, variables, loops, or complex evaluation.

Runtime imports include content from files and URLs in workflow prompts at runtime (unlike compile-time imports). File paths are restricted to the .github folder. Use {{#runtime-import filepath}} or {{#runtime-import? filepath}} for optional imports.

Use {{#runtime-import filepath}} to include file content at runtime. Optional imports use {{#runtime-import? filepath}} which don’t fail if the file is missing.

Important: All file paths are resolved within the .github folder. You can specify paths with or without the .github/ prefix:

---
on: issues
engine: copilot
---
# Code Review Agent
Follow these coding guidelines:
{{#runtime-import coding-standards.md}}
<!-- Same as: {{#runtime-import .github/coding-standards.md}} -->
Review the code changes and provide feedback.

Line range extraction:

# Bug Fix Validator
The original buggy code was (from .github/docs/auth.go):
{{#runtime-import docs/auth.go:45-52}}
Verify the fix addresses the issue.

Optional imports:

# Issue Analyzer
{{#runtime-import? shared-instructions.md}}
Analyze issue #${{ github.event.issue.number }}.

The macro syntax supports HTTP/HTTPS URLs. URLs are not restricted to .github folder and content is cached for 1 hour.

{{#runtime-import https://raw.githubusercontent.com/org/repo/main/checklist.md}}
{{#runtime-import https://example.com/standards.md:10-50}}

All runtime imports include automatic security protections.

Content Sanitization: YAML front matter and HTML/XML comments are automatically stripped. GitHub Actions expressions (${{ ... }}) are rejected with error to prevent template injection and unintended variable expansion.

Path Validation: File paths are restricted to the .github folder to prevent access to arbitrary repository files. Path traversal and absolute paths are rejected:

{{#runtime-import ../src/config.go}} # Error: Relative traversal outside .github
{{#runtime-import /etc/passwd}} # Error: Absolute path not allowed

Fetched URLs are cached for 1 hour per workflow run at /tmp/gh-aw/url-cache/ (keyed by SHA256 hash). The first fetch adds ~500ms–2s latency; subsequent accesses use cached content.

Runtime imports are processed before other substitutions:

  1. {{#runtime-import}} macros processed (files and URLs)
  2. ${GH_AW_EXPR_*} variable interpolation
  3. {{#if}} template conditionals rendered
  • .github folder only: File paths are restricted to .github folder for security
  • No authentication: URL fetching doesn’t support private URLs with tokens
  • No recursion: Imported content cannot contain additional runtime imports
  • Per-run cache: URL cache doesn’t persist across workflow runs
  • Line numbers: Refer to raw file content before front matter removal
ErrorMessage
File not foundRuntime import file not found: missing.txt
Invalid line rangeInvalid start line 100 for file docs/main.go (total lines: 50)
Path traversalSecurity: Path ../src/main.go must be within .github folder
GitHub Actions macrosFile template.md contains GitHub Actions macros (${{ ... }}) which are not allowed in runtime imports
URL fetch failureFailed to fetch URL https://example.com/file.txt: HTTP 404