Safe Outputs: Runtime and Extensibility
July 18, 2026 · View on GitHub
The report-incomplete safe-output is enabled by default and is distinct from noop. Use it when required tools or data are unavailable and the task cannot be meaningfully performed (e.g., MCP server crash, missing authentication, inaccessible repository). When an agent emits report_incomplete, gh-aw activates failure handling even when the agent process exits 0 — preventing empty outputs from being classified as successful, so every unrecoverable failure is tracked.
Per-handler modifiers — most handlers accept max, github-token, github-app, and staged to override the global equivalents for that one output type. Two extras:
-
issue-intent:(boolean) onclose-issue,set-issue-type,set-issue-field,assign-to-user, andassign-to-agent— enables issue-intent metadata (rationale/confidence/suggest) fields and their schema requirements on that tool's payload. -
normalize-closing-keywords:(boolean) on body-carrying handlers (create-issue,add-comment,create-pull-request, …) — strips backticks around recognized issue-closing keywords so they stay active. -
jobs:- Custom safe-output jobs registered as MCP tools for third-party integrationssafe-outputs: jobs: send-notification: description: "Send a notification to an external service" runs-on: ubuntu-latest output: "Notification sent successfully!" inputs: message: description: "The message to send" required: true type: string permissions: contents: read env: API_KEY: ${{ secrets.API_KEY }} steps: - name: Send notification run: | MESSAGE=$(cat "$GH_AW_AGENT_OUTPUT" | jq -r '.items[] | select(.type == "send_notification") | .message') curl -H "Authorization: $API_KEY" -d "$MESSAGE" https://api.example.com/notifyPost-processing GitHub Actions jobs registered as MCP tools. Agents call the tool by its normalized name (dashes → underscores, e.g.,
send_notification). The job runs after the agent completes with access to$GH_AW_AGENT_OUTPUT(agent output JSON path). Use to integrate Slack, Discord, external APIs, databases, or any service requiring secrets. Import from shared files viaimports:. -
scripts:- Inline JavaScript handlers running inside the safe-outputs job handler loopsafe-outputs: scripts: post-slack-message: description: "Post a message to Slack" inputs: channel: description: "Target Slack channel" type: string default: "#general" script: | // 'channel' is available from config inputs; 'item' contains runtime message values await fetch(process.env.SLACK_WEBHOOK_URL, { method: "POST", body: JSON.stringify({ text: item.message, channel }) });Unlike
jobs:(which create separate GitHub Actions jobs), scripts execute in-process alongside built-in handlers. Write only the handler body — the compiler generates the outer wrapper with config input destructuring andasync function handleX(item, resolvedTemporaryIds) { ... }. Script names with dashes are normalized to underscores (e.g.,post-slack-message→post_slack_message). The handler receivesitem(runtime message with input values) andresolvedTemporaryIds(map of temporary IDs). -
actions:- Custom GitHub Actions mounted as MCP tools for the AI agent (resolved at compile time)safe-outputs: actions: my-action: uses: owner/repo/path@ref # Required: GitHub Action reference (tag, SHA, or branch) description: "Custom description" # Optional: override action's description from action.yml env: API_KEY: ${{ secrets.API_KEY }} # Optional: environment variables for the injected stepResolved at compile time — the compiler fetches
action.yml, parses inputs, and exposes them as MCP tool parameters. The agent calls the action by its normalized name (dashes → underscores). Each action runs as an injected step in the safe-outputs job. Local actions (./path/to/action) are also supported.
Global Safe Output Configuration:
-
github-token:- Custom GitHub token for all safe output jobssafe-outputs: create-issue: add-comment: github-token: ${{ secrets.GH_AW_SAFE_OUTPUTS_TOKEN }} # Use custom PAT instead of GITHUB_TOKENUseful when you need additional permissions or want to perform actions across repositories.
-
urls:- URL sanitization policy for safe output content (string)allowed-only(default) - sanitize all non-allowed URLs everywhereallowed-or-code-region- preserve URLs inside fenced and inline code regions while sanitizing prose
-
allowed-domains:- Allowed domains for URLs in safe output content (array)- URLs from unlisted domains are replaced with
(redacted) - GitHub domains are always included by default
- URLs from unlisted domains are replaced with
-
allowed-github-references:- Allowed repositories for GitHub-style references (array)-
Controls which GitHub references (
#123,owner/repo#456) are allowed in workflow output -
References to unlisted repositories are escaped with backticks to prevent timeline items
-
Configuration options:
[]- Escape all references (prevents all timeline items)["repo"]- Allow only the target repository's references["repo", "owner/other-repo"]- Allow specific repositories- Not specified (default) - All references allowed
-
Example:
safe-outputs: allowed-github-references: [] # Escape all references create-issue: target-repo: "my-org/main-repo"With
[], references like#123become`#123`andother/repo#456becomes`other/repo#456`, preventing timeline clutter while preserving information.
-
-
messages:- Custom message templates for safe-output footer and notification messages (object)-
Available placeholders:
{workflow_name},{run_url},{agentic_workflow_url},{triggering_number},{triggering_type},{workflow_source},{workflow_source_url},{operation},{event_type},{status},{ai_credits},{ai_credits_formatted},{ai_credits_suffix} -
Message types:
footer:- Custom footer for AI-generated contentfooter-install:- Installation instructions appended to footerfooter-workflow-recompile:- Footer for workflow recompile tracking issues (placeholder:{repository})footer-workflow-recompile-comment:- Footer for comments on workflow recompile issues (placeholder:{repository})run-started:- Workflow activation notificationrun-success:- Successful completion messagerun-failure:- Failure notification messagedetection-failure:- Detection job failure messageagent-failure-issue:- Footer for agent failure tracking issuesagent-failure-comment:- Footer for comments on agent failure tracking issuesstaged-title:- Staged mode preview titlestaged-description:- Staged mode preview descriptionappend-only-comments:- Create new comments instead of editing existing ones (boolean, default: false)pull-request-created:- Custom message when a PR is created. Placeholders:{item_number},{item_url}issue-created:- Custom message when an issue is created. Placeholders:{item_number},{item_url}commit-pushed:- Custom message when a commit is pushed. Placeholders:{commit_sha},{short_sha},{commit_url}body-header:- Custom header text prepended to every message body (issues, comments, PRs, discussions). Placeholders:{workflow_name},{run_url}disclosure-header:- AI authorship disclosure header prepended to every message body. Set to"true"for built-in default text, or provide a custom template string. Placeholders:{workflow_name},{run_url}
-
Example:
safe-outputs: messages: append-only-comments: true footer: "> Generated by [{workflow_name}]({run_url})" run-started: "[{workflow_name}]({run_url}) started processing this {event_type}."
-
-
mentions:- Configuration for @mention filtering in safe outputs (boolean or object)-
Boolean format:
false- Always escape mentions;true- Always allow (error in strict mode) -
Object format for fine-grained control:
safe-outputs: mentions: allowed-collaborators: true # Allow repository collaborators (default: true; `allow-team-members` is a deprecated alias) allow-context: true # Allow mentions from event context (default: true) allowed: [copilot, user1] # Always allow specific users/bots allowed-teams: # Allow all members of named GitHub teams - myorg/eng # org/team-slug format (cross-org) - reviewers # bare team-slug (uses current repo's org) max: 50 # Maximum mentions per message (default: 50) -
Team members include collaborators with any permission level (excluding bots unless explicitly listed)
-
Context mentions include issue/PR authors, assignees, and commenters
-
allowed-teamsresolves team membership from the GitHub API at runtime; bot accounts are excluded. Useorg/team-slugfor cross-org teams or a bareteam-slugto resolve against the current repository's organization. -
allowed-teamsrequiresread:orgscope. The defaultGITHUB_TOKENdoes not include this scope. Provide a classic PAT withread:org, a fine-grained PAT with the "Members" permission (read), or a GitHub App installation token with the "Members" permission (read) viasafe-outputs.github-token:orsafe-outputs.github-app:. If the token lacks the required scope, team lookup fails with a warning and the workflow continues without those team members in the allowlist.
-
-
runs-on:- Runner specification for all safe-outputs jobs (string)- Defaults to
ubuntu-slim(1-vCPU runner) - Examples:
ubuntu-latest,windows-latest,self-hosted - Applies to activation, create-issue, add-comment, and other safe-output jobs
- Defaults to
-
footer:- Global footer control for all safe outputs (boolean, default:true)- When
false, omits visible AI-generated footer content from all created/updated entities (issues, PRs, discussions, releases) while still including XML markers for searchability - Individual safe-output types can override this setting
- When
-
staged:- Preview mode for all safe outputs (boolean)- When
true, emits step summary messages instead of making GitHub API calls; useful for testing without side effects
- When
-
env:- Environment variables passed to all safe output jobs (object)- Values typically reference secrets:
MY_VAR: ${{ secrets.MY_SECRET }}
- Values typically reference secrets:
-
steps:- Custom steps injected into all safe-output jobs, running after repository checkout and before safe-output code (array)-
Useful for installing dependencies or performing setup needed by safe-output logic
-
Example:
safe-outputs: steps: - name: Install custom dependencies run: npm install my-package create-issue:
-
-
max-bot-mentions:- Maximum bot trigger references (e.g.@copilot,@github-actions) allowed in output before all excess are escaped with backticks (integer or expression, default: 10)- Set to
0to escape all bot trigger phrases - Example:
max-bot-mentions: 3
- Set to
-
activation-comments:- Disable all activation and fallback comments (boolean or expression, default:true)- When
false, disables run-started, run-success, run-failure, and PR/issue creation link comments - Supports templatable boolean:
false,true, or GitHub Actions expressions like${{ inputs.activation-comments }}
- When
Templatable Integer Fields: The max, expires, and max-bot-mentions fields (and most other numeric/boolean fields) accept GitHub Actions expression strings in addition to literal values, enabling runtime-configured limits:
safe-outputs:
max-bot-mentions: ${{ inputs.max-mentions }}
create-issue:
max: ${{ inputs.max-issues }}
expires: ${{ inputs.expires-days }}
Fields that influence permission computation (add-comment.discussions, create-pull-request.fallback-as-issue) remain literal booleans.
-
timeout-minutes:- Timeout for the safe-outputs job in minutes (integer, default:45)- Increase for workflows with many sequential safe-output operations (e.g.
push-to-pull-request-branchagainst large repositories)
- Increase for workflows with many sequential safe-output operations (e.g.
-
max-patch-size:- Maximum allowed git patch size in kilobytes (integer, default: 4096 KB = 4 MB)- Patches exceeding this size are rejected to prevent accidental large changes
-
max-patch-files:- Maximum allowed number of unique files in a create-pull-request patch (integer, default: 100)- Counts unique file paths deduplicated across multi-commit patches; reflects how many distinct files the agent is pushing per iteration
- Increase this limit for long-running branches that touch many files
-
group-reports:- Group workflow failure reports as sub-issues (boolean, default:false)- When
true, creates a parent[aw] Failed runsissue that tracks all workflow failures as sub-issues; useful for larger repositories
- When
-
report-failure-as-issue:- Control whether workflow failures are reported as GitHub issues (boolean, expression, or category array; default:true)- When
false, suppresses automatic failure issue creation for this workflow - Supports templatable boolean expressions, e.g.
report-failure-as-issue: ${{ inputs.report-failure-as-issue }} - Use to silence noisy failure reports for workflows where failures are expected or handled externally
- When
-
failure-issue-repo:- Repository to create failure tracking issues in (string, format:"owner/repo")- Defaults to the current repository when not specified
- Use when the current repository has issues disabled:
failure-issue-repo: "myorg/infra-alerts"
-
id-token:- Override the id-token permission for the safe-outputs job (string:"write"or"none")"write": force-enableid-token: writepermission (required for OIDC authentication with cloud providers)"none": suppress automatic detection and prevent addingid-token: writeeven when vault/OIDC actions are detected in steps- Default: auto-detects known OIDC/vault actions (e.g.,
aws-actions/configure-aws-credentials,azure/login,hashicorp/vault-action) and addsid-token: writeautomatically
-
concurrency-group:- Concurrency group for the safe-outputs job (string)- When set, the safe-outputs job uses this concurrency group with
cancel-in-progress: false - Supports GitHub Actions expressions, e.g.,
"safe-outputs-${{ github.repository }}"
- When set, the safe-outputs job uses this concurrency group with
-
needs:- Additional custom workflow jobs the safe-outputs job depends on (array)- Example:
needs: [secrets_fetcher] - Use when the safe-outputs job requires outputs from a custom job defined in
jobs:
- Example:
-
environment:- Override the GitHub deployment environment for the safe-outputs job (string)- Defaults to the top-level
environment:field when not specified - Use when the main job and safe-outputs job need different deployment environments for protection rules
- Defaults to the top-level
-
github-app:- GitHub App credentials for minting installation access tokens (object)-
When configured, generates a token from the app and uses it for all safe output operations (alternative to
github-token) -
Fields:
client-id:- GitHub App client ID (required, e.g.,${{ vars.APP_ID }}). Useapp-id:for legacy compatibility.private-key:- GitHub App private key (required, e.g.,${{ secrets.APP_PRIVATE_KEY }})owner:- Optional App installation owner (defaults to current repository owner)repositories:- Optional list of repositories to grant access to
-
Example:
safe-outputs: github-app: client-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} create-issue:
-
-
threat-detection:- Threat detection configuration (auto-enabled for all safe-outputs workflows)-
Automatically enabled by default; customizable via explicit configuration
-
Fields:
enabled:- Enable/disable threat detection (boolean or expression, default:true)prompt:- Additional instructions appended to threat detection analysis (string)engine:- AI engine for threat detection (engine config orfalseto disable AI detection)steps:- Extra job steps to run before engine execution (array)post-steps:- Extra job steps to run after engine execution (array)max-ai-credits:- Per-run AIC budget for the detection engine (numeric only, no expressions; default${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }})runs-on:- Runner override for the detection job (defaults toagent.runs-on)continue-on-error:- Whentrue(default), detection failures emit a warning and proceed with aneeds-reviewlabel; whenfalse, failures block safe outputs (boolean or expression)
-
Example to disable AI-based detection (use custom steps only):
safe-outputs: threat-detection: engine: false steps: - name: Custom check run: echo "Custom threat check"
-
Output Variables
The safe-outputs job emits named step outputs for the first successful result of each type:
| Safe Output | Step Output Variables |
|---|---|
create-issue | created_issue_number, created_issue_url |
create-pull-request | created_pr_number, created_pr_url |
add-comment | comment_id, comment_url |
push-to-pull-request-branch | push_commit_sha, push_commit_url |