From 5aaa87d2e12fa84c7e789566e384cbd8d50f1e5a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Feb 2026 18:05:15 +0000
Subject: [PATCH 1/7] Initial plan
From 2054346f16cb260c16301ed57fb53a35771b7261 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Feb 2026 18:11:29 +0000
Subject: [PATCH 2/7] Initial investigation of safe-outputs github-token issue
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
---
test-multiple-tokens.md | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 test-multiple-tokens.md
diff --git a/test-multiple-tokens.md b/test-multiple-tokens.md
new file mode 100644
index 00000000000..6d011c3dfce
--- /dev/null
+++ b/test-multiple-tokens.md
@@ -0,0 +1,20 @@
+---
+name: Test Multiple Tokens
+on:
+ workflow_dispatch:
+engine: copilot
+safe-outputs:
+ create-issue:
+ github-token: ${{ secrets.AGENT_GITHUB_TOKEN }}
+ title-prefix: '[dependabot-burner] '
+ assignees: ['copilot']
+ max: 10
+ update-project:
+ github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }}
+ project: "https://github.com/orgs/my-mona-org/projects/1"
+ max: 50
+---
+
+# Test Multiple GitHub Tokens
+
+Test workflow to investigate why different github-tokens in safe-outputs don't compile.
From d7e2004dcbd36a3294eb5bde489f2e17efc26931 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Feb 2026 18:16:13 +0000
Subject: [PATCH 3/7] Add github-token support to all safe-output handler
schemas and configs
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
---
pkg/parser/schemas/main_workflow_schema.json | 1722 ++++++++++++++----
pkg/workflow/compiler_safe_outputs_config.go | 20 +
test-multiple-tokens.lock.yml | 1234 +++++++++++++
3 files changed, 2659 insertions(+), 317 deletions(-)
create mode 100644 test-multiple-tokens.lock.yml
diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json
index bcf50136d64..93dc1a44935 100644
--- a/pkg/parser/schemas/main_workflow_schema.json
+++ b/pkg/parser/schemas/main_workflow_schema.json
@@ -5,25 +5,36 @@
"description": "JSON Schema for validating agentic workflow frontmatter configuration",
"version": "1.0.0",
"type": "object",
- "required": ["on"],
+ "required": [
+ "on"
+ ],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"description": "Workflow name that appears in the GitHub Actions interface. If not specified, defaults to the filename without extension.",
- "examples": ["Copilot Agent PR Analysis", "Dev Hawk", "Smoke Claude"]
+ "examples": [
+ "Copilot Agent PR Analysis",
+ "Dev Hawk",
+ "Smoke Claude"
+ ]
},
"description": {
"type": "string",
"maxLength": 10000,
"description": "Optional workflow description that is rendered as a comment in the generated GitHub Actions YAML file (.lock.yml)",
- "examples": ["Quickstart for using the GitHub Actions library"]
+ "examples": [
+ "Quickstart for using the GitHub Actions library"
+ ]
},
"source": {
"type": "string",
"description": "Optional source reference indicating where this workflow was added from. Format: owner/repo/path@ref (e.g., githubnext/agentics/workflows/ci-doctor.md@v1.0.0). Rendered as a comment in the generated lock file.",
- "examples": ["githubnext/agentics/workflows/ci-doctor.md", "githubnext/agentics/workflows/daily-perf-improver.md@1f181b37d3fe5862ab590648f25a292e345b5de6"]
+ "examples": [
+ "githubnext/agentics/workflows/ci-doctor.md",
+ "githubnext/agentics/workflows/daily-perf-improver.md@1f181b37d3fe5862ab590648f25a292e345b5de6"
+ ]
},
"tracker-id": {
"type": "string",
@@ -31,7 +42,11 @@
"maxLength": 128,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Optional tracker identifier to tag all created assets (issues, discussions, comments, pull requests). Must be at least 8 characters and contain only alphanumeric characters, hyphens, and underscores. This identifier will be inserted in the body/description of all created assets to enable searching and retrieving assets associated with this workflow.",
- "examples": ["workflow-2024-q1", "team-alpha-bot", "security_audit_v2"]
+ "examples": [
+ "workflow-2024-q1",
+ "team-alpha-bot",
+ "security_audit_v2"
+ ]
},
"labels": {
"type": "array",
@@ -41,9 +56,18 @@
"minLength": 1
},
"examples": [
- ["automation", "security"],
- ["docs", "maintenance"],
- ["ci", "testing"]
+ [
+ "automation",
+ "security"
+ ],
+ [
+ "docs",
+ "maintenance"
+ ],
+ [
+ "ci",
+ "testing"
+ ]
]
},
"metadata": {
@@ -77,7 +101,9 @@
{
"type": "object",
"description": "Import specification with path and optional inputs",
- "required": ["path"],
+ "required": [
+ "path"
+ ],
"additionalProperties": false,
"properties": {
"path": {
@@ -106,10 +132,21 @@
]
},
"examples": [
- ["shared/jqschema.md", "shared/reporting.md"],
- ["shared/mcp/gh-aw.md", "shared/jqschema.md", "shared/reporting.md"],
- ["../instructions/documentation.instructions.md"],
- [".github/agents/my-agent.md"],
+ [
+ "shared/jqschema.md",
+ "shared/reporting.md"
+ ],
+ [
+ "shared/mcp/gh-aw.md",
+ "shared/jqschema.md",
+ "shared/reporting.md"
+ ],
+ [
+ "../instructions/documentation.instructions.md"
+ ],
+ [
+ ".github/agents/my-agent.md"
+ ],
[
{
"path": "shared/discussions-data-fetch.md",
@@ -125,12 +162,17 @@
"examples": [
{
"issues": {
- "types": ["opened"]
+ "types": [
+ "opened"
+ ]
}
},
{
"pull_request": {
- "types": ["opened", "synchronize"]
+ "types": [
+ "opened",
+ "synchronize"
+ ]
}
},
"workflow_dispatch",
@@ -144,7 +186,13 @@
"type": "string",
"minLength": 1,
"description": "Simple trigger event name (e.g., 'push', 'issues', 'pull_request', 'discussion', 'schedule', 'fork', 'create', 'delete', 'public', 'watch', 'workflow_call'), schedule shorthand (e.g., 'daily', 'weekly'), or slash command shorthand (e.g., '/my-bot' expands to slash_command + workflow_dispatch)",
- "examples": ["push", "issues", "workflow_dispatch", "daily", "/my-bot"]
+ "examples": [
+ "push",
+ "issues",
+ "workflow_dispatch",
+ "daily",
+ "/my-bot"
+ ]
},
{
"type": "object",
@@ -196,7 +244,16 @@
{
"type": "string",
"description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.",
- "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"]
+ "enum": [
+ "*",
+ "issues",
+ "issue_comment",
+ "pull_request_comment",
+ "pull_request",
+ "pull_request_review_comment",
+ "discussion",
+ "discussion_comment"
+ ]
},
{
"type": "array",
@@ -205,7 +262,16 @@
"items": {
"type": "string",
"description": "GitHub Actions event name.",
- "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"]
+ "enum": [
+ "*",
+ "issues",
+ "issue_comment",
+ "pull_request_comment",
+ "pull_request",
+ "pull_request_review_comment",
+ "discussion",
+ "discussion_comment"
+ ]
},
"maxItems": 25
}
@@ -262,7 +328,16 @@
{
"type": "string",
"description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.",
- "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"]
+ "enum": [
+ "*",
+ "issues",
+ "issue_comment",
+ "pull_request_comment",
+ "pull_request",
+ "pull_request_review_comment",
+ "discussion",
+ "discussion_comment"
+ ]
},
{
"type": "array",
@@ -271,7 +346,16 @@
"items": {
"type": "string",
"description": "GitHub Actions event name.",
- "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"]
+ "enum": [
+ "*",
+ "issues",
+ "issue_comment",
+ "pull_request_comment",
+ "pull_request",
+ "pull_request_review_comment",
+ "discussion",
+ "discussion_comment"
+ ]
},
"maxItems": 25
}
@@ -336,25 +420,37 @@
},
"oneOf": [
{
- "required": ["branches"],
+ "required": [
+ "branches"
+ ],
"not": {
- "required": ["branches-ignore"]
+ "required": [
+ "branches-ignore"
+ ]
}
},
{
- "required": ["branches-ignore"],
+ "required": [
+ "branches-ignore"
+ ],
"not": {
- "required": ["branches"]
+ "required": [
+ "branches"
+ ]
}
},
{
"not": {
"anyOf": [
{
- "required": ["branches"]
+ "required": [
+ "branches"
+ ]
},
{
- "required": ["branches-ignore"]
+ "required": [
+ "branches-ignore"
+ ]
}
]
}
@@ -364,25 +460,37 @@
{
"oneOf": [
{
- "required": ["paths"],
+ "required": [
+ "paths"
+ ],
"not": {
- "required": ["paths-ignore"]
+ "required": [
+ "paths-ignore"
+ ]
}
},
{
- "required": ["paths-ignore"],
+ "required": [
+ "paths-ignore"
+ ],
"not": {
- "required": ["paths"]
+ "required": [
+ "paths"
+ ]
}
},
{
"not": {
"anyOf": [
{
- "required": ["paths"]
+ "required": [
+ "paths"
+ ]
},
{
- "required": ["paths-ignore"]
+ "required": [
+ "paths-ignore"
+ ]
}
]
}
@@ -502,25 +610,37 @@
"additionalProperties": false,
"oneOf": [
{
- "required": ["branches"],
+ "required": [
+ "branches"
+ ],
"not": {
- "required": ["branches-ignore"]
+ "required": [
+ "branches-ignore"
+ ]
}
},
{
- "required": ["branches-ignore"],
+ "required": [
+ "branches-ignore"
+ ],
"not": {
- "required": ["branches"]
+ "required": [
+ "branches"
+ ]
}
},
{
"not": {
"anyOf": [
{
- "required": ["branches"]
+ "required": [
+ "branches"
+ ]
},
{
- "required": ["branches-ignore"]
+ "required": [
+ "branches-ignore"
+ ]
}
]
}
@@ -530,25 +650,37 @@
{
"oneOf": [
{
- "required": ["paths"],
+ "required": [
+ "paths"
+ ],
"not": {
- "required": ["paths-ignore"]
+ "required": [
+ "paths-ignore"
+ ]
}
},
{
- "required": ["paths-ignore"],
+ "required": [
+ "paths-ignore"
+ ],
"not": {
- "required": ["paths"]
+ "required": [
+ "paths"
+ ]
}
},
{
"not": {
"anyOf": [
{
- "required": ["paths"]
+ "required": [
+ "paths"
+ ]
},
{
- "required": ["paths-ignore"]
+ "required": [
+ "paths-ignore"
+ ]
}
]
}
@@ -567,7 +699,26 @@
"description": "Types of issue events",
"items": {
"type": "string",
- "enum": ["opened", "edited", "deleted", "transferred", "pinned", "unpinned", "closed", "reopened", "assigned", "unassigned", "labeled", "unlabeled", "locked", "unlocked", "milestoned", "demilestoned", "typed", "untyped"]
+ "enum": [
+ "opened",
+ "edited",
+ "deleted",
+ "transferred",
+ "pinned",
+ "unpinned",
+ "closed",
+ "reopened",
+ "assigned",
+ "unassigned",
+ "labeled",
+ "unlabeled",
+ "locked",
+ "unlocked",
+ "milestoned",
+ "demilestoned",
+ "typed",
+ "untyped"
+ ]
}
},
"names": {
@@ -605,7 +756,11 @@
"description": "Types of issue comment events",
"items": {
"type": "string",
- "enum": ["created", "edited", "deleted"]
+ "enum": [
+ "created",
+ "edited",
+ "deleted"
+ ]
}
},
"lock-for-agent": {
@@ -624,7 +779,21 @@
"description": "Types of discussion events",
"items": {
"type": "string",
- "enum": ["created", "edited", "deleted", "transferred", "pinned", "unpinned", "labeled", "unlabeled", "locked", "unlocked", "category_changed", "answered", "unanswered"]
+ "enum": [
+ "created",
+ "edited",
+ "deleted",
+ "transferred",
+ "pinned",
+ "unpinned",
+ "labeled",
+ "unlabeled",
+ "locked",
+ "unlocked",
+ "category_changed",
+ "answered",
+ "unanswered"
+ ]
}
}
}
@@ -639,7 +808,11 @@
"description": "Types of discussion comment events",
"items": {
"type": "string",
- "enum": ["created", "edited", "deleted"]
+ "enum": [
+ "created",
+ "edited",
+ "deleted"
+ ]
}
}
}
@@ -664,7 +837,9 @@
"description": "Cron expression using standard format (e.g., '0 9 * * 1') or fuzzy format (e.g., 'daily', 'daily around 14:00', 'daily between 9:00 and 17:00', 'weekly', 'weekly on monday', 'weekly on friday around 5pm', 'hourly', 'every 2h', 'every 10 minutes'). Fuzzy formats support: daily/weekly schedules with optional time windows, hourly intervals with scattered minutes, interval schedules (minimum 5 minutes), short duration units (m/h/d/w), and UTC timezone offsets (utc+N or utc+HH:MM)."
}
},
- "required": ["cron"],
+ "required": [
+ "cron"
+ ],
"additionalProperties": false
},
"maxItems": 10
@@ -714,7 +889,13 @@
},
"type": {
"type": "string",
- "enum": ["string", "choice", "boolean", "number", "environment"],
+ "enum": [
+ "string",
+ "choice",
+ "boolean",
+ "number",
+ "environment"
+ ],
"description": "Input type. GitHub Actions supports: string (default), boolean, choice (string with predefined options), number, and environment (string referencing a GitHub environment)"
},
"options": {
@@ -748,7 +929,11 @@
"description": "Types of workflow run events",
"items": {
"type": "string",
- "enum": ["completed", "requested", "in_progress"]
+ "enum": [
+ "completed",
+ "requested",
+ "in_progress"
+ ]
}
},
"branches": {
@@ -770,25 +955,37 @@
},
"oneOf": [
{
- "required": ["branches"],
+ "required": [
+ "branches"
+ ],
"not": {
- "required": ["branches-ignore"]
+ "required": [
+ "branches-ignore"
+ ]
}
},
{
- "required": ["branches-ignore"],
+ "required": [
+ "branches-ignore"
+ ],
"not": {
- "required": ["branches"]
+ "required": [
+ "branches"
+ ]
}
},
{
"not": {
"anyOf": [
{
- "required": ["branches"]
+ "required": [
+ "branches"
+ ]
},
{
- "required": ["branches-ignore"]
+ "required": [
+ "branches-ignore"
+ ]
}
]
}
@@ -805,7 +1002,15 @@
"description": "Types of release events",
"items": {
"type": "string",
- "enum": ["published", "unpublished", "created", "edited", "deleted", "prereleased", "released"]
+ "enum": [
+ "published",
+ "unpublished",
+ "created",
+ "edited",
+ "deleted",
+ "prereleased",
+ "released"
+ ]
}
}
}
@@ -820,7 +1025,11 @@
"description": "Types of pull request review comment events",
"items": {
"type": "string",
- "enum": ["created", "edited", "deleted"]
+ "enum": [
+ "created",
+ "edited",
+ "deleted"
+ ]
}
}
}
@@ -835,7 +1044,11 @@
"description": "Types of branch protection rule events",
"items": {
"type": "string",
- "enum": ["created", "edited", "deleted"]
+ "enum": [
+ "created",
+ "edited",
+ "deleted"
+ ]
}
}
}
@@ -850,7 +1063,12 @@
"description": "Types of check run events",
"items": {
"type": "string",
- "enum": ["created", "rerequested", "completed", "requested_action"]
+ "enum": [
+ "created",
+ "rerequested",
+ "completed",
+ "requested_action"
+ ]
}
}
}
@@ -865,7 +1083,9 @@
"description": "Types of check suite events",
"items": {
"type": "string",
- "enum": ["completed"]
+ "enum": [
+ "completed"
+ ]
}
}
}
@@ -958,7 +1178,11 @@
"description": "Types of label events",
"items": {
"type": "string",
- "enum": ["created", "edited", "deleted"]
+ "enum": [
+ "created",
+ "edited",
+ "deleted"
+ ]
}
}
}
@@ -973,7 +1197,9 @@
"description": "Types of merge group events",
"items": {
"type": "string",
- "enum": ["checks_requested"]
+ "enum": [
+ "checks_requested"
+ ]
}
}
}
@@ -988,7 +1214,13 @@
"description": "Types of milestone events",
"items": {
"type": "string",
- "enum": ["created", "closed", "opened", "edited", "deleted"]
+ "enum": [
+ "created",
+ "closed",
+ "opened",
+ "edited",
+ "deleted"
+ ]
}
}
}
@@ -1106,25 +1338,37 @@
"additionalProperties": false,
"oneOf": [
{
- "required": ["branches"],
+ "required": [
+ "branches"
+ ],
"not": {
- "required": ["branches-ignore"]
+ "required": [
+ "branches-ignore"
+ ]
}
},
{
- "required": ["branches-ignore"],
+ "required": [
+ "branches-ignore"
+ ],
"not": {
- "required": ["branches"]
+ "required": [
+ "branches"
+ ]
}
},
{
"not": {
"anyOf": [
{
- "required": ["branches"]
+ "required": [
+ "branches"
+ ]
},
{
- "required": ["branches-ignore"]
+ "required": [
+ "branches-ignore"
+ ]
}
]
}
@@ -1134,25 +1378,37 @@
{
"oneOf": [
{
- "required": ["paths"],
+ "required": [
+ "paths"
+ ],
"not": {
- "required": ["paths-ignore"]
+ "required": [
+ "paths-ignore"
+ ]
}
},
{
- "required": ["paths-ignore"],
+ "required": [
+ "paths-ignore"
+ ],
"not": {
- "required": ["paths"]
+ "required": [
+ "paths"
+ ]
}
},
{
"not": {
"anyOf": [
{
- "required": ["paths"]
+ "required": [
+ "paths"
+ ]
},
{
- "required": ["paths-ignore"]
+ "required": [
+ "paths-ignore"
+ ]
}
]
}
@@ -1171,7 +1427,11 @@
"description": "Types of pull request review events",
"items": {
"type": "string",
- "enum": ["submitted", "edited", "dismissed"]
+ "enum": [
+ "submitted",
+ "edited",
+ "dismissed"
+ ]
}
}
}
@@ -1186,7 +1446,10 @@
"description": "Types of registry package events",
"items": {
"type": "string",
- "enum": ["published", "updated"]
+ "enum": [
+ "published",
+ "updated"
+ ]
}
}
}
@@ -1228,7 +1491,9 @@
"description": "Types of watch events",
"items": {
"type": "string",
- "enum": ["started"]
+ "enum": [
+ "started"
+ ]
}
}
}
@@ -1260,7 +1525,11 @@
},
"type": {
"type": "string",
- "enum": ["string", "number", "boolean"],
+ "enum": [
+ "string",
+ "number",
+ "boolean"
+ ],
"description": "Type of the input parameter"
},
"default": {
@@ -1302,7 +1571,9 @@
},
{
"type": "object",
- "required": ["query"],
+ "required": [
+ "query"
+ ],
"properties": {
"query": {
"type": "string",
@@ -1328,7 +1599,9 @@
},
{
"type": "object",
- "required": ["query"],
+ "required": [
+ "query"
+ ],
"properties": {
"query": {
"type": "string",
@@ -1354,17 +1627,37 @@
"oneOf": [
{
"type": "string",
- "enum": ["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes", "none"]
+ "enum": [
+ "+1",
+ "-1",
+ "laugh",
+ "confused",
+ "heart",
+ "hooray",
+ "rocket",
+ "eyes",
+ "none"
+ ]
},
{
"type": "integer",
- "enum": [1, -1],
+ "enum": [
+ 1,
+ -1
+ ],
"description": "YAML parses +1 and -1 without quotes as integers. These are converted to +1 and -1 strings respectively."
}
],
"default": "eyes",
"description": "AI reaction to add/remove on triggering item (one of: +1, -1, laugh, confused, heart, hooray, rocket, eyes, none). Use 'none' to disable reactions. Defaults to 'eyes' if not specified.",
- "examples": ["eyes", "rocket", "+1", 1, -1, "none"]
+ "examples": [
+ "eyes",
+ "rocket",
+ "+1",
+ 1,
+ -1,
+ "none"
+ ]
}
},
"additionalProperties": false,
@@ -1380,25 +1673,37 @@
{
"command": {
"name": "mergefest",
- "events": ["pull_request_comment"]
+ "events": [
+ "pull_request_comment"
+ ]
}
},
{
"workflow_run": {
- "workflows": ["Dev"],
- "types": ["completed"],
- "branches": ["copilot/**"]
+ "workflows": [
+ "Dev"
+ ],
+ "types": [
+ "completed"
+ ],
+ "branches": [
+ "copilot/**"
+ ]
}
},
{
"pull_request": {
- "types": ["ready_for_review"]
+ "types": [
+ "ready_for_review"
+ ]
},
"workflow_dispatch": null
},
{
"push": {
- "branches": ["main"]
+ "branches": [
+ "main"
+ ]
}
}
]
@@ -1425,7 +1730,10 @@
"oneOf": [
{
"type": "string",
- "enum": ["read-all", "write-all"],
+ "enum": [
+ "read-all",
+ "write-all"
+ ],
"description": "Simple permissions string: 'read-all' (all read permissions) or 'write-all' (all write permissions)"
},
{
@@ -1435,82 +1743,143 @@
"properties": {
"actions": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission for GitHub Actions workflows and runs (read: view workflows, write: manage workflows, none: no access)"
},
"attestations": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission for artifact attestations (read: view attestations, write: create attestations, none: no access)"
},
"checks": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission for repository checks and status checks (read: view checks, write: create/update checks, none: no access)"
},
"contents": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission for repository contents (read: view files, write: modify files/branches, none: no access)"
},
"deployments": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission for repository deployments (read: view deployments, write: create/update deployments, none: no access)"
},
"discussions": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission for repository discussions (read: view discussions, write: create/update discussions, none: no access)"
},
"id-token": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission level for OIDC token requests (read/write/none). Allows workflows to request JWT tokens for cloud provider authentication."
},
"issues": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission for repository issues (read: view issues, write: create/update/close issues, none: no access)"
},
"models": {
"type": "string",
- "enum": ["read", "none"],
+ "enum": [
+ "read",
+ "none"
+ ],
"description": "Permission for GitHub Copilot models (read: access AI models for agentic workflows, none: no access)"
},
"metadata": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission for repository metadata (read: view repository information, write: update repository metadata, none: no access)"
},
"packages": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission level for GitHub Packages (read/write/none). Controls access to publish, modify, or delete packages."
},
"pages": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission level for GitHub Pages (read/write/none). Controls access to deploy and manage GitHub Pages sites."
},
"pull-requests": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission level for pull requests (read/write/none). Controls access to create, edit, review, and manage pull requests."
},
"security-events": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission level for security events (read/write/none). Controls access to view and manage code scanning alerts and security findings."
},
"statuses": {
"type": "string",
- "enum": ["read", "write", "none"],
+ "enum": [
+ "read",
+ "write",
+ "none"
+ ],
"description": "Permission level for commit statuses (read/write/none). Controls access to create and update commit status checks."
},
"all": {
"type": "string",
- "enum": ["read"],
+ "enum": [
+ "read"
+ ],
"description": "Permission shorthand that applies read access to all permission scopes. Can be combined with specific write permissions to override individual scopes. 'write' is not allowed for all."
}
}
@@ -1520,7 +1889,10 @@
"run-name": {
"type": "string",
"description": "Custom name for workflow runs that appears in the GitHub Actions interface (supports GitHub expressions like ${{ github.event.issue.title }})",
- "examples": ["Deploy to ${{ github.event.inputs.environment }}", "Build #${{ github.run_number }}"]
+ "examples": [
+ "Deploy to ${{ github.event.inputs.environment }}",
+ "Build #${{ github.run_number }}"
+ ]
},
"jobs": {
"type": "object",
@@ -1563,10 +1935,14 @@
"additionalProperties": false,
"oneOf": [
{
- "required": ["uses"]
+ "required": [
+ "uses"
+ ]
},
{
- "required": ["run"]
+ "required": [
+ "run"
+ ]
}
],
"properties": {
@@ -1779,17 +2155,26 @@
],
"examples": [
"ubuntu-latest",
- ["ubuntu-latest", "self-hosted"],
+ [
+ "ubuntu-latest",
+ "self-hosted"
+ ],
{
"group": "larger-runners",
- "labels": ["ubuntu-latest-8-cores"]
+ "labels": [
+ "ubuntu-latest-8-cores"
+ ]
}
]
},
"timeout-minutes": {
"type": "integer",
"description": "Workflow timeout in minutes (GitHub Actions standard field). Defaults to 20 minutes for agentic workflows. Has sensible defaults and can typically be omitted.",
- "examples": [5, 10, 30]
+ "examples": [
+ 5,
+ 10,
+ 30
+ ]
},
"timeout_minutes": {
"type": "integer",
@@ -1803,7 +2188,10 @@
{
"type": "string",
"description": "Simple concurrency group name to prevent multiple runs in the same group. Use expressions like '${{ github.workflow }}' for per-workflow isolation or '${{ github.ref }}' for per-branch isolation. Agentic workflows automatically generate enhanced concurrency policies using 'gh-aw-{engine-id}' as the default group to limit concurrent AI workloads across all workflows using the same engine.",
- "examples": ["my-workflow-group", "workflow-${{ github.ref }}"]
+ "examples": [
+ "my-workflow-group",
+ "workflow-${{ github.ref }}"
+ ]
},
{
"type": "object",
@@ -1819,7 +2207,9 @@
"description": "Whether to cancel in-progress workflows in the same concurrency group when a new one starts. Default: false (queue new runs). Set to true for agentic workflows where only the latest run matters (e.g., PR analysis that becomes stale when new commits are pushed)."
}
},
- "required": ["group"],
+ "required": [
+ "group"
+ ],
"examples": [
{
"group": "dev-workflow-${{ github.ref }}",
@@ -1888,7 +2278,9 @@
{
"type": "object",
"description": "Secret with metadata",
- "required": ["value"],
+ "required": [
+ "value"
+ ],
"properties": {
"value": {
"type": "string",
@@ -1940,7 +2332,9 @@
"description": "A deployment URL"
}
},
- "required": ["name"],
+ "required": [
+ "name"
+ ],
"additionalProperties": false
}
]
@@ -2008,7 +2402,9 @@
"description": "Additional Docker container options"
}
},
- "required": ["image"],
+ "required": [
+ "image"
+ ],
"additionalProperties": false
}
]
@@ -2078,7 +2474,9 @@
"description": "Additional Docker container options"
}
},
- "required": ["image"],
+ "required": [
+ "image"
+ ],
"additionalProperties": false
}
]
@@ -2090,13 +2488,24 @@
"examples": [
"defaults",
{
- "allowed": ["defaults", "github"]
+ "allowed": [
+ "defaults",
+ "github"
+ ]
},
{
- "allowed": ["defaults", "python", "node", "*.example.com"]
+ "allowed": [
+ "defaults",
+ "python",
+ "node",
+ "*.example.com"
+ ]
},
{
- "allowed": ["api.openai.com", "*.github.com"],
+ "allowed": [
+ "api.openai.com",
+ "*.github.com"
+ ],
"firewall": {
"version": "v1.0.0",
"log-level": "debug"
@@ -2106,7 +2515,9 @@
"oneOf": [
{
"type": "string",
- "enum": ["defaults"],
+ "enum": [
+ "defaults"
+ ],
"description": "Use default network permissions (basic infrastructure: certificates, JSON schema, Ubuntu, etc.)"
},
{
@@ -2146,7 +2557,9 @@
},
{
"type": "string",
- "enum": ["disable"],
+ "enum": [
+ "disable"
+ ],
"description": "Disable AWF firewall (triggers warning if allowed != *, error in strict mode if allowed is not * or engine does not support firewall)"
},
{
@@ -2161,14 +2574,27 @@
}
},
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "AWF version to use (empty = latest release). Can be a string (e.g., 'v1.0.0', 'latest') or number (e.g., 20, 3.11). Numeric values are automatically converted to strings at runtime.",
- "examples": ["v1.0.0", "latest", 20, 3.11]
+ "examples": [
+ "v1.0.0",
+ "latest",
+ 20,
+ 3.11
+ ]
},
"log-level": {
"type": "string",
"description": "AWF log level (default: info). Valid values: debug, info, warn, error",
- "enum": ["debug", "info", "warn", "error"]
+ "enum": [
+ "debug",
+ "info",
+ "warn",
+ "error"
+ ]
},
"ssl-bump": {
"type": "boolean",
@@ -2183,7 +2609,12 @@
"pattern": "^https://.*",
"description": "HTTPS URL pattern with optional wildcards (e.g., 'https://github.com/githubnext/*')"
},
- "examples": [["https://github.com/githubnext/*", "https://api.github.com/repos/*"]]
+ "examples": [
+ [
+ "https://github.com/githubnext/*",
+ "https://api.github.com/repos/*"
+ ]
+ ]
}
},
"additionalProperties": false
@@ -2204,7 +2635,12 @@
},
{
"type": "string",
- "enum": ["default", "sandbox-runtime", "awf", "srt"],
+ "enum": [
+ "default",
+ "sandbox-runtime",
+ "awf",
+ "srt"
+ ],
"description": "Legacy string format for sandbox type: 'default' for no sandbox, 'sandbox-runtime' or 'srt' for Anthropic Sandbox Runtime, 'awf' for Agent Workflow Firewall"
},
{
@@ -2213,7 +2649,12 @@
"properties": {
"type": {
"type": "string",
- "enum": ["default", "sandbox-runtime", "awf", "srt"],
+ "enum": [
+ "default",
+ "sandbox-runtime",
+ "awf",
+ "srt"
+ ],
"description": "Legacy sandbox type field (use agent instead)"
},
"agent": {
@@ -2222,7 +2663,10 @@
"oneOf": [
{
"type": "string",
- "enum": ["awf", "srt"],
+ "enum": [
+ "awf",
+ "srt"
+ ],
"description": "Sandbox type: 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime"
},
{
@@ -2231,12 +2675,18 @@
"properties": {
"id": {
"type": "string",
- "enum": ["awf", "srt"],
+ "enum": [
+ "awf",
+ "srt"
+ ],
"description": "Agent identifier (replaces 'type' field in new format): 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime"
},
"type": {
"type": "string",
- "enum": ["awf", "srt"],
+ "enum": [
+ "awf",
+ "srt"
+ ],
"description": "Legacy: Sandbox type to use (use 'id' instead)"
},
"command": {
@@ -2265,7 +2715,12 @@
"pattern": "^[^:]+:[^:]+:(ro|rw)$",
"description": "Mount specification in format 'source:destination:mode'"
},
- "examples": [["/host/data:/data:ro", "/usr/local/bin/custom-tool:/usr/local/bin/custom-tool:ro"]]
+ "examples": [
+ [
+ "/host/data:/data:ro",
+ "/usr/local/bin/custom-tool:/usr/local/bin/custom-tool:ro"
+ ]
+ ]
},
"config": {
"type": "object",
@@ -2380,14 +2835,24 @@
"description": "Container image for the MCP gateway executable (required)"
},
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Optional version/tag for the container image (e.g., 'latest', 'v1.0.0')",
- "examples": ["latest", "v1.0.0"]
+ "examples": [
+ "latest",
+ "v1.0.0"
+ ]
},
"entrypoint": {
"type": "string",
"description": "Optional custom entrypoint for the MCP gateway container. Overrides the container's default entrypoint.",
- "examples": ["/bin/bash", "/custom/start.sh", "/usr/bin/env"]
+ "examples": [
+ "/bin/bash",
+ "/custom/start.sh",
+ "/usr/bin/env"
+ ]
},
"args": {
"type": "array",
@@ -2411,7 +2876,12 @@
"pattern": "^[^:]+:[^:]+:(ro|rw)$",
"description": "Mount specification in format 'source:destination:mode'"
},
- "examples": [["/host/data:/container/data:ro", "/host/config:/container/config:rw"]]
+ "examples": [
+ [
+ "/host/data:/container/data:ro",
+ "/host/config:/container/config:rw"
+ ]
+ ]
},
"env": {
"type": "object",
@@ -2436,11 +2906,16 @@
},
"domain": {
"type": "string",
- "enum": ["localhost", "host.docker.internal"],
+ "enum": [
+ "localhost",
+ "host.docker.internal"
+ ],
"description": "Gateway domain for URL generation (default: 'host.docker.internal' when agent is enabled, 'localhost' when disabled)"
}
},
- "required": ["container"],
+ "required": [
+ "container"
+ ],
"additionalProperties": false
}
},
@@ -2461,7 +2936,10 @@
"type": "srt",
"config": {
"filesystem": {
- "allowWrite": [".", "/tmp"]
+ "allowWrite": [
+ ".",
+ "/tmp"
+ ]
}
}
}
@@ -2483,9 +2961,12 @@
]
},
"plugins": {
- "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.",
+ "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.",
"examples": [
- ["github/copilot-plugin", "acme/custom-tools"],
+ [
+ "github/copilot-plugin",
+ "acme/custom-tools"
+ ],
[
"github/simple-plugin",
{
@@ -2498,7 +2979,10 @@
}
],
{
- "repos": ["github/copilot-plugin", "acme/custom-tools"],
+ "repos": [
+ "github/copilot-plugin",
+ "acme/custom-tools"
+ ],
"github-token": "${{ secrets.CUSTOM_PLUGIN_TOKEN }}"
}
],
@@ -2516,7 +3000,9 @@
{
"type": "object",
"description": "Plugin configuration with ID and optional MCP settings for environment variables",
- "required": ["id"],
+ "required": [
+ "id"
+ ],
"properties": {
"id": {
"type": "string",
@@ -2552,7 +3038,9 @@
{
"type": "object",
"description": "Plugin configuration with custom GitHub token. Repos can be either strings or objects with MCP configuration.",
- "required": ["repos"],
+ "required": [
+ "repos"
+ ],
"properties": {
"repos": {
"type": "array",
@@ -2567,7 +3055,9 @@
{
"type": "object",
"description": "Plugin configuration with ID and optional MCP settings",
- "required": ["id"],
+ "required": [
+ "id"
+ ],
"properties": {
"id": {
"type": "string",
@@ -2597,7 +3087,9 @@
"github-token": {
"type": "string",
"description": "Custom GitHub token expression to use for plugin installation. Overrides the default cascading token resolution (GH_AW_PLUGINS_TOKEN -> GH_AW_GITHUB_TOKEN -> GITHUB_TOKEN).",
- "examples": ["${{ secrets.CUSTOM_PLUGIN_TOKEN }}"]
+ "examples": [
+ "${{ secrets.CUSTOM_PLUGIN_TOKEN }}"
+ ]
}
},
"additionalProperties": false
@@ -2607,7 +3099,10 @@
"if": {
"type": "string",
"description": "Conditional execution expression",
- "examples": ["${{ github.event.workflow_run.event == 'workflow_dispatch' }}", "${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}"]
+ "examples": [
+ "${{ github.event.workflow_run.event == 'workflow_dispatch' }}",
+ "${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}"
+ ]
},
"steps": {
"description": "Custom workflow steps",
@@ -2680,7 +3175,7 @@
[
{
"name": "Verify Post-Steps Execution",
- "run": "echo \"✅ Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n"
+ "run": "echo \"\u2705 Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n"
},
{
"name": "Upload Test Results",
@@ -2725,7 +3220,10 @@
"filesystem": {
"type": "stdio",
"command": "npx",
- "args": ["-y", "@modelcontextprotocol/server-filesystem"]
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem"
+ ]
}
},
{
@@ -2802,13 +3300,24 @@
},
"mode": {
"type": "string",
- "enum": ["local", "remote"],
+ "enum": [
+ "local",
+ "remote"
+ ],
"description": "MCP server mode: 'local' (Docker-based, default) or 'remote' (hosted at api.githubcopilot.com)"
},
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Optional version specification for the GitHub MCP server (used with 'local' type). Can be a string (e.g., 'v1.0.0', 'latest') or number (e.g., 20, 3.11). Numeric values are automatically converted to strings at runtime.",
- "examples": ["v1.0.0", "latest", 20, 3.11]
+ "examples": [
+ "v1.0.0",
+ "latest",
+ 20,
+ 3.11
+ ]
},
"args": {
"type": "array",
@@ -2873,7 +3382,15 @@
"pattern": "^[^:]+:[^:]+(:(ro|rw))?$",
"description": "Mount specification in format 'host:container:mode'"
},
- "examples": [["/data:/data:ro", "/tmp:/tmp:rw"], ["/opt:/opt:ro"]]
+ "examples": [
+ [
+ "/data:/data:ro",
+ "/tmp:/tmp:rw"
+ ],
+ [
+ "/opt:/opt:ro"
+ ]
+ ]
},
"app": {
"type": "object",
@@ -2899,7 +3416,10 @@
}
}
},
- "required": ["app-id", "private-key"],
+ "required": [
+ "app-id",
+ "private-key"
+ ],
"additionalProperties": false,
"examples": [
{
@@ -2909,7 +3429,10 @@
{
"app-id": "${{ vars.APP_ID }}",
"private-key": "${{ secrets.APP_PRIVATE_KEY }}",
- "repositories": ["repo1", "repo2"]
+ "repositories": [
+ "repo1",
+ "repo2"
+ ]
}
]
}
@@ -2917,16 +3440,30 @@
"additionalProperties": false,
"examples": [
{
- "toolsets": ["pull_requests", "actions", "repos"]
+ "toolsets": [
+ "pull_requests",
+ "actions",
+ "repos"
+ ]
},
{
- "allowed": ["search_pull_requests", "pull_request_read", "list_pull_requests", "get_file_contents", "list_commits", "get_commit"]
+ "allowed": [
+ "search_pull_requests",
+ "pull_request_read",
+ "list_pull_requests",
+ "get_file_contents",
+ "list_commits",
+ "get_commit"
+ ]
},
{
"read-only": true
},
{
- "toolsets": ["pull_requests", "repos"]
+ "toolsets": [
+ "pull_requests",
+ "repos"
+ ]
}
]
}
@@ -2934,14 +3471,25 @@
"examples": [
null,
{
- "toolsets": ["pull_requests", "actions", "repos"]
+ "toolsets": [
+ "pull_requests",
+ "actions",
+ "repos"
+ ]
},
{
- "allowed": ["search_pull_requests", "pull_request_read", "get_file_contents"]
+ "allowed": [
+ "search_pull_requests",
+ "pull_request_read",
+ "get_file_contents"
+ ]
},
{
"read-only": true,
- "toolsets": ["repos", "issues"]
+ "toolsets": [
+ "repos",
+ "issues"
+ ]
},
false
]
@@ -2968,10 +3516,36 @@
],
"examples": [
true,
- ["git fetch", "git checkout", "git status", "git diff", "git log", "make recompile", "make fmt", "make lint", "make test-unit", "cat", "echo", "ls"],
- ["echo", "ls", "cat"],
- ["gh pr list *", "gh search prs *", "jq *"],
- ["date *", "echo *", "cat", "ls"]
+ [
+ "git fetch",
+ "git checkout",
+ "git status",
+ "git diff",
+ "git log",
+ "make recompile",
+ "make fmt",
+ "make lint",
+ "make test-unit",
+ "cat",
+ "echo",
+ "ls"
+ ],
+ [
+ "echo",
+ "ls",
+ "cat"
+ ],
+ [
+ "gh pr list *",
+ "gh search prs *",
+ "jq *"
+ ],
+ [
+ "date *",
+ "echo *",
+ "cat",
+ "ls"
+ ]
]
},
"web-fetch": {
@@ -3048,9 +3622,16 @@
"description": "Playwright tool configuration with custom version and domain restrictions",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Optional Playwright container version (e.g., 'v1.41.0', 1.41, 20). Numeric values are automatically converted to strings at runtime.",
- "examples": ["v1.41.0", 1.41, 20]
+ "examples": [
+ "v1.41.0",
+ 1.41,
+ 20
+ ]
},
"allowed_domains": {
"description": "Domains allowed for Playwright browser network access. Defaults to localhost only for security.",
@@ -3092,7 +3673,10 @@
"description": "Enable agentic-workflows tool with default settings (same as true)"
}
],
- "examples": [true, null]
+ "examples": [
+ true,
+ null
+ ]
},
"cache-memory": {
"description": "Cache memory MCP configuration for persistent memory storage",
@@ -3168,7 +3752,10 @@
"description": "If true, only restore the cache without saving it back. Uses actions/cache/restore instead of actions/cache. No artifact upload step will be generated."
}
},
- "required": ["id", "key"],
+ "required": [
+ "id",
+ "key"
+ ],
"additionalProperties": false
},
"minItems": 1,
@@ -3209,7 +3796,11 @@
"type": "integer",
"minimum": 1,
"description": "Timeout in seconds for tool/MCP server operations. Applies to all tools and MCP servers if supported by the engine. Default varies by engine (Claude: 60s, Codex: 120s).",
- "examples": [60, 120, 300]
+ "examples": [
+ 60,
+ 120,
+ 300
+ ]
},
"startup-timeout": {
"type": "integer",
@@ -3228,7 +3819,14 @@
"description": "Short syntax: array of language identifiers to enable (e.g., [\"go\", \"typescript\"])",
"items": {
"type": "string",
- "enum": ["go", "typescript", "python", "java", "rust", "csharp"]
+ "enum": [
+ "go",
+ "typescript",
+ "python",
+ "java",
+ "rust",
+ "csharp"
+ ]
}
},
{
@@ -3236,14 +3834,24 @@
"description": "Serena configuration with custom version and language-specific settings",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Optional Serena MCP version. Numeric values are automatically converted to strings at runtime.",
- "examples": ["latest", "0.1.0", 1.0]
+ "examples": [
+ "latest",
+ "0.1.0",
+ 1.0
+ ]
},
"mode": {
"type": "string",
"description": "Serena execution mode: 'docker' (default, runs in container) or 'local' (runs locally with uvx and HTTP transport)",
- "enum": ["docker", "local"],
+ "enum": [
+ "docker",
+ "local"
+ ],
"default": "docker"
},
"args": {
@@ -3267,7 +3875,10 @@
"type": "object",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Go version (e.g., \"1.21\", 1.21)"
},
"go-mod-file": {
@@ -3294,7 +3905,10 @@
"type": "object",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Node.js version for TypeScript (e.g., \"22\", 22)"
}
},
@@ -3313,7 +3927,10 @@
"type": "object",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Python version (e.g., \"3.12\", 3.12)"
}
},
@@ -3332,7 +3949,10 @@
"type": "object",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Java version (e.g., \"21\", 21)"
}
},
@@ -3351,7 +3971,10 @@
"type": "object",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Rust version (e.g., \"stable\", \"1.75\")"
}
},
@@ -3370,7 +3993,10 @@
"type": "object",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": ".NET version for C# (e.g., \"8.0\", 8.0)"
}
},
@@ -3602,11 +4228,19 @@
},
"type": {
"type": "string",
- "enum": ["stdio", "http", "remote", "local"],
+ "enum": [
+ "stdio",
+ "http",
+ "remote",
+ "local"
+ ],
"description": "MCP connection type. Use 'stdio' for command-based or container-based servers, 'http' for HTTP-based servers. 'local' is an alias for 'stdio' and is normalized during parsing."
},
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Version of the MCP server"
},
"toolsets": {
@@ -3643,7 +4277,10 @@
"registry": {
"type": "string",
"description": "URI to installation location from MCP registry",
- "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown", "https://registry.npmjs.org/@my/tool"]
+ "examples": [
+ "https://api.mcp.github.com/v0/servers/microsoft/markitdown",
+ "https://registry.npmjs.org/@my/tool"
+ ]
},
"allowed": {
"type": "array",
@@ -3651,12 +4288,28 @@
"type": "string"
},
"description": "List of allowed tool names (restricts which tools from the MCP server can be used)",
- "examples": [["*"], ["store_memory", "retrieve_memory"], ["create-issue", "add-comment"]]
+ "examples": [
+ [
+ "*"
+ ],
+ [
+ "store_memory",
+ "retrieve_memory"
+ ],
+ [
+ "create-issue",
+ "add-comment"
+ ]
+ ]
},
"entrypoint": {
"type": "string",
"description": "Optional entrypoint override for container (equivalent to docker run --entrypoint)",
- "examples": ["/bin/sh", "/custom/entrypoint.sh", "python"]
+ "examples": [
+ "/bin/sh",
+ "/custom/entrypoint.sh",
+ "python"
+ ]
},
"mounts": {
"type": "array",
@@ -3665,7 +4318,15 @@
"pattern": "^[^:]+:[^:]+:(ro|rw)$"
},
"description": "Volume mounts for container in format 'source:dest:mode' where mode is 'ro' or 'rw'",
- "examples": [["/tmp/data:/data:ro"], ["/workspace:/workspace:rw", "/config:/config:ro"]]
+ "examples": [
+ [
+ "/tmp/data:/data:ro"
+ ],
+ [
+ "/workspace:/workspace:rw",
+ "/config:/config:ro"
+ ]
+ ]
}
},
"additionalProperties": true
@@ -3733,17 +4394,25 @@
"description": "If true, only checks if cache entry exists and skips download"
}
},
- "required": ["key", "path"],
+ "required": [
+ "key",
+ "path"
+ ],
"additionalProperties": false,
"examples": [
{
"key": "node-modules-${{ hashFiles('package-lock.json') }}",
"path": "node_modules",
- "restore-keys": ["node-modules-"]
+ "restore-keys": [
+ "node-modules-"
+ ]
},
{
"key": "build-cache-${{ github.sha }}",
- "path": ["dist", ".cache"],
+ "path": [
+ "dist",
+ ".cache"
+ ],
"restore-keys": "build-cache-",
"fail-on-cache-miss": false
}
@@ -3804,7 +4473,10 @@
"description": "If true, only checks if cache entry exists and skips download"
}
},
- "required": ["key", "path"],
+ "required": [
+ "key",
+ "path"
+ ],
"additionalProperties": false
}
}
@@ -3818,13 +4490,18 @@
{
"create-issue": {
"title-prefix": "[AI] ",
- "labels": ["automation", "ai-generated"]
+ "labels": [
+ "automation",
+ "ai-generated"
+ ]
}
},
{
"create-pull-request": {
"title-prefix": "[Bot] ",
- "labels": ["bot"]
+ "labels": [
+ "bot"
+ ]
}
},
{
@@ -3847,7 +4524,19 @@
"type": "string",
"pattern": "^(repo|[a-zA-Z0-9][-a-zA-Z0-9]{0,38}/[a-zA-Z0-9._-]+)$"
},
- "examples": [["repo"], ["repo", "octocat/hello-world"], ["microsoft/vscode", "microsoft/typescript"]]
+ "examples": [
+ [
+ "repo"
+ ],
+ [
+ "repo",
+ "octocat/hello-world"
+ ],
+ [
+ "microsoft/vscode",
+ "microsoft/typescript"
+ ]
+ ]
},
"create-issue": {
"oneOf": [
@@ -3895,6 +4584,10 @@
"minimum": 1,
"maximum": 100
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target-repo": {
"type": "string",
"description": "Target repository in format 'owner/repo' for cross-repository issue creation. Takes precedence over trial target repo settings."
@@ -3920,7 +4613,9 @@
},
{
"type": "boolean",
- "enum": [false],
+ "enum": [
+ false
+ ],
"description": "Set to false to explicitly disable expiration"
}
],
@@ -3941,21 +4636,33 @@
"examples": [
{
"title-prefix": "[ca] ",
- "labels": ["automation", "dependencies"],
+ "labels": [
+ "automation",
+ "dependencies"
+ ],
"assignees": "copilot"
},
{
"title-prefix": "[duplicate-code] ",
- "labels": ["code-quality", "automated-analysis"],
+ "labels": [
+ "code-quality",
+ "automated-analysis"
+ ],
"assignees": "copilot"
},
{
- "allowed-repos": ["org/other-repo", "org/another-repo"],
+ "allowed-repos": [
+ "org/other-repo",
+ "org/another-repo"
+ ],
"title-prefix": "[cross-repo] "
},
{
"title-prefix": "[weekly-report] ",
- "labels": ["report", "automation"],
+ "labels": [
+ "report",
+ "automation"
+ ],
"close-older-issues": true
}
]
@@ -4055,7 +4762,9 @@
{
"type": "object",
"description": "Configuration for managing GitHub Projects v2 boards. Smart tool that can add issue/PR items and update custom fields on existing items. By default it is update-only: if the project does not exist, the job fails with instructions to create it manually. To allow workflows to create missing projects, explicitly opt in via the agent output field create_if_missing=true (and/or provide a github-token override). NOTE: Projects v2 requires a Personal Access Token (PAT) or GitHub App token with appropriate permissions; the GITHUB_TOKEN cannot be used for Projects v2. Safe output items produced by the agent use type=update_project. Configuration also supports an optional views array for declaring project views to create. Safe output items produced by the agent use type=update_project and may include: project (board name), content_type (issue|pull_request), content_number, fields, and create_if_missing.",
- "required": ["project"],
+ "required": [
+ "project"
+ ],
"properties": {
"max": {
"type": "integer",
@@ -4071,7 +4780,10 @@
"type": "string",
"description": "Target project URL for update-project operations. This is required in the configuration for documentation purposes. Agent messages MUST explicitly include the project field in their output - the configured value is not used as a fallback. Must be a valid GitHub Projects v2 URL.",
"pattern": "^https://github\\.com/(users|orgs)/([^/]+|<[A-Z_]+>)/projects/(\\d+|<[A-Z_]+>)$",
- "examples": ["https://github.com/orgs/myorg/projects/123", "https://github.com/users/username/projects/456"]
+ "examples": [
+ "https://github.com/orgs/myorg/projects/123",
+ "https://github.com/users/username/projects/456"
+ ]
},
"views": {
"type": "array",
@@ -4079,7 +4791,10 @@
"items": {
"type": "object",
"description": "View configuration for creating project views",
- "required": ["name", "layout"],
+ "required": [
+ "name",
+ "layout"
+ ],
"properties": {
"name": {
"type": "string",
@@ -4087,7 +4802,11 @@
},
"layout": {
"type": "string",
- "enum": ["table", "board", "roadmap"],
+ "enum": [
+ "table",
+ "board",
+ "roadmap"
+ ],
"description": "The layout type of the view"
},
"filter": {
@@ -4114,7 +4833,10 @@
"description": "Optional array of project custom fields to create up-front.",
"items": {
"type": "object",
- "required": ["name", "data-type"],
+ "required": [
+ "name",
+ "data-type"
+ ],
"properties": {
"name": {
"type": "string",
@@ -4122,7 +4844,13 @@
},
"data-type": {
"type": "string",
- "enum": ["DATE", "TEXT", "NUMBER", "SINGLE_SELECT", "ITERATION"],
+ "enum": [
+ "DATE",
+ "TEXT",
+ "NUMBER",
+ "SINGLE_SELECT",
+ "ITERATION"
+ ],
"description": "The GitHub Projects v2 custom field type"
},
"options": {
@@ -4185,7 +4913,10 @@
"items": {
"type": "object",
"description": "View configuration for creating project views",
- "required": ["name", "layout"],
+ "required": [
+ "name",
+ "layout"
+ ],
"properties": {
"name": {
"type": "string",
@@ -4193,7 +4924,11 @@
},
"layout": {
"type": "string",
- "enum": ["table", "board", "roadmap"],
+ "enum": [
+ "table",
+ "board",
+ "roadmap"
+ ],
"description": "The layout type of the view"
},
"filter": {
@@ -4220,7 +4955,10 @@
"description": "Optional array of project custom fields to create automatically after project creation.",
"items": {
"type": "object",
- "required": ["name", "data-type"],
+ "required": [
+ "name",
+ "data-type"
+ ],
"properties": {
"name": {
"type": "string",
@@ -4228,7 +4966,13 @@
},
"data-type": {
"type": "string",
- "enum": ["DATE", "TEXT", "NUMBER", "SINGLE_SELECT", "ITERATION"],
+ "enum": [
+ "DATE",
+ "TEXT",
+ "NUMBER",
+ "SINGLE_SELECT",
+ "ITERATION"
+ ],
"description": "The GitHub Projects v2 custom field type"
},
"options": {
@@ -4250,7 +4994,9 @@
"description": "Enable project creation with default configuration (max=1)"
},
{
- "enum": [null],
+ "enum": [
+ null
+ ],
"description": "Alternative null value syntax"
}
],
@@ -4264,7 +5010,9 @@
{
"type": "object",
"description": "Configuration for creating GitHub Project status updates. Status updates provide stakeholder communication and historical record of project progress. Requires a Personal Access Token (PAT) or GitHub App token with Projects: Read+Write permission. The GITHUB_TOKEN cannot be used for Projects v2. Status updates are created on the specified project board and appear in the Updates tab. Typically used by orchestrators to post run summaries with progress, findings, and next steps.",
- "required": ["project"],
+ "required": [
+ "project"
+ ],
"properties": {
"max": {
"type": "integer",
@@ -4280,7 +5028,10 @@
"type": "string",
"description": "Target project URL for status update operations. This is required in the configuration for documentation purposes. Agent messages MUST explicitly include the project field in their output - the configured value is not used as a fallback. Must be a valid GitHub Projects v2 URL.",
"pattern": "^https://github\\.com/(users|orgs)/([^/]+|<[A-Z_]+>)/projects/(\\d+|<[A-Z_]+>)$",
- "examples": ["https://github.com/orgs/myorg/projects/123", "https://github.com/users/username/projects/456"]
+ "examples": [
+ "https://github.com/orgs/myorg/projects/123",
+ "https://github.com/users/username/projects/456"
+ ]
}
},
"additionalProperties": false,
@@ -4312,9 +5063,16 @@
"description": "Optional prefix for the discussion title"
},
"category": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Optional discussion category. Can be a category ID (string or numeric value), category name, or category slug/route. If not specified, uses the first available category. Matched first against category IDs, then against category names, then against category slugs. Numeric values are automatically converted to strings at runtime.",
- "examples": ["General", "audits", 123456789]
+ "examples": [
+ "General",
+ "audits",
+ 123456789
+ ]
},
"labels": {
"type": "array",
@@ -4336,6 +5094,10 @@
"minimum": 1,
"maximum": 100
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target-repo": {
"type": "string",
"description": "Target repository in format 'owner/repo' for cross-repository discussion creation. Takes precedence over trial target repo settings."
@@ -4371,7 +5133,9 @@
},
{
"type": "boolean",
- "enum": [false],
+ "enum": [
+ false
+ ],
"description": "Set to false to explicitly disable expiration"
}
],
@@ -4398,12 +5162,17 @@
"close-older-discussions": true
},
{
- "labels": ["weekly-report", "automation"],
+ "labels": [
+ "weekly-report",
+ "automation"
+ ],
"category": "reports",
"close-older-discussions": true
},
{
- "allowed-repos": ["org/other-repo"],
+ "allowed-repos": [
+ "org/other-repo"
+ ],
"category": "General"
}
]
@@ -4446,6 +5215,10 @@
"minimum": 1,
"maximum": 100
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target-repo": {
"type": "string",
"description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings."
@@ -4457,7 +5230,10 @@
"required-category": "Ideas"
},
{
- "required-labels": ["resolved", "completed"],
+ "required-labels": [
+ "resolved",
+ "completed"
+ ],
"max": 1
}
]
@@ -4504,6 +5280,10 @@
"minimum": 1,
"maximum": 100
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target-repo": {
"type": "string",
"description": "Target repository in format 'owner/repo' for cross-repository discussion updates. Takes precedence over trial target repo settings."
@@ -4545,6 +5325,10 @@
"minimum": 1,
"maximum": 100
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target-repo": {
"type": "string",
"description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings."
@@ -4556,7 +5340,10 @@
"required-title-prefix": "[refactor] "
},
{
- "required-labels": ["automated", "stale"],
+ "required-labels": [
+ "automated",
+ "stale"
+ ],
"max": 10
}
]
@@ -4610,7 +5397,10 @@
"required-title-prefix": "[bot] "
},
{
- "required-labels": ["automated", "outdated"],
+ "required-labels": [
+ "automated",
+ "outdated"
+ ],
"max": 5
}
]
@@ -4664,7 +5454,10 @@
"required-title-prefix": "[bot] "
},
{
- "required-labels": ["automated", "ready"],
+ "required-labels": [
+ "automated",
+ "ready"
+ ],
"max": 1
}
]
@@ -4688,6 +5481,10 @@
"minimum": 1,
"maximum": 100
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target": {
"type": "string",
"description": "Target for comments: 'triggering' (default), '*' (any issue), or explicit issue number"
@@ -4718,7 +5515,13 @@
"description": "List of allowed reasons for hiding older comments when hide-older-comments is enabled. Default: all reasons allowed (spam, abuse, off_topic, outdated, resolved).",
"items": {
"type": "string",
- "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"]
+ "enum": [
+ "spam",
+ "abuse",
+ "off_topic",
+ "outdated",
+ "resolved"
+ ]
}
}
},
@@ -4787,7 +5590,11 @@
},
"if-no-changes": {
"type": "string",
- "enum": ["warn", "error", "ignore"],
+ "enum": [
+ "warn",
+ "error",
+ "ignore"
+ ],
"description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)"
},
"allow-empty": {
@@ -4834,13 +5641,19 @@
"examples": [
{
"title-prefix": "[docs] ",
- "labels": ["documentation", "automation"],
+ "labels": [
+ "documentation",
+ "automation"
+ ],
"reviewers": "copilot",
"draft": false
},
{
"title-prefix": "[security-fix] ",
- "labels": ["security", "automated-fix"],
+ "labels": [
+ "security",
+ "automated-fix"
+ ],
"reviewers": "copilot"
}
]
@@ -4867,7 +5680,10 @@
"side": {
"type": "string",
"description": "Side of the diff for comments: 'LEFT' or 'RIGHT' (default: 'RIGHT')",
- "enum": ["LEFT", "RIGHT"]
+ "enum": [
+ "LEFT",
+ "RIGHT"
+ ]
},
"target": {
"type": "string",
@@ -5143,7 +5959,10 @@
"minimum": 1
},
"target": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Target issue/PR to assign agents to. Use 'triggering' (default) for the triggering issue/PR, '*' to require explicit issue_number/pull_number, or a specific issue/PR number. With 'triggering', auto-resolves from github.event.issue.number or github.event.pull_request.number."
},
"target-repo": {
@@ -5188,7 +6007,10 @@
"minimum": 1
},
"target": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Target issue to assign users to. Use 'triggering' (default) for the triggering issue, '*' to allow any issue, or a specific issue number."
},
"target-repo": {
@@ -5289,6 +6111,10 @@
"minimum": 1,
"maximum": 100
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target-repo": {
"type": "string",
"description": "Target repository in format 'owner/repo' for cross-repository issue updates. Takes precedence over trial target repo settings."
@@ -5324,7 +6150,11 @@
"operation": {
"type": "string",
"description": "Default operation for body updates: 'append' (add to end), 'prepend' (add to start), or 'replace' (overwrite completely). Defaults to 'replace' if not specified.",
- "enum": ["append", "prepend", "replace"]
+ "enum": [
+ "append",
+ "prepend",
+ "replace"
+ ]
},
"max": {
"type": "integer",
@@ -5381,7 +6211,11 @@
},
"if-no-changes": {
"type": "string",
- "enum": ["warn", "error", "ignore"],
+ "enum": [
+ "warn",
+ "error",
+ "ignore"
+ ],
"description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)"
},
"commit-title-suffix": {
@@ -5414,6 +6248,10 @@
"minimum": 1,
"maximum": 100
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target-repo": {
"type": "string",
"description": "Target repository in format 'owner/repo' for cross-repository comment hiding. Takes precedence over trial target repo settings."
@@ -5423,7 +6261,13 @@
"description": "List of allowed reasons for hiding comments. Default: all reasons allowed (spam, abuse, off_topic, outdated, resolved).",
"items": {
"type": "string",
- "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"]
+ "enum": [
+ "spam",
+ "abuse",
+ "off_topic",
+ "outdated",
+ "resolved"
+ ]
}
}
},
@@ -5460,7 +6304,9 @@
"description": "GitHub token to use for dispatching workflows. Overrides global github-token if specified."
}
},
- "required": ["workflows"],
+ "required": [
+ "workflows"
+ ],
"additionalProperties": false
},
{
@@ -5667,6 +6513,10 @@
"maximum": 10,
"default": 1
},
+ "github-token": {
+ "$ref": "#/$defs/github_token",
+ "description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
+ },
"target-repo": {
"type": "string",
"description": "Target repository for cross-repo release updates (format: owner/repo). If not specified, updates releases in the workflow's repository.",
@@ -5685,7 +6535,10 @@
"staged": {
"type": "boolean",
"description": "If true, emit step summary messages instead of making GitHub API calls (preview mode)",
- "examples": [true, false]
+ "examples": [
+ true,
+ false
+ ]
},
"env": {
"type": "object",
@@ -5701,7 +6554,11 @@
"github-token": {
"$ref": "#/$defs/github_token",
"description": "GitHub token to use for safe output jobs. Typically a secret reference like ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }}",
- "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"]
+ "examples": [
+ "${{ secrets.GITHUB_TOKEN }}",
+ "${{ secrets.CUSTOM_PAT }}",
+ "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"
+ ]
},
"app": {
"type": "object",
@@ -5710,17 +6567,25 @@
"app-id": {
"type": "string",
"description": "GitHub App ID. Should reference a variable (e.g., ${{ vars.APP_ID }}).",
- "examples": ["${{ vars.APP_ID }}", "${{ secrets.APP_ID }}"]
+ "examples": [
+ "${{ vars.APP_ID }}",
+ "${{ secrets.APP_ID }}"
+ ]
},
"private-key": {
"type": "string",
"description": "GitHub App private key. Should reference a secret (e.g., ${{ secrets.APP_PRIVATE_KEY }}).",
- "examples": ["${{ secrets.APP_PRIVATE_KEY }}"]
+ "examples": [
+ "${{ secrets.APP_PRIVATE_KEY }}"
+ ]
},
"owner": {
"type": "string",
"description": "Optional: The owner of the GitHub App installation. If empty, defaults to the current repository owner.",
- "examples": ["my-organization", "${{ github.repository_owner }}"]
+ "examples": [
+ "my-organization",
+ "${{ github.repository_owner }}"
+ ]
},
"repositories": {
"type": "array",
@@ -5728,10 +6593,21 @@
"items": {
"type": "string"
},
- "examples": [["repo1", "repo2"], ["my-repo"]]
+ "examples": [
+ [
+ "repo1",
+ "repo2"
+ ],
+ [
+ "my-repo"
+ ]
+ ]
}
},
- "required": ["app-id", "private-key"],
+ "required": [
+ "app-id",
+ "private-key"
+ ],
"additionalProperties": false
},
"max-patch-size": {
@@ -5889,7 +6765,13 @@
},
"type": {
"type": "string",
- "enum": ["string", "boolean", "choice", "number", "environment"],
+ "enum": [
+ "string",
+ "boolean",
+ "choice",
+ "number",
+ "environment"
+ ],
"description": "Input parameter type. Supports: string (default), boolean, choice (string with predefined options), number, and environment (string referencing a GitHub environment)",
"default": "string"
},
@@ -5926,52 +6808,81 @@
"footer": {
"type": "string",
"description": "Custom footer message template for AI-generated content. Available placeholders: {workflow_name}, {run_url}, {triggering_number}, {workflow_source}, {workflow_source_url}. Example: '> Generated by [{workflow_name}]({run_url})'",
- "examples": ["> Generated by [{workflow_name}]({run_url})", "> AI output from [{workflow_name}]({run_url}) for #{triggering_number}"]
+ "examples": [
+ "> Generated by [{workflow_name}]({run_url})",
+ "> AI output from [{workflow_name}]({run_url}) for #{triggering_number}"
+ ]
},
"footer-install": {
"type": "string",
"description": "Custom installation instructions template appended to the footer. Available placeholders: {workflow_source}, {workflow_source_url}. Example: '> Install: `gh aw add {workflow_source}`'",
- "examples": ["> Install: `gh aw add {workflow_source}`", "> [Add this workflow]({workflow_source_url})"]
+ "examples": [
+ "> Install: `gh aw add {workflow_source}`",
+ "> [Add this workflow]({workflow_source_url})"
+ ]
},
"footer-workflow-recompile": {
"type": "string",
"description": "Custom footer message template for workflow recompile issues. Available placeholders: {workflow_name}, {run_url}, {repository}. Example: '> Workflow sync report by [{workflow_name}]({run_url}) for {repository}'",
- "examples": ["> Workflow sync report by [{workflow_name}]({run_url}) for {repository}", "> Maintenance report by [{workflow_name}]({run_url})"]
+ "examples": [
+ "> Workflow sync report by [{workflow_name}]({run_url}) for {repository}",
+ "> Maintenance report by [{workflow_name}]({run_url})"
+ ]
},
"footer-workflow-recompile-comment": {
"type": "string",
"description": "Custom footer message template for comments on workflow recompile issues. Available placeholders: {workflow_name}, {run_url}, {repository}. Example: '> Update from [{workflow_name}]({run_url}) for {repository}'",
- "examples": ["> Update from [{workflow_name}]({run_url}) for {repository}", "> Maintenance update by [{workflow_name}]({run_url})"]
+ "examples": [
+ "> Update from [{workflow_name}]({run_url}) for {repository}",
+ "> Maintenance update by [{workflow_name}]({run_url})"
+ ]
},
"staged-title": {
"type": "string",
- "description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '🎭 Preview: {operation}'",
- "examples": ["🎭 Preview: {operation}", "## Staged Mode: {operation}"]
+ "description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '\ud83c\udfad Preview: {operation}'",
+ "examples": [
+ "\ud83c\udfad Preview: {operation}",
+ "## Staged Mode: {operation}"
+ ]
},
"staged-description": {
"type": "string",
"description": "Custom description template for staged mode preview. Available placeholders: {operation}. Example: 'The following {operation} would occur if staged mode was disabled:'",
- "examples": ["The following {operation} would occur if staged mode was disabled:"]
+ "examples": [
+ "The following {operation} would occur if staged mode was disabled:"
+ ]
},
"run-started": {
"type": "string",
"description": "Custom message template for workflow activation comment. Available placeholders: {workflow_name}, {run_url}, {event_type}. Default: 'Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.'",
- "examples": ["Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.", "[{workflow_name}]({run_url}) started processing this {event_type}."]
+ "examples": [
+ "Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.",
+ "[{workflow_name}]({run_url}) started processing this {event_type}."
+ ]
},
"run-success": {
"type": "string",
- "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."]
+ "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."
+ ]
},
"run-failure": {
"type": "string",
- "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}."]
+ "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}."
+ ]
},
"detection-failure": {
"type": "string",
- "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})."]
+ "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})."
+ ]
},
"append-only-comments": {
"type": "boolean",
@@ -6051,19 +6962,27 @@
"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 (⚠️ 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 (\u26a0\ufe0f security consideration).",
"oneOf": [
{
"type": "string",
- "enum": ["all"],
- "description": "Allow any authenticated user to trigger the workflow (⚠️ disables permission checking entirely - use with caution)"
+ "enum": [
+ "all"
+ ],
+ "description": "Allow any authenticated user to trigger the workflow (\u26a0\ufe0f disables permission checking entirely - use with caution)"
},
{
"type": "array",
"description": "List of repository permission levels that can trigger the workflow. Permission checks are automatically applied to potentially unsafe triggers.",
"items": {
"type": "string",
- "enum": ["admin", "maintainer", "maintain", "write", "triage"],
+ "enum": [
+ "admin",
+ "maintainer",
+ "maintain",
+ "write",
+ "triage"
+ ],
"description": "Repository permission level: 'admin' (full access), 'maintainer'/'maintain' (repository management), 'write' (push access), 'triage' (issue management)"
},
"minItems": 1,
@@ -6085,7 +7004,10 @@
"default": true,
"$comment": "Strict mode enforces several security constraints that are validated in Go code (pkg/workflow/strict_mode_validation.go) rather than JSON Schema: (1) Write Permissions + Safe Outputs: When strict=true AND permissions contains write values (contents:write, issues:write, pull-requests:write), safe-outputs must be configured. This relationship is too complex for JSON Schema as it requires checking if ANY permission property has a 'write' value. (2) Network Requirements: When strict=true, the 'network' field must be present and cannot contain standalone wildcard '*' (but patterns like '*.example.com' ARE allowed). (3) MCP Container Network: Custom MCP servers with containers require explicit network configuration. (4) Action Pinning: Actions must be pinned to commit SHAs. These are enforced during compilation via validateStrictMode().",
"description": "Enable strict mode validation for enhanced security and compliance. Strict mode enforces: (1) Write Permissions - refuses contents:write, issues:write, pull-requests:write; requires safe-outputs instead, (2) Network Configuration - requires explicit network configuration with no standalone wildcard '*' in allowed domains (patterns like '*.example.com' are allowed), (3) Action Pinning - enforces actions pinned to commit SHAs instead of tags/branches, (4) MCP Network - requires network configuration for custom MCP servers with containers, (5) Deprecated Fields - refuses deprecated frontmatter fields. Can be enabled per-workflow via 'strict: true' in frontmatter, or disabled via 'strict: false'. CLI flag takes precedence over frontmatter (gh aw compile --strict enforces strict mode). Defaults to true. See: https://github.github.com/gh-aw/reference/frontmatter/#strict-mode-strict",
- "examples": [true, false]
+ "examples": [
+ true,
+ false
+ ]
},
"safe-inputs": {
"type": "object",
@@ -6094,7 +7016,9 @@
"^([a-ln-z][a-z0-9_-]*|m[a-np-z][a-z0-9_-]*|mo[a-ce-z][a-z0-9_-]*|mod[a-df-z][a-z0-9_-]*|mode[a-z0-9_-]+)$": {
"type": "object",
"description": "Custom tool definition. The key is the tool name (lowercase alphanumeric with dashes/underscores).",
- "required": ["description"],
+ "required": [
+ "description"
+ ],
"properties": {
"description": {
"type": "string",
@@ -6108,7 +7032,13 @@
"properties": {
"type": {
"type": "string",
- "enum": ["string", "number", "boolean", "array", "object"],
+ "enum": [
+ "string",
+ "number",
+ "boolean",
+ "array",
+ "object"
+ ],
"default": "string",
"description": "The JSON schema type of the input parameter."
},
@@ -6162,71 +7092,108 @@
"description": "Timeout in seconds for tool execution. Default is 60 seconds. Applies to shell (run) and Python (py) tools.",
"default": 60,
"minimum": 1,
- "examples": [30, 60, 120, 300]
+ "examples": [
+ 30,
+ 60,
+ 120,
+ 300
+ ]
}
},
"additionalProperties": false,
"oneOf": [
{
- "required": ["script"],
+ "required": [
+ "script"
+ ],
"not": {
"anyOf": [
{
- "required": ["run"]
+ "required": [
+ "run"
+ ]
},
{
- "required": ["py"]
+ "required": [
+ "py"
+ ]
},
{
- "required": ["go"]
+ "required": [
+ "go"
+ ]
}
]
}
},
{
- "required": ["run"],
+ "required": [
+ "run"
+ ],
"not": {
"anyOf": [
{
- "required": ["script"]
+ "required": [
+ "script"
+ ]
},
{
- "required": ["py"]
+ "required": [
+ "py"
+ ]
},
{
- "required": ["go"]
+ "required": [
+ "go"
+ ]
}
]
}
},
{
- "required": ["py"],
+ "required": [
+ "py"
+ ],
"not": {
"anyOf": [
{
- "required": ["script"]
+ "required": [
+ "script"
+ ]
},
{
- "required": ["run"]
+ "required": [
+ "run"
+ ]
},
{
- "required": ["go"]
+ "required": [
+ "go"
+ ]
}
]
}
},
{
- "required": ["go"],
+ "required": [
+ "go"
+ ],
"not": {
"anyOf": [
{
- "required": ["script"]
+ "required": [
+ "script"
+ ]
},
{
- "required": ["run"]
+ "required": [
+ "run"
+ ]
},
{
- "required": ["py"]
+ "required": [
+ "py"
+ ]
}
]
}
@@ -6284,9 +7251,18 @@
"description": "Runtime configuration object identified by runtime ID (e.g., 'node', 'python', 'go')",
"properties": {
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Runtime version as a string (e.g., '22', '3.12', 'latest') or number (e.g., 22, 3.12). Numeric values are automatically converted to strings at runtime.",
- "examples": ["22", "3.12", "latest", 22, 3.12]
+ "examples": [
+ "22",
+ "3.12",
+ "latest",
+ 22,
+ 3.12
+ ]
},
"action-repo": {
"type": "string",
@@ -6323,7 +7299,9 @@
}
}
},
- "required": ["slash_command"]
+ "required": [
+ "slash_command"
+ ]
},
{
"properties": {
@@ -6333,7 +7311,9 @@
}
}
},
- "required": ["command"]
+ "required": [
+ "command"
+ ]
}
]
}
@@ -6352,7 +7332,9 @@
}
}
},
- "required": ["issue_comment"]
+ "required": [
+ "issue_comment"
+ ]
},
{
"properties": {
@@ -6362,7 +7344,9 @@
}
}
},
- "required": ["pull_request_review_comment"]
+ "required": [
+ "pull_request_review_comment"
+ ]
},
{
"properties": {
@@ -6372,7 +7356,9 @@
}
}
},
- "required": ["label"]
+ "required": [
+ "label"
+ ]
}
]
}
@@ -6406,7 +7392,12 @@
"oneOf": [
{
"type": "string",
- "enum": ["claude", "codex", "copilot", "custom"],
+ "enum": [
+ "claude",
+ "codex",
+ "copilot",
+ "custom"
+ ],
"description": "Simple engine name: 'claude' (default, Claude Code), 'copilot' (GitHub Copilot CLI), 'codex' (OpenAI Codex CLI), or 'custom' (user-defined steps)"
},
{
@@ -6415,13 +7406,26 @@
"properties": {
"id": {
"type": "string",
- "enum": ["claude", "codex", "custom", "copilot"],
+ "enum": [
+ "claude",
+ "codex",
+ "custom",
+ "copilot"
+ ],
"description": "AI engine identifier: 'claude' (Claude Code), 'codex' (OpenAI Codex CLI), 'copilot' (GitHub Copilot CLI), or 'custom' (user-defined GitHub Actions steps)"
},
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Optional version of the AI engine action (e.g., 'beta', 'stable', 20). Has sensible defaults and can typically be omitted. Numeric values are automatically converted to strings at runtime.",
- "examples": ["beta", "stable", 20, 3.11]
+ "examples": [
+ "beta",
+ "stable",
+ 20,
+ 3.11
+ ]
},
"model": {
"type": "string",
@@ -6459,7 +7463,9 @@
"description": "Whether to cancel in-progress runs of the same concurrency group. Defaults to false for agentic workflow runs."
}
},
- "required": ["group"],
+ "required": [
+ "group"
+ ],
"additionalProperties": false
}
],
@@ -6518,7 +7524,9 @@
"description": "Human-readable description of what this pattern matches"
}
},
- "required": ["pattern"],
+ "required": [
+ "pattern"
+ ],
"additionalProperties": false
}
},
@@ -6538,7 +7546,9 @@
"description": "Optional array of command-line arguments to pass to the AI engine CLI. These arguments are injected after all other args but before the prompt."
}
},
- "required": ["id"],
+ "required": [
+ "id"
+ ],
"additionalProperties": false
}
]
@@ -6549,13 +7559,18 @@
"properties": {
"type": {
"type": "string",
- "enum": ["stdio", "local"],
+ "enum": [
+ "stdio",
+ "local"
+ ],
"description": "MCP connection type for stdio (local is an alias for stdio)"
},
"registry": {
"type": "string",
"description": "URI to the installation location when MCP is installed from a registry",
- "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown"]
+ "examples": [
+ "https://api.mcp.github.com/v0/servers/microsoft/markitdown"
+ ]
},
"command": {
"type": "string",
@@ -6570,9 +7585,17 @@
"description": "Container image for stdio MCP connections"
},
"version": {
- "type": ["string", "number"],
+ "type": [
+ "string",
+ "number"
+ ],
"description": "Optional version/tag for the container image (e.g., 'latest', 'v1.0.0', 20, 3.11). Numeric values are automatically converted to strings at runtime.",
- "examples": ["latest", "v1.0.0", 20, 3.11]
+ "examples": [
+ "latest",
+ "v1.0.0",
+ 20,
+ 3.11
+ ]
},
"args": {
"type": "array",
@@ -6584,7 +7607,11 @@
"entrypoint": {
"type": "string",
"description": "Optional entrypoint override for container (equivalent to docker run --entrypoint)",
- "examples": ["/bin/sh", "/custom/entrypoint.sh", "python"]
+ "examples": [
+ "/bin/sh",
+ "/custom/entrypoint.sh",
+ "python"
+ ]
},
"entrypointArgs": {
"type": "array",
@@ -6600,7 +7627,15 @@
"pattern": "^[^:]+:[^:]+:(ro|rw)$"
},
"description": "Volume mounts for container in format 'source:dest:mode' where mode is 'ro' or 'rw'",
- "examples": [["/tmp/data:/data:ro"], ["/workspace:/workspace:rw", "/config:/config:ro"]]
+ "examples": [
+ [
+ "/tmp/data:/data:ro"
+ ],
+ [
+ "/workspace:/workspace:rw",
+ "/config:/config:ro"
+ ]
+ ]
},
"env": {
"type": "object",
@@ -6646,29 +7681,50 @@
"items": {
"type": "string"
},
- "examples": [["*"], ["store_memory", "retrieve_memory"], ["brave_web_search"]]
+ "examples": [
+ [
+ "*"
+ ],
+ [
+ "store_memory",
+ "retrieve_memory"
+ ],
+ [
+ "brave_web_search"
+ ]
+ ]
}
},
"additionalProperties": false,
"$comment": "Validation constraints: (1) Mutual exclusion: 'command' and 'container' cannot both be specified. (2) Requirement: Either 'command' or 'container' must be provided (via 'anyOf'). (3) Type constraint: When 'type' is 'stdio' or 'local', either 'command' or 'container' is required. Note: Per-server 'network' field is deprecated and ignored.",
"anyOf": [
{
- "required": ["type"]
+ "required": [
+ "type"
+ ]
},
{
- "required": ["command"]
+ "required": [
+ "command"
+ ]
},
{
- "required": ["container"]
+ "required": [
+ "container"
+ ]
}
],
"not": {
"allOf": [
{
- "required": ["command"]
+ "required": [
+ "command"
+ ]
},
{
- "required": ["container"]
+ "required": [
+ "container"
+ ]
}
]
},
@@ -6677,17 +7733,24 @@
"if": {
"properties": {
"type": {
- "enum": ["stdio", "local"]
+ "enum": [
+ "stdio",
+ "local"
+ ]
}
}
},
"then": {
"anyOf": [
{
- "required": ["command"]
+ "required": [
+ "command"
+ ]
},
{
- "required": ["container"]
+ "required": [
+ "container"
+ ]
}
]
}
@@ -6700,13 +7763,17 @@
"properties": {
"type": {
"type": "string",
- "enum": ["http"],
+ "enum": [
+ "http"
+ ],
"description": "MCP connection type for HTTP"
},
"registry": {
"type": "string",
"description": "URI to the installation location when MCP is installed from a registry",
- "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown"]
+ "examples": [
+ "https://api.mcp.github.com/v0/servers/microsoft/markitdown"
+ ]
},
"url": {
"type": "string",
@@ -6729,17 +7796,34 @@
"items": {
"type": "string"
},
- "examples": [["*"], ["store_memory", "retrieve_memory"], ["brave_web_search"]]
+ "examples": [
+ [
+ "*"
+ ],
+ [
+ "store_memory",
+ "retrieve_memory"
+ ],
+ [
+ "brave_web_search"
+ ]
+ ]
}
},
- "required": ["url"],
+ "required": [
+ "url"
+ ],
"additionalProperties": false
},
"github_token": {
"type": "string",
"pattern": "^\\$\\{\\{\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*(\\s*\\|\\|\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*)*\\s*\\}\\}$",
"description": "GitHub token expression using secrets. Pattern details: `[A-Za-z_][A-Za-z0-9_]*` matches a valid secret name (starts with a letter or underscore, followed by letters, digits, or underscores). The full pattern matches expressions like `${{ secrets.NAME }}` or `${{ secrets.NAME1 || secrets.NAME2 }}`.",
- "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"]
+ "examples": [
+ "${{ secrets.GITHUB_TOKEN }}",
+ "${{ secrets.CUSTOM_PAT }}",
+ "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"
+ ]
},
"githubActionsStep": {
"type": "object",
@@ -6800,10 +7884,14 @@
"additionalProperties": false,
"anyOf": [
{
- "required": ["uses"]
+ "required": [
+ "uses"
+ ]
},
{
- "required": ["run"]
+ "required": [
+ "run"
+ ]
}
]
}
diff --git a/pkg/workflow/compiler_safe_outputs_config.go b/pkg/workflow/compiler_safe_outputs_config.go
index 0fbc23757a9..774ab7955a7 100644
--- a/pkg/workflow/compiler_safe_outputs_config.go
+++ b/pkg/workflow/compiler_safe_outputs_config.go
@@ -102,6 +102,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.CreateIssues
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddStringSlice("allowed_labels", c.AllowedLabels).
AddStringSlice("allowed_repos", c.AllowedRepos).
AddIfPositive("expires", c.Expires).
@@ -120,6 +121,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.AddComments
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("target", c.Target).
AddIfTrue("hide_older_comments", c.HideOlderComments).
AddIfNotEmpty("target-repo", c.TargetRepoSlug).
@@ -133,6 +135,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.CreateDiscussions
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("category", c.Category).
AddIfNotEmpty("title_prefix", c.TitlePrefix).
AddStringSlice("labels", c.Labels).
@@ -152,6 +155,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.CloseIssues
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("target", c.Target).
AddStringSlice("required_labels", c.RequiredLabels).
AddIfNotEmpty("required_title_prefix", c.RequiredTitlePrefix).
@@ -166,6 +170,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.CloseDiscussions
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("target", c.Target).
AddStringSlice("required_labels", c.RequiredLabels).
AddIfNotEmpty("required_title_prefix", c.RequiredTitlePrefix).
@@ -180,6 +185,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.AddLabels
config := newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddStringSlice("allowed", c.Allowed).
AddIfNotEmpty("target", c.Target).
AddIfNotEmpty("target-repo", c.TargetRepoSlug).
@@ -201,6 +207,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.RemoveLabels
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddStringSlice("allowed", c.Allowed).
AddIfNotEmpty("target", c.Target).
AddIfNotEmpty("target-repo", c.TargetRepoSlug).
@@ -214,6 +221,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.UpdateIssues
builder := newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("target", c.Target)
// Boolean pointer fields indicate which fields can be updated
if c.Status != nil {
@@ -237,6 +245,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.UpdateDiscussions
builder := newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("target", c.Target)
// Boolean pointer fields indicate which fields can be updated
if c.Title != nil {
@@ -261,6 +270,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.LinkSubIssue
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddStringSlice("parent_required_labels", c.ParentRequiredLabels).
AddIfNotEmpty("parent_title_prefix", c.ParentTitlePrefix).
AddStringSlice("sub_required_labels", c.SubRequiredLabels).
@@ -276,6 +286,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.UpdateRelease
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
Build()
},
"create_pull_request_review_comment": func(cfg *SafeOutputsConfig) map[string]any {
@@ -285,6 +296,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.CreatePullRequestReviewComments
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("side", c.Side).
AddIfNotEmpty("target", c.Target).
AddIfNotEmpty("target-repo", c.TargetRepoSlug).
@@ -302,6 +314,7 @@ var handlerRegistry = map[string]handlerBuilder{
}
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("title_prefix", c.TitlePrefix).
AddStringSlice("labels", c.Labels).
AddBoolPtr("draft", c.Draft).
@@ -326,6 +339,7 @@ var handlerRegistry = map[string]handlerBuilder{
}
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("target", c.Target).
AddIfNotEmpty("title_prefix", c.TitlePrefix).
AddStringSlice("labels", c.Labels).
@@ -342,6 +356,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.UpdatePullRequests
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("target", c.Target).
AddBoolPtrOrDefault("allow_title", c.Title, true).
AddBoolPtrOrDefault("allow_body", c.Body, true).
@@ -357,6 +372,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.ClosePullRequests
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddIfNotEmpty("target", c.Target).
AddStringSlice("required_labels", c.RequiredLabels).
AddIfNotEmpty("required_title_prefix", c.RequiredTitlePrefix).
@@ -371,6 +387,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.HideComment
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddStringSlice("allowed_reasons", c.AllowedReasons).
AddIfNotEmpty("target-repo", c.TargetRepoSlug).
AddStringSlice("allowed_repos", c.AllowedRepos).
@@ -383,6 +400,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.DispatchWorkflow
builder := newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
AddStringSlice("workflows", c.Workflows)
// Add workflow_files map if it has entries
@@ -399,6 +417,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.MissingTool
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
Build()
},
"missing_data": func(cfg *SafeOutputsConfig) map[string]any {
@@ -408,6 +427,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.MissingData
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
+ AddIfNotEmpty("github-token", c.GitHubToken).
Build()
},
// Note: "noop" is intentionally NOT included here because it is always processed
diff --git a/test-multiple-tokens.lock.yml b/test-multiple-tokens.lock.yml
new file mode 100644
index 00000000000..f7a1f21c4e5
--- /dev/null
+++ b/test-multiple-tokens.lock.yml
@@ -0,0 +1,1234 @@
+#
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by gh-aw. DO NOT EDIT.
+#
+# To update this file, edit the corresponding .md file and run:
+# gh aw compile
+# For more information: https://github.com/github/gh-aw/blob/main/.github/aw/github-agentic-workflows.md
+#
+#
+# frontmatter-hash: 80b2f1f120c633bdf4dbdebd375ffe00442c2c1001765bf82559795773261d03
+
+name: "Test Multiple Tokens"
+"on":
+ workflow_dispatch:
+
+permissions: {}
+
+concurrency:
+ group: "gh-aw-${{ github.workflow }}"
+
+run-name: "Test Multiple Tokens"
+
+jobs:
+ activation:
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ outputs:
+ comment_id: ""
+ comment_repo: ""
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Check workflow file timestamps
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_WORKFLOW_FILE: "test-multiple-tokens.lock.yml"
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ await main();
+
+ agent:
+ needs: activation
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ concurrency:
+ group: "gh-aw-copilot-${{ github.workflow }}"
+ env:
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ""
+ GH_AW_ASSETS_BRANCH: ""
+ GH_AW_ASSETS_MAX_SIZE_KB: 0
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ outputs:
+ checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
+ has_patch: ${{ steps.collect_output.outputs.has_patch }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Create gh-aw temp directory
+ run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Checkout PR branch
+ id: checkout-pr
+ if: |
+ github.event.pull_request
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
+ await main();
+ - name: Generate agentic run info
+ id: generate_aw_info
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const fs = require('fs');
+
+ const awInfo = {
+ engine_id: "copilot",
+ engine_name: "GitHub Copilot CLI",
+ model: process.env.GH_AW_MODEL_AGENT_COPILOT || "",
+ version: "",
+ agent_version: "0.0.405",
+ workflow_name: "Test Multiple Tokens",
+ experimental: false,
+ supports_tools_allowlist: true,
+ supports_http_transport: true,
+ run_id: context.runId,
+ run_number: context.runNumber,
+ run_attempt: process.env.GITHUB_RUN_ATTEMPT,
+ repository: context.repo.owner + '/' + context.repo.repo,
+ ref: context.ref,
+ sha: context.sha,
+ actor: context.actor,
+ event_name: context.eventName,
+ staged: false,
+ allowed_domains: ["defaults"],
+ firewall_enabled: true,
+ awf_version: "v0.13.12",
+ awmg_version: "",
+ steps: {
+ firewall: "squid"
+ },
+ created_at: new Date().toISOString()
+ };
+
+ // Write to /tmp/gh-aw directory to avoid inclusion in PR
+ const tmpPath = '/tmp/gh-aw/aw_info.json';
+ fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2));
+ console.log('Generated aw_info.json at:', tmpPath);
+ console.log(JSON.stringify(awInfo, null, 2));
+
+ // Set model as output for reuse in other steps/jobs
+ core.setOutput('model', awInfo.model);
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Install GitHub Copilot CLI
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405
+ - name: Install awf binary
+ run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.13.12
+ - name: Determine automatic lockdown mode for GitHub MCP server
+ id: determine-automatic-lockdown
+ env:
+ TOKEN_CHECK: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ if: env.TOKEN_CHECK != ''
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
+ await determineAutomaticLockdown(github, context, core);
+ - name: Download container images
+ run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:v0.0.113 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine
+ - name: Write Safe Outputs Config
+ run: |
+ mkdir -p /opt/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
+ cat > /opt/gh-aw/safeoutputs/config.json << 'EOF'
+ {"create_issue":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1},"update_project":{"max":50}}
+ EOF
+ cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF'
+ [
+ {
+ "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[dependabot-burner] \". Assignees [copilot] will be automatically assigned.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "body": {
+ "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.",
+ "type": "string"
+ },
+ "labels": {
+ "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "parent": {
+ "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123def456') from a previously created issue in the same workflow run.",
+ "type": [
+ "number",
+ "string"
+ ]
+ },
+ "temporary_id": {
+ "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 12 hex characters (e.g., 'aw_abc123def456'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.",
+ "type": "string"
+ },
+ "title": {
+ "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "title",
+ "body"
+ ],
+ "type": "object"
+ },
+ "name": "create_issue"
+ },
+ {
+ "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "alternatives": {
+ "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
+ "type": "string"
+ },
+ "reason": {
+ "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).",
+ "type": "string"
+ },
+ "tool": {
+ "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "reason"
+ ],
+ "type": "object"
+ },
+ "name": "missing_tool"
+ },
+ {
+ "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "message": {
+ "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').",
+ "type": "string"
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "type": "object"
+ },
+ "name": "noop"
+ },
+ {
+ "description": "Add or update items in GitHub Projects v2 boards. Can add issues/PRs to a project and update custom field values. Requires the project URL, content type (issue or pull_request), and content number.\n\nThree usage modes:\n1. Add/update project item: Requires project + content_type. For 'issue' or 'pull_request', also requires content_number. For 'draft_issue', requires draft_title.\n2. Create project fields: Requires project + operation='create_fields' + field_definitions.\n3. Create project view: Requires project + operation='create_view' + view.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "content_number": {
+ "description": "Issue or pull request number to add to the project. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123 for issue #123, or 456 in github.com/owner/repo/pull/456 for PR #456). Required when content_type is 'issue' or 'pull_request'.",
+ "type": "number"
+ },
+ "content_type": {
+ "description": "Type of item to add to the project. Use 'issue' or 'pull_request' to add existing repo content, or 'draft_issue' to create a draft item inside the project. Required when operation is not specified.",
+ "enum": [
+ "issue",
+ "pull_request",
+ "draft_issue"
+ ],
+ "type": "string"
+ },
+ "create_if_missing": {
+ "description": "Whether to create the project if it doesn't exist. Defaults to false. Requires projects:write permission when true.",
+ "type": "boolean"
+ },
+ "draft_body": {
+ "description": "Optional body for a Projects v2 draft issue (markdown). Only used when content_type is 'draft_issue'.",
+ "type": "string"
+ },
+ "draft_title": {
+ "description": "Title for a Projects v2 draft issue. Required when content_type is 'draft_issue'.",
+ "type": "string"
+ },
+ "field_definitions": {
+ "description": "Field definitions to create when operation is create_fields. Required when operation='create_fields'.",
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "data_type": {
+ "description": "Field type. Use SINGLE_SELECT with options for enumerated values.",
+ "enum": [
+ "TEXT",
+ "NUMBER",
+ "DATE",
+ "SINGLE_SELECT",
+ "ITERATION"
+ ],
+ "type": "string"
+ },
+ "name": {
+ "description": "Field name to create (e.g., 'size', 'priority').",
+ "type": "string"
+ },
+ "options": {
+ "description": "Options for SINGLE_SELECT fields.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "name",
+ "data_type"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "fields": {
+ "description": "Custom field values to set on the project item (e.g., {'Status': 'In Progress', 'Priority': 'High'}). Field names must match custom fields defined in the project.",
+ "type": "object"
+ },
+ "operation": {
+ "description": "Optional operation mode. Use create_fields to create required fields up-front, or create_view to add a project view. When omitted, the tool adds/updates project items.",
+ "enum": [
+ "create_fields",
+ "create_view"
+ ],
+ "type": "string"
+ },
+ "project": {
+ "description": "Full GitHub project URL (e.g., 'https://github.com/orgs/myorg/projects/42' or 'https://github.com/users/username/projects/5'). Project names or numbers alone are NOT accepted.",
+ "pattern": "^https://github\\.com/(orgs|users)/[^/]+/projects/\\d+$",
+ "type": "string"
+ },
+ "view": {
+ "additionalProperties": false,
+ "description": "View definition to create when operation is create_view. Required when operation='create_view'.",
+ "properties": {
+ "filter": {
+ "type": "string"
+ },
+ "layout": {
+ "enum": [
+ "table",
+ "board",
+ "roadmap"
+ ],
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "visible_fields": {
+ "description": "Field IDs to show in the view (table/board only).",
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "name",
+ "layout"
+ ],
+ "type": "object"
+ }
+ },
+ "required": [
+ "project"
+ ],
+ "type": "object"
+ },
+ "name": "update_project"
+ },
+ {
+ "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "alternatives": {
+ "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
+ "type": "string"
+ },
+ "context": {
+ "description": "Additional context about the missing data or where it should come from (max 256 characters).",
+ "type": "string"
+ },
+ "data_type": {
+ "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.",
+ "type": "string"
+ },
+ "reason": {
+ "description": "Explanation of why this data is needed to complete the task (max 256 characters).",
+ "type": "string"
+ }
+ },
+ "required": [],
+ "type": "object"
+ },
+ "name": "missing_data"
+ }
+ ]
+ EOF
+ cat > /opt/gh-aw/safeoutputs/validation.json << 'EOF'
+ {
+ "create_issue": {
+ "defaultMax": 1,
+ "fields": {
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "labels": {
+ "type": "array",
+ "itemType": "string",
+ "itemSanitize": true,
+ "itemMaxLength": 128
+ },
+ "parent": {
+ "issueOrPRNumber": true
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
+ },
+ "temporary_id": {
+ "type": "string"
+ },
+ "title": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "missing_tool": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "tool": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "noop": {
+ "defaultMax": 1,
+ "fields": {
+ "message": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ }
+ }
+ },
+ "update_project": {
+ "defaultMax": 10,
+ "fields": {
+ "content_number": {
+ "optionalPositiveInteger": true
+ },
+ "content_type": {
+ "type": "string",
+ "enum": [
+ "issue",
+ "pull_request",
+ "draft_issue"
+ ]
+ },
+ "draft_body": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "draft_title": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "fields": {
+ "type": "object"
+ },
+ "issue": {
+ "optionalPositiveInteger": true
+ },
+ "project": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512,
+ "pattern": "^https://github\\.com/(orgs|users)/[^/]+/projects/\\d+",
+ "patternError": "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"
+ },
+ "pull_request": {
+ "optionalPositiveInteger": true
+ }
+ }
+ }
+ }
+ EOF
+ - name: Generate Safe Outputs MCP Server Config
+ id: safe-outputs-config
+ run: |
+ # Generate a secure random API key (360 bits of entropy, 40+ chars)
+ # Mask immediately to prevent timing vulnerabilities
+ API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${API_KEY}"
+
+ PORT=3001
+
+ # Set outputs for next steps
+ {
+ echo "safe_outputs_api_key=${API_KEY}"
+ echo "safe_outputs_port=${PORT}"
+ } >> "$GITHUB_OUTPUT"
+
+ echo "Safe Outputs MCP server will run on port ${PORT}"
+
+ - name: Start Safe Outputs MCP HTTP Server
+ id: safe-outputs-start
+ env:
+ DEBUG: '*'
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ run: |
+ # Environment variables are set above to prevent template injection
+ export DEBUG
+ export GH_AW_SAFE_OUTPUTS_PORT
+ export GH_AW_SAFE_OUTPUTS_API_KEY
+ export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
+ export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
+ export GH_AW_MCP_LOG_DIR
+
+ bash /opt/gh-aw/actions/start_safe_outputs_server.sh
+
+ - name: Start MCP gateway
+ id: start-mcp-gateway
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
+ GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ run: |
+ set -eo pipefail
+ mkdir -p /tmp/gh-aw/mcp-config
+
+ # Export gateway environment variables for MCP config and gateway script
+ export MCP_GATEWAY_PORT="80"
+ export MCP_GATEWAY_DOMAIN="host.docker.internal"
+ MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_API_KEY}"
+ export MCP_GATEWAY_API_KEY
+ export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
+ mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
+ export DEBUG="*"
+
+ export GH_AW_ENGINE="copilot"
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.0.113'
+
+ mkdir -p /home/runner/.copilot
+ cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
+ {
+ "mcpServers": {
+ "github": {
+ "type": "stdio",
+ "container": "ghcr.io/github/github-mcp-server:v0.30.3",
+ "env": {
+ "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN",
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_READ_ONLY": "1",
+ "GITHUB_TOOLSETS": "context,repos,issues,pull_requests"
+ }
+ },
+ "safeoutputs": {
+ "type": "http",
+ "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
+ "headers": {
+ "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
+ }
+ }
+ },
+ "gateway": {
+ "port": $MCP_GATEWAY_PORT,
+ "domain": "${MCP_GATEWAY_DOMAIN}",
+ "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
+ }
+ }
+ MCPCONFIG_EOF
+ - name: Generate workflow overview
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
+ await generateWorkflowOverview(core);
+ - name: Create prompt with built-in context
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ run: |
+ bash /opt/gh-aw/actions/create_prompt_first.sh
+ cat << 'PROMPT_EOF' > "$GH_AW_PROMPT"
+
+ PROMPT_EOF
+ cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT"
+ cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
+ cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+
+ GitHub API Access Instructions
+
+ The gh CLI is NOT authenticated. Do NOT use gh commands for GitHub operations.
+
+
+ To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls.
+
+ Discover available tools from the safeoutputs MCP server.
+
+ **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped.
+
+ **Note**: If you made no other safe output tool calls during this workflow execution, call the "noop" tool to provide a status message indicating completion or that no actions were needed.
+
+
+
+ The following GitHub context information is available for this workflow:
+ {{#if __GH_AW_GITHUB_ACTOR__ }}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_REPOSITORY__ }}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_WORKSPACE__ }}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
+ - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
+ - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
+ - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
+ - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_RUN_ID__ }}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ PROMPT_EOF
+ cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+
+ PROMPT_EOF
+ cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+ {{#runtime-import test-multiple-tokens.md}}
+ PROMPT_EOF
+ - name: Substitute placeholders
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ with:
+ script: |
+ const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
+
+ // Call the substitution function
+ return await substitutePlaceholders({
+ file: process.env.GH_AW_PROMPT,
+ substitutions: {
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
+ GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
+ GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE
+ }
+ });
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
+ await main();
+ - name: Validate prompt placeholders
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
+ - name: Print prompt
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/print_prompt_summary.sh
+ - name: Clean git credentials
+ run: bash /opt/gh-aw/actions/clean_git_credentials.sh
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ sudo -E awf --enable-chroot --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.13.12 --skip-pull \
+ -- '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' \
+ 2>&1 | tee /tmp/gh-aw/agent-stdio.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Copy Copilot session state files to logs
+ if: always()
+ continue-on-error: true
+ run: |
+ # Copy Copilot session state files to logs folder for artifact collection
+ # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
+ SESSION_STATE_DIR="$HOME/.copilot/session-state"
+ LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
+
+ if [ -d "$SESSION_STATE_DIR" ]; then
+ echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
+ mkdir -p "$LOGS_DIR"
+ cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
+ echo "Session state files copied successfully"
+ else
+ echo "No session-state directory found at $SESSION_STATE_DIR"
+ fi
+ - name: Stop MCP gateway
+ if: always()
+ continue-on-error: true
+ env:
+ MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
+ MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
+ run: |
+ bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
+ - name: Redact secrets in logs
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs');
+ await main();
+ env:
+ GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
+ SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Upload Safe Outputs
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: safe-output
+ path: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ if-no-files-found: warn
+ - name: Ingest agent output
+ id: collect_output
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs');
+ await main();
+ - name: Upload sanitized agent output
+ if: always() && env.GH_AW_AGENT_OUTPUT
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: agent-output
+ path: ${{ env.GH_AW_AGENT_OUTPUT }}
+ if-no-files-found: warn
+ - name: Upload engine output files
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: agent_outputs
+ path: |
+ /tmp/gh-aw/sandbox/agent/logs/
+ /tmp/gh-aw/redacted-urls.log
+ if-no-files-found: ignore
+ - name: Parse agent logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs');
+ await main();
+ - name: Parse MCP gateway logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ await main();
+ - name: Print firewall logs
+ if: always()
+ continue-on-error: true
+ env:
+ AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
+ run: |
+ # Fix permissions on firewall logs so they can be uploaded as artifacts
+ # AWF runs with sudo, creating files owned by root
+ sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
+ awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ - name: Upload agent artifacts
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: agent-artifacts
+ path: |
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/mcp-logs/
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/agent-stdio.log
+ /tmp/gh-aw/agent/
+ if-no-files-found: ignore
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - detection
+ - safe_outputs
+ if: (always()) && (needs.agent.result != 'skipped')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ discussions: write
+ issues: write
+ pull-requests: write
+ outputs:
+ noop_message: ${{ steps.noop.outputs.noop_message }}
+ tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
+ total_count: ${{ steps.missing_tool.outputs.total_count }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Debug job inputs
+ env:
+ COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
+ COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
+ AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
+ AGENT_CONCLUSION: ${{ needs.agent.result }}
+ run: |
+ echo "Comment ID: $COMMENT_ID"
+ echo "Comment Repo: $COMMENT_REPO"
+ echo "Agent Output Types: $AGENT_OUTPUT_TYPES"
+ echo "Agent Conclusion: $AGENT_CONCLUSION"
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/safeoutputs/
+ - name: Setup agent output environment variable
+ run: |
+ mkdir -p /tmp/gh-aw/safeoutputs/
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process No-Op Messages
+ id: noop
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: 1
+ GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/noop.cjs');
+ await main();
+ - name: Record Missing Tool
+ id: missing_tool
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/missing_tool.cjs');
+ await main();
+ - name: Handle Agent Failure
+ id: handle_agent_failure
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_WORKFLOW_ID: "test-multiple-tokens"
+ GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }}
+ GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs');
+ await main();
+ - name: Handle No-Op Message
+ id: handle_noop_message
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }}
+ GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs');
+ await main();
+ - name: Update reaction comment with completion status
+ id: conclusion
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
+ GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.result }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/notify_comment_error.cjs');
+ await main();
+
+ detection:
+ needs: agent
+ if: needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ concurrency:
+ group: "gh-aw-copilot-${{ github.workflow }}"
+ timeout-minutes: 10
+ outputs:
+ success: ${{ steps.parse_results.outputs.success }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Download agent artifacts
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: agent-artifacts
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/threat-detection/
+ - name: Echo agent output types
+ env:
+ AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
+ run: |
+ echo "Agent output-types: $AGENT_OUTPUT_TYPES"
+ - name: Setup threat detection
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ WORKFLOW_NAME: "Test Multiple Tokens"
+ WORKFLOW_DESCRIPTION: "No description provided"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs');
+ await main();
+ - name: Ensure threat-detection directory and log
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection
+ touch /tmp/gh-aw/threat-detection/detection.log
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Install GitHub Copilot CLI
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool shell(cat)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(head)
+ # --allow-tool shell(jq)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(wc)
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"
+ mkdir -p /tmp/
+ mkdir -p /tmp/gh-aw/
+ mkdir -p /tmp/gh-aw/agent/
+ mkdir -p /tmp/gh-aw/sandbox/agent/logs/
+ copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool 'shell(cat)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(jq)' --allow-tool 'shell(ls)' --allow-tool 'shell(tail)' --allow-tool 'shell(wc)' --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$COPILOT_CLI_INSTRUCTION"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"} 2>&1 | tee /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Parse threat detection results
+ id: parse_results
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
+ await main();
+ - name: Upload threat detection log
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: threat-detection.log
+ path: /tmp/gh-aw/threat-detection/detection.log
+ if-no-files-found: ignore
+
+ safe_outputs:
+ needs:
+ - agent
+ - detection
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.detection.outputs.success == 'true')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ issues: write
+ timeout-minutes: 15
+ env:
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_WORKFLOW_ID: "test-multiple-tokens"
+ GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
+ outputs:
+ create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
+ create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ safe-output-projects: 'true'
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/safeoutputs/
+ - name: Setup agent output environment variable
+ run: |
+ mkdir -p /tmp/gh-aw/safeoutputs/
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process Safe Outputs
+ id: process_safe_outputs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"github-token\":\"${{ secrets.AGENT_GITHUB_TOKEN }}\",\"max\":10,\"title_prefix\":\"[dependabot-burner] \"},\"missing_data\":{},\"missing_tool\":{},\"update_project\":{\"github-token\":\"${{ secrets.PROJECT_GITHUB_TOKEN }}\",\"max\":50,\"project\":\"https://github.com/orgs/my-mona-org/projects/1\"}}"
+ GH_AW_ASSIGN_COPILOT: "true"
+ GH_AW_PROJECT_URL: "https://github.com/orgs/my-mona-org/projects/1"
+ GH_AW_PROJECT_GITHUB_TOKEN: ${{ secrets.PROJECT_GITHUB_TOKEN }}
+ with:
+ github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs');
+ await main();
+ - name: Assign Copilot to created issues
+ if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != ''
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }}
+ with:
+ github-token: ${{ secrets.AGENT_GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/assign_copilot_to_created_issues.cjs');
+ await main();
+
From a19fff00434b96fd936a3917eb6baa40d7b83b46 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Feb 2026 18:22:44 +0000
Subject: [PATCH 4/7] Add test for multiple github-token configurations in
safe-outputs
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
---
.../docs/reference/frontmatter-full.md | 45 +
pkg/parser/schemas/main_workflow_schema.json | 1670 +++--------------
...safe_outputs_handler_manager_token_test.go | 43 +
3 files changed, 397 insertions(+), 1361 deletions(-)
diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md
index b3cc8f95571..f6a696ea8cd 100644
--- a/docs/src/content/docs/reference/frontmatter-full.md
+++ b/docs/src/content/docs/reference/frontmatter-full.md
@@ -1827,6 +1827,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target repository in format 'owner/repo' for cross-repository issue creation.
# Takes precedence over trial target repo settings.
# (optional)
@@ -2182,6 +2187,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target repository in format 'owner/repo' for cross-repository discussion
# creation. Takes precedence over trial target repo settings.
# (optional)
@@ -2261,6 +2271,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target repository in format 'owner/repo' for cross-repository operations. Takes
# precedence over trial target repo settings.
# (optional)
@@ -2305,6 +2320,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target repository in format 'owner/repo' for cross-repository discussion
# updates. Takes precedence over trial target repo settings.
# (optional)
@@ -2339,6 +2359,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target repository in format 'owner/repo' for cross-repository operations. Takes
# precedence over trial target repo settings.
# (optional)
@@ -2439,6 +2464,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target for comments: 'triggering' (default), '*' (any issue), or explicit issue
# number
# (optional)
@@ -2957,6 +2987,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target repository in format 'owner/repo' for cross-repository issue updates.
# Takes precedence over trial target repo settings.
# (optional)
@@ -3071,6 +3106,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target repository in format 'owner/repo' for cross-repository comment hiding.
# Takes precedence over trial target repo settings.
# (optional)
@@ -3257,6 +3297,11 @@ safe-outputs:
# (optional)
max: 1
+ # GitHub token to use for this specific output type. Overrides global github-token
+ # if specified.
+ # (optional)
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
# Target repository for cross-repo release updates (format: owner/repo). If not
# specified, updates releases in the workflow's repository.
# (optional)
diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json
index 93dc1a44935..19baef65784 100644
--- a/pkg/parser/schemas/main_workflow_schema.json
+++ b/pkg/parser/schemas/main_workflow_schema.json
@@ -5,36 +5,25 @@
"description": "JSON Schema for validating agentic workflow frontmatter configuration",
"version": "1.0.0",
"type": "object",
- "required": [
- "on"
- ],
+ "required": ["on"],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"description": "Workflow name that appears in the GitHub Actions interface. If not specified, defaults to the filename without extension.",
- "examples": [
- "Copilot Agent PR Analysis",
- "Dev Hawk",
- "Smoke Claude"
- ]
+ "examples": ["Copilot Agent PR Analysis", "Dev Hawk", "Smoke Claude"]
},
"description": {
"type": "string",
"maxLength": 10000,
"description": "Optional workflow description that is rendered as a comment in the generated GitHub Actions YAML file (.lock.yml)",
- "examples": [
- "Quickstart for using the GitHub Actions library"
- ]
+ "examples": ["Quickstart for using the GitHub Actions library"]
},
"source": {
"type": "string",
"description": "Optional source reference indicating where this workflow was added from. Format: owner/repo/path@ref (e.g., githubnext/agentics/workflows/ci-doctor.md@v1.0.0). Rendered as a comment in the generated lock file.",
- "examples": [
- "githubnext/agentics/workflows/ci-doctor.md",
- "githubnext/agentics/workflows/daily-perf-improver.md@1f181b37d3fe5862ab590648f25a292e345b5de6"
- ]
+ "examples": ["githubnext/agentics/workflows/ci-doctor.md", "githubnext/agentics/workflows/daily-perf-improver.md@1f181b37d3fe5862ab590648f25a292e345b5de6"]
},
"tracker-id": {
"type": "string",
@@ -42,11 +31,7 @@
"maxLength": 128,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Optional tracker identifier to tag all created assets (issues, discussions, comments, pull requests). Must be at least 8 characters and contain only alphanumeric characters, hyphens, and underscores. This identifier will be inserted in the body/description of all created assets to enable searching and retrieving assets associated with this workflow.",
- "examples": [
- "workflow-2024-q1",
- "team-alpha-bot",
- "security_audit_v2"
- ]
+ "examples": ["workflow-2024-q1", "team-alpha-bot", "security_audit_v2"]
},
"labels": {
"type": "array",
@@ -56,18 +41,9 @@
"minLength": 1
},
"examples": [
- [
- "automation",
- "security"
- ],
- [
- "docs",
- "maintenance"
- ],
- [
- "ci",
- "testing"
- ]
+ ["automation", "security"],
+ ["docs", "maintenance"],
+ ["ci", "testing"]
]
},
"metadata": {
@@ -101,9 +77,7 @@
{
"type": "object",
"description": "Import specification with path and optional inputs",
- "required": [
- "path"
- ],
+ "required": ["path"],
"additionalProperties": false,
"properties": {
"path": {
@@ -132,21 +106,10 @@
]
},
"examples": [
- [
- "shared/jqschema.md",
- "shared/reporting.md"
- ],
- [
- "shared/mcp/gh-aw.md",
- "shared/jqschema.md",
- "shared/reporting.md"
- ],
- [
- "../instructions/documentation.instructions.md"
- ],
- [
- ".github/agents/my-agent.md"
- ],
+ ["shared/jqschema.md", "shared/reporting.md"],
+ ["shared/mcp/gh-aw.md", "shared/jqschema.md", "shared/reporting.md"],
+ ["../instructions/documentation.instructions.md"],
+ [".github/agents/my-agent.md"],
[
{
"path": "shared/discussions-data-fetch.md",
@@ -162,17 +125,12 @@
"examples": [
{
"issues": {
- "types": [
- "opened"
- ]
+ "types": ["opened"]
}
},
{
"pull_request": {
- "types": [
- "opened",
- "synchronize"
- ]
+ "types": ["opened", "synchronize"]
}
},
"workflow_dispatch",
@@ -186,13 +144,7 @@
"type": "string",
"minLength": 1,
"description": "Simple trigger event name (e.g., 'push', 'issues', 'pull_request', 'discussion', 'schedule', 'fork', 'create', 'delete', 'public', 'watch', 'workflow_call'), schedule shorthand (e.g., 'daily', 'weekly'), or slash command shorthand (e.g., '/my-bot' expands to slash_command + workflow_dispatch)",
- "examples": [
- "push",
- "issues",
- "workflow_dispatch",
- "daily",
- "/my-bot"
- ]
+ "examples": ["push", "issues", "workflow_dispatch", "daily", "/my-bot"]
},
{
"type": "object",
@@ -244,16 +196,7 @@
{
"type": "string",
"description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.",
- "enum": [
- "*",
- "issues",
- "issue_comment",
- "pull_request_comment",
- "pull_request",
- "pull_request_review_comment",
- "discussion",
- "discussion_comment"
- ]
+ "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"]
},
{
"type": "array",
@@ -262,16 +205,7 @@
"items": {
"type": "string",
"description": "GitHub Actions event name.",
- "enum": [
- "*",
- "issues",
- "issue_comment",
- "pull_request_comment",
- "pull_request",
- "pull_request_review_comment",
- "discussion",
- "discussion_comment"
- ]
+ "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"]
},
"maxItems": 25
}
@@ -328,16 +262,7 @@
{
"type": "string",
"description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.",
- "enum": [
- "*",
- "issues",
- "issue_comment",
- "pull_request_comment",
- "pull_request",
- "pull_request_review_comment",
- "discussion",
- "discussion_comment"
- ]
+ "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"]
},
{
"type": "array",
@@ -346,16 +271,7 @@
"items": {
"type": "string",
"description": "GitHub Actions event name.",
- "enum": [
- "*",
- "issues",
- "issue_comment",
- "pull_request_comment",
- "pull_request",
- "pull_request_review_comment",
- "discussion",
- "discussion_comment"
- ]
+ "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"]
},
"maxItems": 25
}
@@ -420,37 +336,25 @@
},
"oneOf": [
{
- "required": [
- "branches"
- ],
+ "required": ["branches"],
"not": {
- "required": [
- "branches-ignore"
- ]
+ "required": ["branches-ignore"]
}
},
{
- "required": [
- "branches-ignore"
- ],
+ "required": ["branches-ignore"],
"not": {
- "required": [
- "branches"
- ]
+ "required": ["branches"]
}
},
{
"not": {
"anyOf": [
{
- "required": [
- "branches"
- ]
+ "required": ["branches"]
},
{
- "required": [
- "branches-ignore"
- ]
+ "required": ["branches-ignore"]
}
]
}
@@ -460,37 +364,25 @@
{
"oneOf": [
{
- "required": [
- "paths"
- ],
+ "required": ["paths"],
"not": {
- "required": [
- "paths-ignore"
- ]
+ "required": ["paths-ignore"]
}
},
{
- "required": [
- "paths-ignore"
- ],
+ "required": ["paths-ignore"],
"not": {
- "required": [
- "paths"
- ]
+ "required": ["paths"]
}
},
{
"not": {
"anyOf": [
{
- "required": [
- "paths"
- ]
+ "required": ["paths"]
},
{
- "required": [
- "paths-ignore"
- ]
+ "required": ["paths-ignore"]
}
]
}
@@ -610,37 +502,25 @@
"additionalProperties": false,
"oneOf": [
{
- "required": [
- "branches"
- ],
+ "required": ["branches"],
"not": {
- "required": [
- "branches-ignore"
- ]
+ "required": ["branches-ignore"]
}
},
{
- "required": [
- "branches-ignore"
- ],
+ "required": ["branches-ignore"],
"not": {
- "required": [
- "branches"
- ]
+ "required": ["branches"]
}
},
{
"not": {
"anyOf": [
{
- "required": [
- "branches"
- ]
+ "required": ["branches"]
},
{
- "required": [
- "branches-ignore"
- ]
+ "required": ["branches-ignore"]
}
]
}
@@ -650,37 +530,25 @@
{
"oneOf": [
{
- "required": [
- "paths"
- ],
+ "required": ["paths"],
"not": {
- "required": [
- "paths-ignore"
- ]
+ "required": ["paths-ignore"]
}
},
{
- "required": [
- "paths-ignore"
- ],
+ "required": ["paths-ignore"],
"not": {
- "required": [
- "paths"
- ]
+ "required": ["paths"]
}
},
{
"not": {
"anyOf": [
{
- "required": [
- "paths"
- ]
+ "required": ["paths"]
},
{
- "required": [
- "paths-ignore"
- ]
+ "required": ["paths-ignore"]
}
]
}
@@ -699,26 +567,7 @@
"description": "Types of issue events",
"items": {
"type": "string",
- "enum": [
- "opened",
- "edited",
- "deleted",
- "transferred",
- "pinned",
- "unpinned",
- "closed",
- "reopened",
- "assigned",
- "unassigned",
- "labeled",
- "unlabeled",
- "locked",
- "unlocked",
- "milestoned",
- "demilestoned",
- "typed",
- "untyped"
- ]
+ "enum": ["opened", "edited", "deleted", "transferred", "pinned", "unpinned", "closed", "reopened", "assigned", "unassigned", "labeled", "unlabeled", "locked", "unlocked", "milestoned", "demilestoned", "typed", "untyped"]
}
},
"names": {
@@ -756,11 +605,7 @@
"description": "Types of issue comment events",
"items": {
"type": "string",
- "enum": [
- "created",
- "edited",
- "deleted"
- ]
+ "enum": ["created", "edited", "deleted"]
}
},
"lock-for-agent": {
@@ -779,21 +624,7 @@
"description": "Types of discussion events",
"items": {
"type": "string",
- "enum": [
- "created",
- "edited",
- "deleted",
- "transferred",
- "pinned",
- "unpinned",
- "labeled",
- "unlabeled",
- "locked",
- "unlocked",
- "category_changed",
- "answered",
- "unanswered"
- ]
+ "enum": ["created", "edited", "deleted", "transferred", "pinned", "unpinned", "labeled", "unlabeled", "locked", "unlocked", "category_changed", "answered", "unanswered"]
}
}
}
@@ -808,11 +639,7 @@
"description": "Types of discussion comment events",
"items": {
"type": "string",
- "enum": [
- "created",
- "edited",
- "deleted"
- ]
+ "enum": ["created", "edited", "deleted"]
}
}
}
@@ -837,9 +664,7 @@
"description": "Cron expression using standard format (e.g., '0 9 * * 1') or fuzzy format (e.g., 'daily', 'daily around 14:00', 'daily between 9:00 and 17:00', 'weekly', 'weekly on monday', 'weekly on friday around 5pm', 'hourly', 'every 2h', 'every 10 minutes'). Fuzzy formats support: daily/weekly schedules with optional time windows, hourly intervals with scattered minutes, interval schedules (minimum 5 minutes), short duration units (m/h/d/w), and UTC timezone offsets (utc+N or utc+HH:MM)."
}
},
- "required": [
- "cron"
- ],
+ "required": ["cron"],
"additionalProperties": false
},
"maxItems": 10
@@ -889,13 +714,7 @@
},
"type": {
"type": "string",
- "enum": [
- "string",
- "choice",
- "boolean",
- "number",
- "environment"
- ],
+ "enum": ["string", "choice", "boolean", "number", "environment"],
"description": "Input type. GitHub Actions supports: string (default), boolean, choice (string with predefined options), number, and environment (string referencing a GitHub environment)"
},
"options": {
@@ -929,11 +748,7 @@
"description": "Types of workflow run events",
"items": {
"type": "string",
- "enum": [
- "completed",
- "requested",
- "in_progress"
- ]
+ "enum": ["completed", "requested", "in_progress"]
}
},
"branches": {
@@ -955,37 +770,25 @@
},
"oneOf": [
{
- "required": [
- "branches"
- ],
+ "required": ["branches"],
"not": {
- "required": [
- "branches-ignore"
- ]
+ "required": ["branches-ignore"]
}
},
{
- "required": [
- "branches-ignore"
- ],
+ "required": ["branches-ignore"],
"not": {
- "required": [
- "branches"
- ]
+ "required": ["branches"]
}
},
{
"not": {
"anyOf": [
{
- "required": [
- "branches"
- ]
+ "required": ["branches"]
},
{
- "required": [
- "branches-ignore"
- ]
+ "required": ["branches-ignore"]
}
]
}
@@ -1002,15 +805,7 @@
"description": "Types of release events",
"items": {
"type": "string",
- "enum": [
- "published",
- "unpublished",
- "created",
- "edited",
- "deleted",
- "prereleased",
- "released"
- ]
+ "enum": ["published", "unpublished", "created", "edited", "deleted", "prereleased", "released"]
}
}
}
@@ -1025,11 +820,7 @@
"description": "Types of pull request review comment events",
"items": {
"type": "string",
- "enum": [
- "created",
- "edited",
- "deleted"
- ]
+ "enum": ["created", "edited", "deleted"]
}
}
}
@@ -1044,11 +835,7 @@
"description": "Types of branch protection rule events",
"items": {
"type": "string",
- "enum": [
- "created",
- "edited",
- "deleted"
- ]
+ "enum": ["created", "edited", "deleted"]
}
}
}
@@ -1063,12 +850,7 @@
"description": "Types of check run events",
"items": {
"type": "string",
- "enum": [
- "created",
- "rerequested",
- "completed",
- "requested_action"
- ]
+ "enum": ["created", "rerequested", "completed", "requested_action"]
}
}
}
@@ -1083,9 +865,7 @@
"description": "Types of check suite events",
"items": {
"type": "string",
- "enum": [
- "completed"
- ]
+ "enum": ["completed"]
}
}
}
@@ -1178,11 +958,7 @@
"description": "Types of label events",
"items": {
"type": "string",
- "enum": [
- "created",
- "edited",
- "deleted"
- ]
+ "enum": ["created", "edited", "deleted"]
}
}
}
@@ -1197,9 +973,7 @@
"description": "Types of merge group events",
"items": {
"type": "string",
- "enum": [
- "checks_requested"
- ]
+ "enum": ["checks_requested"]
}
}
}
@@ -1214,13 +988,7 @@
"description": "Types of milestone events",
"items": {
"type": "string",
- "enum": [
- "created",
- "closed",
- "opened",
- "edited",
- "deleted"
- ]
+ "enum": ["created", "closed", "opened", "edited", "deleted"]
}
}
}
@@ -1338,37 +1106,25 @@
"additionalProperties": false,
"oneOf": [
{
- "required": [
- "branches"
- ],
+ "required": ["branches"],
"not": {
- "required": [
- "branches-ignore"
- ]
+ "required": ["branches-ignore"]
}
},
{
- "required": [
- "branches-ignore"
- ],
+ "required": ["branches-ignore"],
"not": {
- "required": [
- "branches"
- ]
+ "required": ["branches"]
}
},
{
"not": {
"anyOf": [
{
- "required": [
- "branches"
- ]
+ "required": ["branches"]
},
{
- "required": [
- "branches-ignore"
- ]
+ "required": ["branches-ignore"]
}
]
}
@@ -1378,37 +1134,25 @@
{
"oneOf": [
{
- "required": [
- "paths"
- ],
+ "required": ["paths"],
"not": {
- "required": [
- "paths-ignore"
- ]
+ "required": ["paths-ignore"]
}
},
{
- "required": [
- "paths-ignore"
- ],
+ "required": ["paths-ignore"],
"not": {
- "required": [
- "paths"
- ]
+ "required": ["paths"]
}
},
{
"not": {
"anyOf": [
{
- "required": [
- "paths"
- ]
+ "required": ["paths"]
},
{
- "required": [
- "paths-ignore"
- ]
+ "required": ["paths-ignore"]
}
]
}
@@ -1427,11 +1171,7 @@
"description": "Types of pull request review events",
"items": {
"type": "string",
- "enum": [
- "submitted",
- "edited",
- "dismissed"
- ]
+ "enum": ["submitted", "edited", "dismissed"]
}
}
}
@@ -1446,10 +1186,7 @@
"description": "Types of registry package events",
"items": {
"type": "string",
- "enum": [
- "published",
- "updated"
- ]
+ "enum": ["published", "updated"]
}
}
}
@@ -1491,9 +1228,7 @@
"description": "Types of watch events",
"items": {
"type": "string",
- "enum": [
- "started"
- ]
+ "enum": ["started"]
}
}
}
@@ -1525,11 +1260,7 @@
},
"type": {
"type": "string",
- "enum": [
- "string",
- "number",
- "boolean"
- ],
+ "enum": ["string", "number", "boolean"],
"description": "Type of the input parameter"
},
"default": {
@@ -1571,9 +1302,7 @@
},
{
"type": "object",
- "required": [
- "query"
- ],
+ "required": ["query"],
"properties": {
"query": {
"type": "string",
@@ -1599,9 +1328,7 @@
},
{
"type": "object",
- "required": [
- "query"
- ],
+ "required": ["query"],
"properties": {
"query": {
"type": "string",
@@ -1627,37 +1354,17 @@
"oneOf": [
{
"type": "string",
- "enum": [
- "+1",
- "-1",
- "laugh",
- "confused",
- "heart",
- "hooray",
- "rocket",
- "eyes",
- "none"
- ]
+ "enum": ["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes", "none"]
},
{
"type": "integer",
- "enum": [
- 1,
- -1
- ],
+ "enum": [1, -1],
"description": "YAML parses +1 and -1 without quotes as integers. These are converted to +1 and -1 strings respectively."
}
],
"default": "eyes",
"description": "AI reaction to add/remove on triggering item (one of: +1, -1, laugh, confused, heart, hooray, rocket, eyes, none). Use 'none' to disable reactions. Defaults to 'eyes' if not specified.",
- "examples": [
- "eyes",
- "rocket",
- "+1",
- 1,
- -1,
- "none"
- ]
+ "examples": ["eyes", "rocket", "+1", 1, -1, "none"]
}
},
"additionalProperties": false,
@@ -1673,37 +1380,25 @@
{
"command": {
"name": "mergefest",
- "events": [
- "pull_request_comment"
- ]
+ "events": ["pull_request_comment"]
}
},
{
"workflow_run": {
- "workflows": [
- "Dev"
- ],
- "types": [
- "completed"
- ],
- "branches": [
- "copilot/**"
- ]
+ "workflows": ["Dev"],
+ "types": ["completed"],
+ "branches": ["copilot/**"]
}
},
{
"pull_request": {
- "types": [
- "ready_for_review"
- ]
+ "types": ["ready_for_review"]
},
"workflow_dispatch": null
},
{
"push": {
- "branches": [
- "main"
- ]
+ "branches": ["main"]
}
}
]
@@ -1730,10 +1425,7 @@
"oneOf": [
{
"type": "string",
- "enum": [
- "read-all",
- "write-all"
- ],
+ "enum": ["read-all", "write-all"],
"description": "Simple permissions string: 'read-all' (all read permissions) or 'write-all' (all write permissions)"
},
{
@@ -1743,143 +1435,82 @@
"properties": {
"actions": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission for GitHub Actions workflows and runs (read: view workflows, write: manage workflows, none: no access)"
},
"attestations": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission for artifact attestations (read: view attestations, write: create attestations, none: no access)"
},
"checks": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission for repository checks and status checks (read: view checks, write: create/update checks, none: no access)"
},
"contents": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission for repository contents (read: view files, write: modify files/branches, none: no access)"
},
"deployments": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission for repository deployments (read: view deployments, write: create/update deployments, none: no access)"
},
"discussions": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission for repository discussions (read: view discussions, write: create/update discussions, none: no access)"
},
"id-token": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission level for OIDC token requests (read/write/none). Allows workflows to request JWT tokens for cloud provider authentication."
},
"issues": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission for repository issues (read: view issues, write: create/update/close issues, none: no access)"
},
"models": {
"type": "string",
- "enum": [
- "read",
- "none"
- ],
+ "enum": ["read", "none"],
"description": "Permission for GitHub Copilot models (read: access AI models for agentic workflows, none: no access)"
},
"metadata": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission for repository metadata (read: view repository information, write: update repository metadata, none: no access)"
},
"packages": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission level for GitHub Packages (read/write/none). Controls access to publish, modify, or delete packages."
},
"pages": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission level for GitHub Pages (read/write/none). Controls access to deploy and manage GitHub Pages sites."
},
"pull-requests": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission level for pull requests (read/write/none). Controls access to create, edit, review, and manage pull requests."
},
"security-events": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission level for security events (read/write/none). Controls access to view and manage code scanning alerts and security findings."
},
"statuses": {
"type": "string",
- "enum": [
- "read",
- "write",
- "none"
- ],
+ "enum": ["read", "write", "none"],
"description": "Permission level for commit statuses (read/write/none). Controls access to create and update commit status checks."
},
"all": {
"type": "string",
- "enum": [
- "read"
- ],
+ "enum": ["read"],
"description": "Permission shorthand that applies read access to all permission scopes. Can be combined with specific write permissions to override individual scopes. 'write' is not allowed for all."
}
}
@@ -1889,10 +1520,7 @@
"run-name": {
"type": "string",
"description": "Custom name for workflow runs that appears in the GitHub Actions interface (supports GitHub expressions like ${{ github.event.issue.title }})",
- "examples": [
- "Deploy to ${{ github.event.inputs.environment }}",
- "Build #${{ github.run_number }}"
- ]
+ "examples": ["Deploy to ${{ github.event.inputs.environment }}", "Build #${{ github.run_number }}"]
},
"jobs": {
"type": "object",
@@ -1935,14 +1563,10 @@
"additionalProperties": false,
"oneOf": [
{
- "required": [
- "uses"
- ]
+ "required": ["uses"]
},
{
- "required": [
- "run"
- ]
+ "required": ["run"]
}
],
"properties": {
@@ -2155,26 +1779,17 @@
],
"examples": [
"ubuntu-latest",
- [
- "ubuntu-latest",
- "self-hosted"
- ],
+ ["ubuntu-latest", "self-hosted"],
{
"group": "larger-runners",
- "labels": [
- "ubuntu-latest-8-cores"
- ]
+ "labels": ["ubuntu-latest-8-cores"]
}
]
},
"timeout-minutes": {
"type": "integer",
"description": "Workflow timeout in minutes (GitHub Actions standard field). Defaults to 20 minutes for agentic workflows. Has sensible defaults and can typically be omitted.",
- "examples": [
- 5,
- 10,
- 30
- ]
+ "examples": [5, 10, 30]
},
"timeout_minutes": {
"type": "integer",
@@ -2188,10 +1803,7 @@
{
"type": "string",
"description": "Simple concurrency group name to prevent multiple runs in the same group. Use expressions like '${{ github.workflow }}' for per-workflow isolation or '${{ github.ref }}' for per-branch isolation. Agentic workflows automatically generate enhanced concurrency policies using 'gh-aw-{engine-id}' as the default group to limit concurrent AI workloads across all workflows using the same engine.",
- "examples": [
- "my-workflow-group",
- "workflow-${{ github.ref }}"
- ]
+ "examples": ["my-workflow-group", "workflow-${{ github.ref }}"]
},
{
"type": "object",
@@ -2207,9 +1819,7 @@
"description": "Whether to cancel in-progress workflows in the same concurrency group when a new one starts. Default: false (queue new runs). Set to true for agentic workflows where only the latest run matters (e.g., PR analysis that becomes stale when new commits are pushed)."
}
},
- "required": [
- "group"
- ],
+ "required": ["group"],
"examples": [
{
"group": "dev-workflow-${{ github.ref }}",
@@ -2278,9 +1888,7 @@
{
"type": "object",
"description": "Secret with metadata",
- "required": [
- "value"
- ],
+ "required": ["value"],
"properties": {
"value": {
"type": "string",
@@ -2332,9 +1940,7 @@
"description": "A deployment URL"
}
},
- "required": [
- "name"
- ],
+ "required": ["name"],
"additionalProperties": false
}
]
@@ -2402,9 +2008,7 @@
"description": "Additional Docker container options"
}
},
- "required": [
- "image"
- ],
+ "required": ["image"],
"additionalProperties": false
}
]
@@ -2474,9 +2078,7 @@
"description": "Additional Docker container options"
}
},
- "required": [
- "image"
- ],
+ "required": ["image"],
"additionalProperties": false
}
]
@@ -2488,24 +2090,13 @@
"examples": [
"defaults",
{
- "allowed": [
- "defaults",
- "github"
- ]
+ "allowed": ["defaults", "github"]
},
{
- "allowed": [
- "defaults",
- "python",
- "node",
- "*.example.com"
- ]
+ "allowed": ["defaults", "python", "node", "*.example.com"]
},
{
- "allowed": [
- "api.openai.com",
- "*.github.com"
- ],
+ "allowed": ["api.openai.com", "*.github.com"],
"firewall": {
"version": "v1.0.0",
"log-level": "debug"
@@ -2515,9 +2106,7 @@
"oneOf": [
{
"type": "string",
- "enum": [
- "defaults"
- ],
+ "enum": ["defaults"],
"description": "Use default network permissions (basic infrastructure: certificates, JSON schema, Ubuntu, etc.)"
},
{
@@ -2557,9 +2146,7 @@
},
{
"type": "string",
- "enum": [
- "disable"
- ],
+ "enum": ["disable"],
"description": "Disable AWF firewall (triggers warning if allowed != *, error in strict mode if allowed is not * or engine does not support firewall)"
},
{
@@ -2574,27 +2161,14 @@
}
},
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "AWF version to use (empty = latest release). Can be a string (e.g., 'v1.0.0', 'latest') or number (e.g., 20, 3.11). Numeric values are automatically converted to strings at runtime.",
- "examples": [
- "v1.0.0",
- "latest",
- 20,
- 3.11
- ]
+ "examples": ["v1.0.0", "latest", 20, 3.11]
},
"log-level": {
"type": "string",
"description": "AWF log level (default: info). Valid values: debug, info, warn, error",
- "enum": [
- "debug",
- "info",
- "warn",
- "error"
- ]
+ "enum": ["debug", "info", "warn", "error"]
},
"ssl-bump": {
"type": "boolean",
@@ -2609,12 +2183,7 @@
"pattern": "^https://.*",
"description": "HTTPS URL pattern with optional wildcards (e.g., 'https://github.com/githubnext/*')"
},
- "examples": [
- [
- "https://github.com/githubnext/*",
- "https://api.github.com/repos/*"
- ]
- ]
+ "examples": [["https://github.com/githubnext/*", "https://api.github.com/repos/*"]]
}
},
"additionalProperties": false
@@ -2635,12 +2204,7 @@
},
{
"type": "string",
- "enum": [
- "default",
- "sandbox-runtime",
- "awf",
- "srt"
- ],
+ "enum": ["default", "sandbox-runtime", "awf", "srt"],
"description": "Legacy string format for sandbox type: 'default' for no sandbox, 'sandbox-runtime' or 'srt' for Anthropic Sandbox Runtime, 'awf' for Agent Workflow Firewall"
},
{
@@ -2649,12 +2213,7 @@
"properties": {
"type": {
"type": "string",
- "enum": [
- "default",
- "sandbox-runtime",
- "awf",
- "srt"
- ],
+ "enum": ["default", "sandbox-runtime", "awf", "srt"],
"description": "Legacy sandbox type field (use agent instead)"
},
"agent": {
@@ -2663,10 +2222,7 @@
"oneOf": [
{
"type": "string",
- "enum": [
- "awf",
- "srt"
- ],
+ "enum": ["awf", "srt"],
"description": "Sandbox type: 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime"
},
{
@@ -2675,18 +2231,12 @@
"properties": {
"id": {
"type": "string",
- "enum": [
- "awf",
- "srt"
- ],
+ "enum": ["awf", "srt"],
"description": "Agent identifier (replaces 'type' field in new format): 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime"
},
"type": {
"type": "string",
- "enum": [
- "awf",
- "srt"
- ],
+ "enum": ["awf", "srt"],
"description": "Legacy: Sandbox type to use (use 'id' instead)"
},
"command": {
@@ -2715,12 +2265,7 @@
"pattern": "^[^:]+:[^:]+:(ro|rw)$",
"description": "Mount specification in format 'source:destination:mode'"
},
- "examples": [
- [
- "/host/data:/data:ro",
- "/usr/local/bin/custom-tool:/usr/local/bin/custom-tool:ro"
- ]
- ]
+ "examples": [["/host/data:/data:ro", "/usr/local/bin/custom-tool:/usr/local/bin/custom-tool:ro"]]
},
"config": {
"type": "object",
@@ -2835,24 +2380,14 @@
"description": "Container image for the MCP gateway executable (required)"
},
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Optional version/tag for the container image (e.g., 'latest', 'v1.0.0')",
- "examples": [
- "latest",
- "v1.0.0"
- ]
+ "examples": ["latest", "v1.0.0"]
},
"entrypoint": {
"type": "string",
"description": "Optional custom entrypoint for the MCP gateway container. Overrides the container's default entrypoint.",
- "examples": [
- "/bin/bash",
- "/custom/start.sh",
- "/usr/bin/env"
- ]
+ "examples": ["/bin/bash", "/custom/start.sh", "/usr/bin/env"]
},
"args": {
"type": "array",
@@ -2876,12 +2411,7 @@
"pattern": "^[^:]+:[^:]+:(ro|rw)$",
"description": "Mount specification in format 'source:destination:mode'"
},
- "examples": [
- [
- "/host/data:/container/data:ro",
- "/host/config:/container/config:rw"
- ]
- ]
+ "examples": [["/host/data:/container/data:ro", "/host/config:/container/config:rw"]]
},
"env": {
"type": "object",
@@ -2906,16 +2436,11 @@
},
"domain": {
"type": "string",
- "enum": [
- "localhost",
- "host.docker.internal"
- ],
+ "enum": ["localhost", "host.docker.internal"],
"description": "Gateway domain for URL generation (default: 'host.docker.internal' when agent is enabled, 'localhost' when disabled)"
}
},
- "required": [
- "container"
- ],
+ "required": ["container"],
"additionalProperties": false
}
},
@@ -2936,10 +2461,7 @@
"type": "srt",
"config": {
"filesystem": {
- "allowWrite": [
- ".",
- "/tmp"
- ]
+ "allowWrite": [".", "/tmp"]
}
}
}
@@ -2963,10 +2485,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.",
"examples": [
- [
- "github/copilot-plugin",
- "acme/custom-tools"
- ],
+ ["github/copilot-plugin", "acme/custom-tools"],
[
"github/simple-plugin",
{
@@ -2979,10 +2498,7 @@
}
],
{
- "repos": [
- "github/copilot-plugin",
- "acme/custom-tools"
- ],
+ "repos": ["github/copilot-plugin", "acme/custom-tools"],
"github-token": "${{ secrets.CUSTOM_PLUGIN_TOKEN }}"
}
],
@@ -3000,9 +2516,7 @@
{
"type": "object",
"description": "Plugin configuration with ID and optional MCP settings for environment variables",
- "required": [
- "id"
- ],
+ "required": ["id"],
"properties": {
"id": {
"type": "string",
@@ -3038,9 +2552,7 @@
{
"type": "object",
"description": "Plugin configuration with custom GitHub token. Repos can be either strings or objects with MCP configuration.",
- "required": [
- "repos"
- ],
+ "required": ["repos"],
"properties": {
"repos": {
"type": "array",
@@ -3055,9 +2567,7 @@
{
"type": "object",
"description": "Plugin configuration with ID and optional MCP settings",
- "required": [
- "id"
- ],
+ "required": ["id"],
"properties": {
"id": {
"type": "string",
@@ -3087,9 +2597,7 @@
"github-token": {
"type": "string",
"description": "Custom GitHub token expression to use for plugin installation. Overrides the default cascading token resolution (GH_AW_PLUGINS_TOKEN -> GH_AW_GITHUB_TOKEN -> GITHUB_TOKEN).",
- "examples": [
- "${{ secrets.CUSTOM_PLUGIN_TOKEN }}"
- ]
+ "examples": ["${{ secrets.CUSTOM_PLUGIN_TOKEN }}"]
}
},
"additionalProperties": false
@@ -3099,10 +2607,7 @@
"if": {
"type": "string",
"description": "Conditional execution expression",
- "examples": [
- "${{ github.event.workflow_run.event == 'workflow_dispatch' }}",
- "${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}"
- ]
+ "examples": ["${{ github.event.workflow_run.event == 'workflow_dispatch' }}", "${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}"]
},
"steps": {
"description": "Custom workflow steps",
@@ -3220,10 +2725,7 @@
"filesystem": {
"type": "stdio",
"command": "npx",
- "args": [
- "-y",
- "@modelcontextprotocol/server-filesystem"
- ]
+ "args": ["-y", "@modelcontextprotocol/server-filesystem"]
}
},
{
@@ -3300,24 +2802,13 @@
},
"mode": {
"type": "string",
- "enum": [
- "local",
- "remote"
- ],
+ "enum": ["local", "remote"],
"description": "MCP server mode: 'local' (Docker-based, default) or 'remote' (hosted at api.githubcopilot.com)"
},
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Optional version specification for the GitHub MCP server (used with 'local' type). Can be a string (e.g., 'v1.0.0', 'latest') or number (e.g., 20, 3.11). Numeric values are automatically converted to strings at runtime.",
- "examples": [
- "v1.0.0",
- "latest",
- 20,
- 3.11
- ]
+ "examples": ["v1.0.0", "latest", 20, 3.11]
},
"args": {
"type": "array",
@@ -3382,15 +2873,7 @@
"pattern": "^[^:]+:[^:]+(:(ro|rw))?$",
"description": "Mount specification in format 'host:container:mode'"
},
- "examples": [
- [
- "/data:/data:ro",
- "/tmp:/tmp:rw"
- ],
- [
- "/opt:/opt:ro"
- ]
- ]
+ "examples": [["/data:/data:ro", "/tmp:/tmp:rw"], ["/opt:/opt:ro"]]
},
"app": {
"type": "object",
@@ -3416,10 +2899,7 @@
}
}
},
- "required": [
- "app-id",
- "private-key"
- ],
+ "required": ["app-id", "private-key"],
"additionalProperties": false,
"examples": [
{
@@ -3429,10 +2909,7 @@
{
"app-id": "${{ vars.APP_ID }}",
"private-key": "${{ secrets.APP_PRIVATE_KEY }}",
- "repositories": [
- "repo1",
- "repo2"
- ]
+ "repositories": ["repo1", "repo2"]
}
]
}
@@ -3440,30 +2917,16 @@
"additionalProperties": false,
"examples": [
{
- "toolsets": [
- "pull_requests",
- "actions",
- "repos"
- ]
+ "toolsets": ["pull_requests", "actions", "repos"]
},
{
- "allowed": [
- "search_pull_requests",
- "pull_request_read",
- "list_pull_requests",
- "get_file_contents",
- "list_commits",
- "get_commit"
- ]
+ "allowed": ["search_pull_requests", "pull_request_read", "list_pull_requests", "get_file_contents", "list_commits", "get_commit"]
},
{
"read-only": true
},
{
- "toolsets": [
- "pull_requests",
- "repos"
- ]
+ "toolsets": ["pull_requests", "repos"]
}
]
}
@@ -3471,25 +2934,14 @@
"examples": [
null,
{
- "toolsets": [
- "pull_requests",
- "actions",
- "repos"
- ]
+ "toolsets": ["pull_requests", "actions", "repos"]
},
{
- "allowed": [
- "search_pull_requests",
- "pull_request_read",
- "get_file_contents"
- ]
+ "allowed": ["search_pull_requests", "pull_request_read", "get_file_contents"]
},
{
"read-only": true,
- "toolsets": [
- "repos",
- "issues"
- ]
+ "toolsets": ["repos", "issues"]
},
false
]
@@ -3516,36 +2968,10 @@
],
"examples": [
true,
- [
- "git fetch",
- "git checkout",
- "git status",
- "git diff",
- "git log",
- "make recompile",
- "make fmt",
- "make lint",
- "make test-unit",
- "cat",
- "echo",
- "ls"
- ],
- [
- "echo",
- "ls",
- "cat"
- ],
- [
- "gh pr list *",
- "gh search prs *",
- "jq *"
- ],
- [
- "date *",
- "echo *",
- "cat",
- "ls"
- ]
+ ["git fetch", "git checkout", "git status", "git diff", "git log", "make recompile", "make fmt", "make lint", "make test-unit", "cat", "echo", "ls"],
+ ["echo", "ls", "cat"],
+ ["gh pr list *", "gh search prs *", "jq *"],
+ ["date *", "echo *", "cat", "ls"]
]
},
"web-fetch": {
@@ -3622,16 +3048,9 @@
"description": "Playwright tool configuration with custom version and domain restrictions",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Optional Playwright container version (e.g., 'v1.41.0', 1.41, 20). Numeric values are automatically converted to strings at runtime.",
- "examples": [
- "v1.41.0",
- 1.41,
- 20
- ]
+ "examples": ["v1.41.0", 1.41, 20]
},
"allowed_domains": {
"description": "Domains allowed for Playwright browser network access. Defaults to localhost only for security.",
@@ -3673,10 +3092,7 @@
"description": "Enable agentic-workflows tool with default settings (same as true)"
}
],
- "examples": [
- true,
- null
- ]
+ "examples": [true, null]
},
"cache-memory": {
"description": "Cache memory MCP configuration for persistent memory storage",
@@ -3752,10 +3168,7 @@
"description": "If true, only restore the cache without saving it back. Uses actions/cache/restore instead of actions/cache. No artifact upload step will be generated."
}
},
- "required": [
- "id",
- "key"
- ],
+ "required": ["id", "key"],
"additionalProperties": false
},
"minItems": 1,
@@ -3796,11 +3209,7 @@
"type": "integer",
"minimum": 1,
"description": "Timeout in seconds for tool/MCP server operations. Applies to all tools and MCP servers if supported by the engine. Default varies by engine (Claude: 60s, Codex: 120s).",
- "examples": [
- 60,
- 120,
- 300
- ]
+ "examples": [60, 120, 300]
},
"startup-timeout": {
"type": "integer",
@@ -3819,14 +3228,7 @@
"description": "Short syntax: array of language identifiers to enable (e.g., [\"go\", \"typescript\"])",
"items": {
"type": "string",
- "enum": [
- "go",
- "typescript",
- "python",
- "java",
- "rust",
- "csharp"
- ]
+ "enum": ["go", "typescript", "python", "java", "rust", "csharp"]
}
},
{
@@ -3834,24 +3236,14 @@
"description": "Serena configuration with custom version and language-specific settings",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Optional Serena MCP version. Numeric values are automatically converted to strings at runtime.",
- "examples": [
- "latest",
- "0.1.0",
- 1.0
- ]
+ "examples": ["latest", "0.1.0", 1.0]
},
"mode": {
"type": "string",
"description": "Serena execution mode: 'docker' (default, runs in container) or 'local' (runs locally with uvx and HTTP transport)",
- "enum": [
- "docker",
- "local"
- ],
+ "enum": ["docker", "local"],
"default": "docker"
},
"args": {
@@ -3875,10 +3267,7 @@
"type": "object",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Go version (e.g., \"1.21\", 1.21)"
},
"go-mod-file": {
@@ -3905,10 +3294,7 @@
"type": "object",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Node.js version for TypeScript (e.g., \"22\", 22)"
}
},
@@ -3927,10 +3313,7 @@
"type": "object",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Python version (e.g., \"3.12\", 3.12)"
}
},
@@ -3949,10 +3332,7 @@
"type": "object",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Java version (e.g., \"21\", 21)"
}
},
@@ -3971,10 +3351,7 @@
"type": "object",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Rust version (e.g., \"stable\", \"1.75\")"
}
},
@@ -3993,10 +3370,7 @@
"type": "object",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": ".NET version for C# (e.g., \"8.0\", 8.0)"
}
},
@@ -4228,19 +3602,11 @@
},
"type": {
"type": "string",
- "enum": [
- "stdio",
- "http",
- "remote",
- "local"
- ],
+ "enum": ["stdio", "http", "remote", "local"],
"description": "MCP connection type. Use 'stdio' for command-based or container-based servers, 'http' for HTTP-based servers. 'local' is an alias for 'stdio' and is normalized during parsing."
},
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Version of the MCP server"
},
"toolsets": {
@@ -4277,10 +3643,7 @@
"registry": {
"type": "string",
"description": "URI to installation location from MCP registry",
- "examples": [
- "https://api.mcp.github.com/v0/servers/microsoft/markitdown",
- "https://registry.npmjs.org/@my/tool"
- ]
+ "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown", "https://registry.npmjs.org/@my/tool"]
},
"allowed": {
"type": "array",
@@ -4288,28 +3651,12 @@
"type": "string"
},
"description": "List of allowed tool names (restricts which tools from the MCP server can be used)",
- "examples": [
- [
- "*"
- ],
- [
- "store_memory",
- "retrieve_memory"
- ],
- [
- "create-issue",
- "add-comment"
- ]
- ]
+ "examples": [["*"], ["store_memory", "retrieve_memory"], ["create-issue", "add-comment"]]
},
"entrypoint": {
"type": "string",
"description": "Optional entrypoint override for container (equivalent to docker run --entrypoint)",
- "examples": [
- "/bin/sh",
- "/custom/entrypoint.sh",
- "python"
- ]
+ "examples": ["/bin/sh", "/custom/entrypoint.sh", "python"]
},
"mounts": {
"type": "array",
@@ -4318,15 +3665,7 @@
"pattern": "^[^:]+:[^:]+:(ro|rw)$"
},
"description": "Volume mounts for container in format 'source:dest:mode' where mode is 'ro' or 'rw'",
- "examples": [
- [
- "/tmp/data:/data:ro"
- ],
- [
- "/workspace:/workspace:rw",
- "/config:/config:ro"
- ]
- ]
+ "examples": [["/tmp/data:/data:ro"], ["/workspace:/workspace:rw", "/config:/config:ro"]]
}
},
"additionalProperties": true
@@ -4394,25 +3733,17 @@
"description": "If true, only checks if cache entry exists and skips download"
}
},
- "required": [
- "key",
- "path"
- ],
+ "required": ["key", "path"],
"additionalProperties": false,
"examples": [
{
"key": "node-modules-${{ hashFiles('package-lock.json') }}",
"path": "node_modules",
- "restore-keys": [
- "node-modules-"
- ]
+ "restore-keys": ["node-modules-"]
},
{
"key": "build-cache-${{ github.sha }}",
- "path": [
- "dist",
- ".cache"
- ],
+ "path": ["dist", ".cache"],
"restore-keys": "build-cache-",
"fail-on-cache-miss": false
}
@@ -4473,10 +3804,7 @@
"description": "If true, only checks if cache entry exists and skips download"
}
},
- "required": [
- "key",
- "path"
- ],
+ "required": ["key", "path"],
"additionalProperties": false
}
}
@@ -4490,18 +3818,13 @@
{
"create-issue": {
"title-prefix": "[AI] ",
- "labels": [
- "automation",
- "ai-generated"
- ]
+ "labels": ["automation", "ai-generated"]
}
},
{
"create-pull-request": {
"title-prefix": "[Bot] ",
- "labels": [
- "bot"
- ]
+ "labels": ["bot"]
}
},
{
@@ -4524,19 +3847,7 @@
"type": "string",
"pattern": "^(repo|[a-zA-Z0-9][-a-zA-Z0-9]{0,38}/[a-zA-Z0-9._-]+)$"
},
- "examples": [
- [
- "repo"
- ],
- [
- "repo",
- "octocat/hello-world"
- ],
- [
- "microsoft/vscode",
- "microsoft/typescript"
- ]
- ]
+ "examples": [["repo"], ["repo", "octocat/hello-world"], ["microsoft/vscode", "microsoft/typescript"]]
},
"create-issue": {
"oneOf": [
@@ -4613,9 +3924,7 @@
},
{
"type": "boolean",
- "enum": [
- false
- ],
+ "enum": [false],
"description": "Set to false to explicitly disable expiration"
}
],
@@ -4636,33 +3945,21 @@
"examples": [
{
"title-prefix": "[ca] ",
- "labels": [
- "automation",
- "dependencies"
- ],
+ "labels": ["automation", "dependencies"],
"assignees": "copilot"
},
{
"title-prefix": "[duplicate-code] ",
- "labels": [
- "code-quality",
- "automated-analysis"
- ],
+ "labels": ["code-quality", "automated-analysis"],
"assignees": "copilot"
},
{
- "allowed-repos": [
- "org/other-repo",
- "org/another-repo"
- ],
+ "allowed-repos": ["org/other-repo", "org/another-repo"],
"title-prefix": "[cross-repo] "
},
{
"title-prefix": "[weekly-report] ",
- "labels": [
- "report",
- "automation"
- ],
+ "labels": ["report", "automation"],
"close-older-issues": true
}
]
@@ -4762,9 +4059,7 @@
{
"type": "object",
"description": "Configuration for managing GitHub Projects v2 boards. Smart tool that can add issue/PR items and update custom fields on existing items. By default it is update-only: if the project does not exist, the job fails with instructions to create it manually. To allow workflows to create missing projects, explicitly opt in via the agent output field create_if_missing=true (and/or provide a github-token override). NOTE: Projects v2 requires a Personal Access Token (PAT) or GitHub App token with appropriate permissions; the GITHUB_TOKEN cannot be used for Projects v2. Safe output items produced by the agent use type=update_project. Configuration also supports an optional views array for declaring project views to create. Safe output items produced by the agent use type=update_project and may include: project (board name), content_type (issue|pull_request), content_number, fields, and create_if_missing.",
- "required": [
- "project"
- ],
+ "required": ["project"],
"properties": {
"max": {
"type": "integer",
@@ -4780,10 +4075,7 @@
"type": "string",
"description": "Target project URL for update-project operations. This is required in the configuration for documentation purposes. Agent messages MUST explicitly include the project field in their output - the configured value is not used as a fallback. Must be a valid GitHub Projects v2 URL.",
"pattern": "^https://github\\.com/(users|orgs)/([^/]+|<[A-Z_]+>)/projects/(\\d+|<[A-Z_]+>)$",
- "examples": [
- "https://github.com/orgs/myorg/projects/123",
- "https://github.com/users/username/projects/456"
- ]
+ "examples": ["https://github.com/orgs/myorg/projects/123", "https://github.com/users/username/projects/456"]
},
"views": {
"type": "array",
@@ -4791,10 +4083,7 @@
"items": {
"type": "object",
"description": "View configuration for creating project views",
- "required": [
- "name",
- "layout"
- ],
+ "required": ["name", "layout"],
"properties": {
"name": {
"type": "string",
@@ -4802,11 +4091,7 @@
},
"layout": {
"type": "string",
- "enum": [
- "table",
- "board",
- "roadmap"
- ],
+ "enum": ["table", "board", "roadmap"],
"description": "The layout type of the view"
},
"filter": {
@@ -4833,10 +4118,7 @@
"description": "Optional array of project custom fields to create up-front.",
"items": {
"type": "object",
- "required": [
- "name",
- "data-type"
- ],
+ "required": ["name", "data-type"],
"properties": {
"name": {
"type": "string",
@@ -4844,13 +4126,7 @@
},
"data-type": {
"type": "string",
- "enum": [
- "DATE",
- "TEXT",
- "NUMBER",
- "SINGLE_SELECT",
- "ITERATION"
- ],
+ "enum": ["DATE", "TEXT", "NUMBER", "SINGLE_SELECT", "ITERATION"],
"description": "The GitHub Projects v2 custom field type"
},
"options": {
@@ -4913,10 +4189,7 @@
"items": {
"type": "object",
"description": "View configuration for creating project views",
- "required": [
- "name",
- "layout"
- ],
+ "required": ["name", "layout"],
"properties": {
"name": {
"type": "string",
@@ -4924,11 +4197,7 @@
},
"layout": {
"type": "string",
- "enum": [
- "table",
- "board",
- "roadmap"
- ],
+ "enum": ["table", "board", "roadmap"],
"description": "The layout type of the view"
},
"filter": {
@@ -4955,10 +4224,7 @@
"description": "Optional array of project custom fields to create automatically after project creation.",
"items": {
"type": "object",
- "required": [
- "name",
- "data-type"
- ],
+ "required": ["name", "data-type"],
"properties": {
"name": {
"type": "string",
@@ -4966,13 +4232,7 @@
},
"data-type": {
"type": "string",
- "enum": [
- "DATE",
- "TEXT",
- "NUMBER",
- "SINGLE_SELECT",
- "ITERATION"
- ],
+ "enum": ["DATE", "TEXT", "NUMBER", "SINGLE_SELECT", "ITERATION"],
"description": "The GitHub Projects v2 custom field type"
},
"options": {
@@ -4994,9 +4254,7 @@
"description": "Enable project creation with default configuration (max=1)"
},
{
- "enum": [
- null
- ],
+ "enum": [null],
"description": "Alternative null value syntax"
}
],
@@ -5010,9 +4268,7 @@
{
"type": "object",
"description": "Configuration for creating GitHub Project status updates. Status updates provide stakeholder communication and historical record of project progress. Requires a Personal Access Token (PAT) or GitHub App token with Projects: Read+Write permission. The GITHUB_TOKEN cannot be used for Projects v2. Status updates are created on the specified project board and appear in the Updates tab. Typically used by orchestrators to post run summaries with progress, findings, and next steps.",
- "required": [
- "project"
- ],
+ "required": ["project"],
"properties": {
"max": {
"type": "integer",
@@ -5028,10 +4284,7 @@
"type": "string",
"description": "Target project URL for status update operations. This is required in the configuration for documentation purposes. Agent messages MUST explicitly include the project field in their output - the configured value is not used as a fallback. Must be a valid GitHub Projects v2 URL.",
"pattern": "^https://github\\.com/(users|orgs)/([^/]+|<[A-Z_]+>)/projects/(\\d+|<[A-Z_]+>)$",
- "examples": [
- "https://github.com/orgs/myorg/projects/123",
- "https://github.com/users/username/projects/456"
- ]
+ "examples": ["https://github.com/orgs/myorg/projects/123", "https://github.com/users/username/projects/456"]
}
},
"additionalProperties": false,
@@ -5063,16 +4316,9 @@
"description": "Optional prefix for the discussion title"
},
"category": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Optional discussion category. Can be a category ID (string or numeric value), category name, or category slug/route. If not specified, uses the first available category. Matched first against category IDs, then against category names, then against category slugs. Numeric values are automatically converted to strings at runtime.",
- "examples": [
- "General",
- "audits",
- 123456789
- ]
+ "examples": ["General", "audits", 123456789]
},
"labels": {
"type": "array",
@@ -5133,9 +4379,7 @@
},
{
"type": "boolean",
- "enum": [
- false
- ],
+ "enum": [false],
"description": "Set to false to explicitly disable expiration"
}
],
@@ -5162,17 +4406,12 @@
"close-older-discussions": true
},
{
- "labels": [
- "weekly-report",
- "automation"
- ],
+ "labels": ["weekly-report", "automation"],
"category": "reports",
"close-older-discussions": true
},
{
- "allowed-repos": [
- "org/other-repo"
- ],
+ "allowed-repos": ["org/other-repo"],
"category": "General"
}
]
@@ -5230,10 +4469,7 @@
"required-category": "Ideas"
},
{
- "required-labels": [
- "resolved",
- "completed"
- ],
+ "required-labels": ["resolved", "completed"],
"max": 1
}
]
@@ -5340,10 +4576,7 @@
"required-title-prefix": "[refactor] "
},
{
- "required-labels": [
- "automated",
- "stale"
- ],
+ "required-labels": ["automated", "stale"],
"max": 10
}
]
@@ -5397,10 +4630,7 @@
"required-title-prefix": "[bot] "
},
{
- "required-labels": [
- "automated",
- "outdated"
- ],
+ "required-labels": ["automated", "outdated"],
"max": 5
}
]
@@ -5454,10 +4684,7 @@
"required-title-prefix": "[bot] "
},
{
- "required-labels": [
- "automated",
- "ready"
- ],
+ "required-labels": ["automated", "ready"],
"max": 1
}
]
@@ -5515,13 +4742,7 @@
"description": "List of allowed reasons for hiding older comments when hide-older-comments is enabled. Default: all reasons allowed (spam, abuse, off_topic, outdated, resolved).",
"items": {
"type": "string",
- "enum": [
- "spam",
- "abuse",
- "off_topic",
- "outdated",
- "resolved"
- ]
+ "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"]
}
}
},
@@ -5590,11 +4811,7 @@
},
"if-no-changes": {
"type": "string",
- "enum": [
- "warn",
- "error",
- "ignore"
- ],
+ "enum": ["warn", "error", "ignore"],
"description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)"
},
"allow-empty": {
@@ -5641,19 +4858,13 @@
"examples": [
{
"title-prefix": "[docs] ",
- "labels": [
- "documentation",
- "automation"
- ],
+ "labels": ["documentation", "automation"],
"reviewers": "copilot",
"draft": false
},
{
"title-prefix": "[security-fix] ",
- "labels": [
- "security",
- "automated-fix"
- ],
+ "labels": ["security", "automated-fix"],
"reviewers": "copilot"
}
]
@@ -5680,10 +4891,7 @@
"side": {
"type": "string",
"description": "Side of the diff for comments: 'LEFT' or 'RIGHT' (default: 'RIGHT')",
- "enum": [
- "LEFT",
- "RIGHT"
- ]
+ "enum": ["LEFT", "RIGHT"]
},
"target": {
"type": "string",
@@ -5959,10 +5167,7 @@
"minimum": 1
},
"target": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Target issue/PR to assign agents to. Use 'triggering' (default) for the triggering issue/PR, '*' to require explicit issue_number/pull_number, or a specific issue/PR number. With 'triggering', auto-resolves from github.event.issue.number or github.event.pull_request.number."
},
"target-repo": {
@@ -6007,10 +5212,7 @@
"minimum": 1
},
"target": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Target issue to assign users to. Use 'triggering' (default) for the triggering issue, '*' to allow any issue, or a specific issue number."
},
"target-repo": {
@@ -6150,11 +5352,7 @@
"operation": {
"type": "string",
"description": "Default operation for body updates: 'append' (add to end), 'prepend' (add to start), or 'replace' (overwrite completely). Defaults to 'replace' if not specified.",
- "enum": [
- "append",
- "prepend",
- "replace"
- ]
+ "enum": ["append", "prepend", "replace"]
},
"max": {
"type": "integer",
@@ -6211,11 +5409,7 @@
},
"if-no-changes": {
"type": "string",
- "enum": [
- "warn",
- "error",
- "ignore"
- ],
+ "enum": ["warn", "error", "ignore"],
"description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)"
},
"commit-title-suffix": {
@@ -6261,13 +5455,7 @@
"description": "List of allowed reasons for hiding comments. Default: all reasons allowed (spam, abuse, off_topic, outdated, resolved).",
"items": {
"type": "string",
- "enum": [
- "spam",
- "abuse",
- "off_topic",
- "outdated",
- "resolved"
- ]
+ "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"]
}
}
},
@@ -6304,9 +5492,7 @@
"description": "GitHub token to use for dispatching workflows. Overrides global github-token if specified."
}
},
- "required": [
- "workflows"
- ],
+ "required": ["workflows"],
"additionalProperties": false
},
{
@@ -6535,10 +5721,7 @@
"staged": {
"type": "boolean",
"description": "If true, emit step summary messages instead of making GitHub API calls (preview mode)",
- "examples": [
- true,
- false
- ]
+ "examples": [true, false]
},
"env": {
"type": "object",
@@ -6554,11 +5737,7 @@
"github-token": {
"$ref": "#/$defs/github_token",
"description": "GitHub token to use for safe output jobs. Typically a secret reference like ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }}",
- "examples": [
- "${{ secrets.GITHUB_TOKEN }}",
- "${{ secrets.CUSTOM_PAT }}",
- "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"
- ]
+ "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"]
},
"app": {
"type": "object",
@@ -6567,25 +5746,17 @@
"app-id": {
"type": "string",
"description": "GitHub App ID. Should reference a variable (e.g., ${{ vars.APP_ID }}).",
- "examples": [
- "${{ vars.APP_ID }}",
- "${{ secrets.APP_ID }}"
- ]
+ "examples": ["${{ vars.APP_ID }}", "${{ secrets.APP_ID }}"]
},
"private-key": {
"type": "string",
"description": "GitHub App private key. Should reference a secret (e.g., ${{ secrets.APP_PRIVATE_KEY }}).",
- "examples": [
- "${{ secrets.APP_PRIVATE_KEY }}"
- ]
+ "examples": ["${{ secrets.APP_PRIVATE_KEY }}"]
},
"owner": {
"type": "string",
"description": "Optional: The owner of the GitHub App installation. If empty, defaults to the current repository owner.",
- "examples": [
- "my-organization",
- "${{ github.repository_owner }}"
- ]
+ "examples": ["my-organization", "${{ github.repository_owner }}"]
},
"repositories": {
"type": "array",
@@ -6593,21 +5764,10 @@
"items": {
"type": "string"
},
- "examples": [
- [
- "repo1",
- "repo2"
- ],
- [
- "my-repo"
- ]
- ]
+ "examples": [["repo1", "repo2"], ["my-repo"]]
}
},
- "required": [
- "app-id",
- "private-key"
- ],
+ "required": ["app-id", "private-key"],
"additionalProperties": false
},
"max-patch-size": {
@@ -6765,13 +5925,7 @@
},
"type": {
"type": "string",
- "enum": [
- "string",
- "boolean",
- "choice",
- "number",
- "environment"
- ],
+ "enum": ["string", "boolean", "choice", "number", "environment"],
"description": "Input parameter type. Supports: string (default), boolean, choice (string with predefined options), number, and environment (string referencing a GitHub environment)",
"default": "string"
},
@@ -6808,81 +5962,52 @@
"footer": {
"type": "string",
"description": "Custom footer message template for AI-generated content. Available placeholders: {workflow_name}, {run_url}, {triggering_number}, {workflow_source}, {workflow_source_url}. Example: '> Generated by [{workflow_name}]({run_url})'",
- "examples": [
- "> Generated by [{workflow_name}]({run_url})",
- "> AI output from [{workflow_name}]({run_url}) for #{triggering_number}"
- ]
+ "examples": ["> Generated by [{workflow_name}]({run_url})", "> AI output from [{workflow_name}]({run_url}) for #{triggering_number}"]
},
"footer-install": {
"type": "string",
"description": "Custom installation instructions template appended to the footer. Available placeholders: {workflow_source}, {workflow_source_url}. Example: '> Install: `gh aw add {workflow_source}`'",
- "examples": [
- "> Install: `gh aw add {workflow_source}`",
- "> [Add this workflow]({workflow_source_url})"
- ]
+ "examples": ["> Install: `gh aw add {workflow_source}`", "> [Add this workflow]({workflow_source_url})"]
},
"footer-workflow-recompile": {
"type": "string",
"description": "Custom footer message template for workflow recompile issues. Available placeholders: {workflow_name}, {run_url}, {repository}. Example: '> Workflow sync report by [{workflow_name}]({run_url}) for {repository}'",
- "examples": [
- "> Workflow sync report by [{workflow_name}]({run_url}) for {repository}",
- "> Maintenance report by [{workflow_name}]({run_url})"
- ]
+ "examples": ["> Workflow sync report by [{workflow_name}]({run_url}) for {repository}", "> Maintenance report by [{workflow_name}]({run_url})"]
},
"footer-workflow-recompile-comment": {
"type": "string",
"description": "Custom footer message template for comments on workflow recompile issues. Available placeholders: {workflow_name}, {run_url}, {repository}. Example: '> Update from [{workflow_name}]({run_url}) for {repository}'",
- "examples": [
- "> Update from [{workflow_name}]({run_url}) for {repository}",
- "> Maintenance update by [{workflow_name}]({run_url})"
- ]
+ "examples": ["> Update from [{workflow_name}]({run_url}) for {repository}", "> Maintenance update by [{workflow_name}]({run_url})"]
},
"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}"
- ]
+ "examples": ["\ud83c\udfad Preview: {operation}", "## Staged Mode: {operation}"]
},
"staged-description": {
"type": "string",
"description": "Custom description template for staged mode preview. Available placeholders: {operation}. Example: 'The following {operation} would occur if staged mode was disabled:'",
- "examples": [
- "The following {operation} would occur if staged mode was disabled:"
- ]
+ "examples": ["The following {operation} would occur if staged mode was disabled:"]
},
"run-started": {
"type": "string",
"description": "Custom message template for workflow activation comment. Available placeholders: {workflow_name}, {run_url}, {event_type}. Default: 'Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.'",
- "examples": [
- "Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.",
- "[{workflow_name}]({run_url}) started processing this {event_type}."
- ]
+ "examples": ["Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.", "[{workflow_name}]({run_url}) started processing this {event_type}."]
},
"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."
- ]
+ "examples": ["\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.", "\u2705 [{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}."
- ]
+ "examples": ["\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", "\u274c [{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})."
- ]
+ "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})."]
},
"append-only-comments": {
"type": "boolean",
@@ -6966,9 +6091,7 @@
"oneOf": [
{
"type": "string",
- "enum": [
- "all"
- ],
+ "enum": ["all"],
"description": "Allow any authenticated user to trigger the workflow (\u26a0\ufe0f disables permission checking entirely - use with caution)"
},
{
@@ -6976,13 +6099,7 @@
"description": "List of repository permission levels that can trigger the workflow. Permission checks are automatically applied to potentially unsafe triggers.",
"items": {
"type": "string",
- "enum": [
- "admin",
- "maintainer",
- "maintain",
- "write",
- "triage"
- ],
+ "enum": ["admin", "maintainer", "maintain", "write", "triage"],
"description": "Repository permission level: 'admin' (full access), 'maintainer'/'maintain' (repository management), 'write' (push access), 'triage' (issue management)"
},
"minItems": 1,
@@ -7004,10 +6121,7 @@
"default": true,
"$comment": "Strict mode enforces several security constraints that are validated in Go code (pkg/workflow/strict_mode_validation.go) rather than JSON Schema: (1) Write Permissions + Safe Outputs: When strict=true AND permissions contains write values (contents:write, issues:write, pull-requests:write), safe-outputs must be configured. This relationship is too complex for JSON Schema as it requires checking if ANY permission property has a 'write' value. (2) Network Requirements: When strict=true, the 'network' field must be present and cannot contain standalone wildcard '*' (but patterns like '*.example.com' ARE allowed). (3) MCP Container Network: Custom MCP servers with containers require explicit network configuration. (4) Action Pinning: Actions must be pinned to commit SHAs. These are enforced during compilation via validateStrictMode().",
"description": "Enable strict mode validation for enhanced security and compliance. Strict mode enforces: (1) Write Permissions - refuses contents:write, issues:write, pull-requests:write; requires safe-outputs instead, (2) Network Configuration - requires explicit network configuration with no standalone wildcard '*' in allowed domains (patterns like '*.example.com' are allowed), (3) Action Pinning - enforces actions pinned to commit SHAs instead of tags/branches, (4) MCP Network - requires network configuration for custom MCP servers with containers, (5) Deprecated Fields - refuses deprecated frontmatter fields. Can be enabled per-workflow via 'strict: true' in frontmatter, or disabled via 'strict: false'. CLI flag takes precedence over frontmatter (gh aw compile --strict enforces strict mode). Defaults to true. See: https://github.github.com/gh-aw/reference/frontmatter/#strict-mode-strict",
- "examples": [
- true,
- false
- ]
+ "examples": [true, false]
},
"safe-inputs": {
"type": "object",
@@ -7016,9 +6130,7 @@
"^([a-ln-z][a-z0-9_-]*|m[a-np-z][a-z0-9_-]*|mo[a-ce-z][a-z0-9_-]*|mod[a-df-z][a-z0-9_-]*|mode[a-z0-9_-]+)$": {
"type": "object",
"description": "Custom tool definition. The key is the tool name (lowercase alphanumeric with dashes/underscores).",
- "required": [
- "description"
- ],
+ "required": ["description"],
"properties": {
"description": {
"type": "string",
@@ -7032,13 +6144,7 @@
"properties": {
"type": {
"type": "string",
- "enum": [
- "string",
- "number",
- "boolean",
- "array",
- "object"
- ],
+ "enum": ["string", "number", "boolean", "array", "object"],
"default": "string",
"description": "The JSON schema type of the input parameter."
},
@@ -7092,108 +6198,71 @@
"description": "Timeout in seconds for tool execution. Default is 60 seconds. Applies to shell (run) and Python (py) tools.",
"default": 60,
"minimum": 1,
- "examples": [
- 30,
- 60,
- 120,
- 300
- ]
+ "examples": [30, 60, 120, 300]
}
},
"additionalProperties": false,
"oneOf": [
{
- "required": [
- "script"
- ],
+ "required": ["script"],
"not": {
"anyOf": [
{
- "required": [
- "run"
- ]
+ "required": ["run"]
},
{
- "required": [
- "py"
- ]
+ "required": ["py"]
},
{
- "required": [
- "go"
- ]
+ "required": ["go"]
}
]
}
},
{
- "required": [
- "run"
- ],
+ "required": ["run"],
"not": {
"anyOf": [
{
- "required": [
- "script"
- ]
+ "required": ["script"]
},
{
- "required": [
- "py"
- ]
+ "required": ["py"]
},
{
- "required": [
- "go"
- ]
+ "required": ["go"]
}
]
}
},
{
- "required": [
- "py"
- ],
+ "required": ["py"],
"not": {
"anyOf": [
{
- "required": [
- "script"
- ]
+ "required": ["script"]
},
{
- "required": [
- "run"
- ]
+ "required": ["run"]
},
{
- "required": [
- "go"
- ]
+ "required": ["go"]
}
]
}
},
{
- "required": [
- "go"
- ],
+ "required": ["go"],
"not": {
"anyOf": [
{
- "required": [
- "script"
- ]
+ "required": ["script"]
},
{
- "required": [
- "run"
- ]
+ "required": ["run"]
},
{
- "required": [
- "py"
- ]
+ "required": ["py"]
}
]
}
@@ -7251,18 +6320,9 @@
"description": "Runtime configuration object identified by runtime ID (e.g., 'node', 'python', 'go')",
"properties": {
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Runtime version as a string (e.g., '22', '3.12', 'latest') or number (e.g., 22, 3.12). Numeric values are automatically converted to strings at runtime.",
- "examples": [
- "22",
- "3.12",
- "latest",
- 22,
- 3.12
- ]
+ "examples": ["22", "3.12", "latest", 22, 3.12]
},
"action-repo": {
"type": "string",
@@ -7299,9 +6359,7 @@
}
}
},
- "required": [
- "slash_command"
- ]
+ "required": ["slash_command"]
},
{
"properties": {
@@ -7311,9 +6369,7 @@
}
}
},
- "required": [
- "command"
- ]
+ "required": ["command"]
}
]
}
@@ -7332,9 +6388,7 @@
}
}
},
- "required": [
- "issue_comment"
- ]
+ "required": ["issue_comment"]
},
{
"properties": {
@@ -7344,9 +6398,7 @@
}
}
},
- "required": [
- "pull_request_review_comment"
- ]
+ "required": ["pull_request_review_comment"]
},
{
"properties": {
@@ -7356,9 +6408,7 @@
}
}
},
- "required": [
- "label"
- ]
+ "required": ["label"]
}
]
}
@@ -7392,12 +6442,7 @@
"oneOf": [
{
"type": "string",
- "enum": [
- "claude",
- "codex",
- "copilot",
- "custom"
- ],
+ "enum": ["claude", "codex", "copilot", "custom"],
"description": "Simple engine name: 'claude' (default, Claude Code), 'copilot' (GitHub Copilot CLI), 'codex' (OpenAI Codex CLI), or 'custom' (user-defined steps)"
},
{
@@ -7406,26 +6451,13 @@
"properties": {
"id": {
"type": "string",
- "enum": [
- "claude",
- "codex",
- "custom",
- "copilot"
- ],
+ "enum": ["claude", "codex", "custom", "copilot"],
"description": "AI engine identifier: 'claude' (Claude Code), 'codex' (OpenAI Codex CLI), 'copilot' (GitHub Copilot CLI), or 'custom' (user-defined GitHub Actions steps)"
},
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Optional version of the AI engine action (e.g., 'beta', 'stable', 20). Has sensible defaults and can typically be omitted. Numeric values are automatically converted to strings at runtime.",
- "examples": [
- "beta",
- "stable",
- 20,
- 3.11
- ]
+ "examples": ["beta", "stable", 20, 3.11]
},
"model": {
"type": "string",
@@ -7463,9 +6495,7 @@
"description": "Whether to cancel in-progress runs of the same concurrency group. Defaults to false for agentic workflow runs."
}
},
- "required": [
- "group"
- ],
+ "required": ["group"],
"additionalProperties": false
}
],
@@ -7524,9 +6554,7 @@
"description": "Human-readable description of what this pattern matches"
}
},
- "required": [
- "pattern"
- ],
+ "required": ["pattern"],
"additionalProperties": false
}
},
@@ -7546,9 +6574,7 @@
"description": "Optional array of command-line arguments to pass to the AI engine CLI. These arguments are injected after all other args but before the prompt."
}
},
- "required": [
- "id"
- ],
+ "required": ["id"],
"additionalProperties": false
}
]
@@ -7559,18 +6585,13 @@
"properties": {
"type": {
"type": "string",
- "enum": [
- "stdio",
- "local"
- ],
+ "enum": ["stdio", "local"],
"description": "MCP connection type for stdio (local is an alias for stdio)"
},
"registry": {
"type": "string",
"description": "URI to the installation location when MCP is installed from a registry",
- "examples": [
- "https://api.mcp.github.com/v0/servers/microsoft/markitdown"
- ]
+ "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown"]
},
"command": {
"type": "string",
@@ -7585,17 +6606,9 @@
"description": "Container image for stdio MCP connections"
},
"version": {
- "type": [
- "string",
- "number"
- ],
+ "type": ["string", "number"],
"description": "Optional version/tag for the container image (e.g., 'latest', 'v1.0.0', 20, 3.11). Numeric values are automatically converted to strings at runtime.",
- "examples": [
- "latest",
- "v1.0.0",
- 20,
- 3.11
- ]
+ "examples": ["latest", "v1.0.0", 20, 3.11]
},
"args": {
"type": "array",
@@ -7607,11 +6620,7 @@
"entrypoint": {
"type": "string",
"description": "Optional entrypoint override for container (equivalent to docker run --entrypoint)",
- "examples": [
- "/bin/sh",
- "/custom/entrypoint.sh",
- "python"
- ]
+ "examples": ["/bin/sh", "/custom/entrypoint.sh", "python"]
},
"entrypointArgs": {
"type": "array",
@@ -7627,15 +6636,7 @@
"pattern": "^[^:]+:[^:]+:(ro|rw)$"
},
"description": "Volume mounts for container in format 'source:dest:mode' where mode is 'ro' or 'rw'",
- "examples": [
- [
- "/tmp/data:/data:ro"
- ],
- [
- "/workspace:/workspace:rw",
- "/config:/config:ro"
- ]
- ]
+ "examples": [["/tmp/data:/data:ro"], ["/workspace:/workspace:rw", "/config:/config:ro"]]
},
"env": {
"type": "object",
@@ -7681,50 +6682,29 @@
"items": {
"type": "string"
},
- "examples": [
- [
- "*"
- ],
- [
- "store_memory",
- "retrieve_memory"
- ],
- [
- "brave_web_search"
- ]
- ]
+ "examples": [["*"], ["store_memory", "retrieve_memory"], ["brave_web_search"]]
}
},
"additionalProperties": false,
"$comment": "Validation constraints: (1) Mutual exclusion: 'command' and 'container' cannot both be specified. (2) Requirement: Either 'command' or 'container' must be provided (via 'anyOf'). (3) Type constraint: When 'type' is 'stdio' or 'local', either 'command' or 'container' is required. Note: Per-server 'network' field is deprecated and ignored.",
"anyOf": [
{
- "required": [
- "type"
- ]
+ "required": ["type"]
},
{
- "required": [
- "command"
- ]
+ "required": ["command"]
},
{
- "required": [
- "container"
- ]
+ "required": ["container"]
}
],
"not": {
"allOf": [
{
- "required": [
- "command"
- ]
+ "required": ["command"]
},
{
- "required": [
- "container"
- ]
+ "required": ["container"]
}
]
},
@@ -7733,24 +6713,17 @@
"if": {
"properties": {
"type": {
- "enum": [
- "stdio",
- "local"
- ]
+ "enum": ["stdio", "local"]
}
}
},
"then": {
"anyOf": [
{
- "required": [
- "command"
- ]
+ "required": ["command"]
},
{
- "required": [
- "container"
- ]
+ "required": ["container"]
}
]
}
@@ -7763,17 +6736,13 @@
"properties": {
"type": {
"type": "string",
- "enum": [
- "http"
- ],
+ "enum": ["http"],
"description": "MCP connection type for HTTP"
},
"registry": {
"type": "string",
"description": "URI to the installation location when MCP is installed from a registry",
- "examples": [
- "https://api.mcp.github.com/v0/servers/microsoft/markitdown"
- ]
+ "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown"]
},
"url": {
"type": "string",
@@ -7796,34 +6765,17 @@
"items": {
"type": "string"
},
- "examples": [
- [
- "*"
- ],
- [
- "store_memory",
- "retrieve_memory"
- ],
- [
- "brave_web_search"
- ]
- ]
+ "examples": [["*"], ["store_memory", "retrieve_memory"], ["brave_web_search"]]
}
},
- "required": [
- "url"
- ],
+ "required": ["url"],
"additionalProperties": false
},
"github_token": {
"type": "string",
"pattern": "^\\$\\{\\{\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*(\\s*\\|\\|\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*)*\\s*\\}\\}$",
"description": "GitHub token expression using secrets. Pattern details: `[A-Za-z_][A-Za-z0-9_]*` matches a valid secret name (starts with a letter or underscore, followed by letters, digits, or underscores). The full pattern matches expressions like `${{ secrets.NAME }}` or `${{ secrets.NAME1 || secrets.NAME2 }}`.",
- "examples": [
- "${{ secrets.GITHUB_TOKEN }}",
- "${{ secrets.CUSTOM_PAT }}",
- "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"
- ]
+ "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"]
},
"githubActionsStep": {
"type": "object",
@@ -7884,14 +6836,10 @@
"additionalProperties": false,
"anyOf": [
{
- "required": [
- "uses"
- ]
+ "required": ["uses"]
},
{
- "required": [
- "run"
- ]
+ "required": ["run"]
}
]
}
diff --git a/pkg/workflow/safe_outputs_handler_manager_token_test.go b/pkg/workflow/safe_outputs_handler_manager_token_test.go
index f62f8f2c662..a19ba91ee8f 100644
--- a/pkg/workflow/safe_outputs_handler_manager_token_test.go
+++ b/pkg/workflow/safe_outputs_handler_manager_token_test.go
@@ -166,3 +166,46 @@ func TestHandlerManagerProjectGitHubTokenEnvVar(t *testing.T) {
})
}
}
+
+// TestHandlerManagerMultipleNonProjectTokens verifies that when multiple non-project handlers
+// specify different github-token values, they are correctly included in the handler config JSON
+func TestHandlerManagerMultipleNonProjectTokens(t *testing.T) {
+ compiler := NewCompiler()
+
+ // Parse frontmatter with create-issue and update-project having different tokens
+ frontmatter := map[string]any{
+ "name": "Test Multiple Tokens",
+ "safe-outputs": map[string]any{
+ "create-issue": map[string]any{
+ "github-token": "${{ secrets.AGENT_GITHUB_TOKEN }}",
+ "title-prefix": "[test] ",
+ "max": 5,
+ },
+ "update-project": map[string]any{
+ "github-token": "${{ secrets.PROJECT_GITHUB_TOKEN }}",
+ "project": "https://github.com/orgs/myorg/projects/1",
+ "max": 10,
+ },
+ },
+ }
+
+ workflowData := &WorkflowData{
+ Name: "test-workflow",
+ SafeOutputs: compiler.extractSafeOutputsConfig(frontmatter),
+ }
+
+ // Build the handler manager step
+ steps := compiler.buildHandlerManagerStep(workflowData)
+ yamlStr := strings.Join(steps, "")
+
+ // The JSON is embedded in YAML with escaped quotes
+ // Looking for: "{\"create_issue\":{\"github-token\":\"${{ secrets.AGENT_GITHUB_TOKEN }}\""
+ assert.Contains(t, yamlStr, `create_issue`, "Expected create_issue handler in config")
+ assert.Contains(t, yamlStr, `${{ secrets.AGENT_GITHUB_TOKEN }}`, "Expected AGENT_GITHUB_TOKEN in config")
+ assert.Contains(t, yamlStr, `update_project`, "Expected update_project handler in config")
+ assert.Contains(t, yamlStr, `${{ secrets.PROJECT_GITHUB_TOKEN }}`, "Expected PROJECT_GITHUB_TOKEN in config")
+
+ // Verify that the project token is used for the github-script step (takes precedence)
+ assert.Contains(t, yamlStr, "github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }}",
+ "Expected PROJECT_GITHUB_TOKEN as the github-script token")
+}
From 6b3ebbe0d273ba77978d42f14053db0e2f6c0762 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Feb 2026 18:23:48 +0000
Subject: [PATCH 5/7] Remove test workflow files used for investigation
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
---
test-multiple-tokens.lock.yml | 1234 ---------------------------------
test-multiple-tokens.md | 20 -
2 files changed, 1254 deletions(-)
delete mode 100644 test-multiple-tokens.lock.yml
delete mode 100644 test-multiple-tokens.md
diff --git a/test-multiple-tokens.lock.yml b/test-multiple-tokens.lock.yml
deleted file mode 100644
index f7a1f21c4e5..00000000000
--- a/test-multiple-tokens.lock.yml
+++ /dev/null
@@ -1,1234 +0,0 @@
-#
-# ___ _ _
-# / _ \ | | (_)
-# | |_| | __ _ ___ _ __ | |_ _ ___
-# | _ |/ _` |/ _ \ '_ \| __| |/ __|
-# | | | | (_| | __/ | | | |_| | (__
-# \_| |_/\__, |\___|_| |_|\__|_|\___|
-# __/ |
-# _ _ |___/
-# | | | | / _| |
-# | | | | ___ _ __ _ __| |_| | _____ ____
-# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
-# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
-# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
-#
-# This file was automatically generated by gh-aw. DO NOT EDIT.
-#
-# To update this file, edit the corresponding .md file and run:
-# gh aw compile
-# For more information: https://github.com/github/gh-aw/blob/main/.github/aw/github-agentic-workflows.md
-#
-#
-# frontmatter-hash: 80b2f1f120c633bdf4dbdebd375ffe00442c2c1001765bf82559795773261d03
-
-name: "Test Multiple Tokens"
-"on":
- workflow_dispatch:
-
-permissions: {}
-
-concurrency:
- group: "gh-aw-${{ github.workflow }}"
-
-run-name: "Test Multiple Tokens"
-
-jobs:
- activation:
- runs-on: ubuntu-slim
- permissions:
- contents: read
- outputs:
- comment_id: ""
- comment_repo: ""
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- sparse-checkout: |
- actions
- persist-credentials: false
- - name: Setup Scripts
- uses: ./actions/setup
- with:
- destination: /opt/gh-aw/actions
- - name: Check workflow file timestamps
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_WORKFLOW_FILE: "test-multiple-tokens.lock.yml"
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs');
- await main();
-
- agent:
- needs: activation
- runs-on: ubuntu-latest
- permissions:
- contents: read
- concurrency:
- group: "gh-aw-copilot-${{ github.workflow }}"
- env:
- DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
- GH_AW_ASSETS_ALLOWED_EXTS: ""
- GH_AW_ASSETS_BRANCH: ""
- GH_AW_ASSETS_MAX_SIZE_KB: 0
- GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
- GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
- GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
- GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
- outputs:
- checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
- has_patch: ${{ steps.collect_output.outputs.has_patch }}
- model: ${{ steps.generate_aw_info.outputs.model }}
- output: ${{ steps.collect_output.outputs.output }}
- output_types: ${{ steps.collect_output.outputs.output_types }}
- secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- sparse-checkout: |
- actions
- persist-credentials: false
- - name: Setup Scripts
- uses: ./actions/setup
- with:
- destination: /opt/gh-aw/actions
- - name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: Create gh-aw temp directory
- run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
- - name: Configure Git credentials
- env:
- REPO_NAME: ${{ github.repository }}
- SERVER_URL: ${{ github.server_url }}
- run: |
- git config --global user.email "github-actions[bot]@users.noreply.github.com"
- git config --global user.name "github-actions[bot]"
- # Re-authenticate git with GitHub token
- SERVER_URL_STRIPPED="${SERVER_URL#https://}"
- git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
- echo "Git configured with standard GitHub Actions identity"
- - name: Checkout PR branch
- id: checkout-pr
- if: |
- github.event.pull_request
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- with:
- github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
- await main();
- - name: Generate agentic run info
- id: generate_aw_info
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const fs = require('fs');
-
- const awInfo = {
- engine_id: "copilot",
- engine_name: "GitHub Copilot CLI",
- model: process.env.GH_AW_MODEL_AGENT_COPILOT || "",
- version: "",
- agent_version: "0.0.405",
- workflow_name: "Test Multiple Tokens",
- experimental: false,
- supports_tools_allowlist: true,
- supports_http_transport: true,
- run_id: context.runId,
- run_number: context.runNumber,
- run_attempt: process.env.GITHUB_RUN_ATTEMPT,
- repository: context.repo.owner + '/' + context.repo.repo,
- ref: context.ref,
- sha: context.sha,
- actor: context.actor,
- event_name: context.eventName,
- staged: false,
- allowed_domains: ["defaults"],
- firewall_enabled: true,
- awf_version: "v0.13.12",
- awmg_version: "",
- steps: {
- firewall: "squid"
- },
- created_at: new Date().toISOString()
- };
-
- // Write to /tmp/gh-aw directory to avoid inclusion in PR
- const tmpPath = '/tmp/gh-aw/aw_info.json';
- fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2));
- console.log('Generated aw_info.json at:', tmpPath);
- console.log(JSON.stringify(awInfo, null, 2));
-
- // Set model as output for reuse in other steps/jobs
- core.setOutput('model', awInfo.model);
- - name: Validate COPILOT_GITHUB_TOKEN secret
- id: validate-secret
- run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
- env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- - name: Install GitHub Copilot CLI
- run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405
- - name: Install awf binary
- run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.13.12
- - name: Determine automatic lockdown mode for GitHub MCP server
- id: determine-automatic-lockdown
- env:
- TOKEN_CHECK: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- if: env.TOKEN_CHECK != ''
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
- await determineAutomaticLockdown(github, context, core);
- - name: Download container images
- run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:v0.0.113 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine
- - name: Write Safe Outputs Config
- run: |
- mkdir -p /opt/gh-aw/safeoutputs
- mkdir -p /tmp/gh-aw/safeoutputs
- mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > /opt/gh-aw/safeoutputs/config.json << 'EOF'
- {"create_issue":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1},"update_project":{"max":50}}
- EOF
- cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF'
- [
- {
- "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[dependabot-burner] \". Assignees [copilot] will be automatically assigned.",
- "inputSchema": {
- "additionalProperties": false,
- "properties": {
- "body": {
- "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.",
- "type": "string"
- },
- "labels": {
- "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.",
- "items": {
- "type": "string"
- },
- "type": "array"
- },
- "parent": {
- "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123def456') from a previously created issue in the same workflow run.",
- "type": [
- "number",
- "string"
- ]
- },
- "temporary_id": {
- "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 12 hex characters (e.g., 'aw_abc123def456'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.",
- "type": "string"
- },
- "title": {
- "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.",
- "type": "string"
- }
- },
- "required": [
- "title",
- "body"
- ],
- "type": "object"
- },
- "name": "create_issue"
- },
- {
- "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.",
- "inputSchema": {
- "additionalProperties": false,
- "properties": {
- "alternatives": {
- "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
- "type": "string"
- },
- "reason": {
- "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).",
- "type": "string"
- },
- "tool": {
- "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.",
- "type": "string"
- }
- },
- "required": [
- "reason"
- ],
- "type": "object"
- },
- "name": "missing_tool"
- },
- {
- "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.",
- "inputSchema": {
- "additionalProperties": false,
- "properties": {
- "message": {
- "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').",
- "type": "string"
- }
- },
- "required": [
- "message"
- ],
- "type": "object"
- },
- "name": "noop"
- },
- {
- "description": "Add or update items in GitHub Projects v2 boards. Can add issues/PRs to a project and update custom field values. Requires the project URL, content type (issue or pull_request), and content number.\n\nThree usage modes:\n1. Add/update project item: Requires project + content_type. For 'issue' or 'pull_request', also requires content_number. For 'draft_issue', requires draft_title.\n2. Create project fields: Requires project + operation='create_fields' + field_definitions.\n3. Create project view: Requires project + operation='create_view' + view.",
- "inputSchema": {
- "additionalProperties": false,
- "properties": {
- "content_number": {
- "description": "Issue or pull request number to add to the project. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123 for issue #123, or 456 in github.com/owner/repo/pull/456 for PR #456). Required when content_type is 'issue' or 'pull_request'.",
- "type": "number"
- },
- "content_type": {
- "description": "Type of item to add to the project. Use 'issue' or 'pull_request' to add existing repo content, or 'draft_issue' to create a draft item inside the project. Required when operation is not specified.",
- "enum": [
- "issue",
- "pull_request",
- "draft_issue"
- ],
- "type": "string"
- },
- "create_if_missing": {
- "description": "Whether to create the project if it doesn't exist. Defaults to false. Requires projects:write permission when true.",
- "type": "boolean"
- },
- "draft_body": {
- "description": "Optional body for a Projects v2 draft issue (markdown). Only used when content_type is 'draft_issue'.",
- "type": "string"
- },
- "draft_title": {
- "description": "Title for a Projects v2 draft issue. Required when content_type is 'draft_issue'.",
- "type": "string"
- },
- "field_definitions": {
- "description": "Field definitions to create when operation is create_fields. Required when operation='create_fields'.",
- "items": {
- "additionalProperties": false,
- "properties": {
- "data_type": {
- "description": "Field type. Use SINGLE_SELECT with options for enumerated values.",
- "enum": [
- "TEXT",
- "NUMBER",
- "DATE",
- "SINGLE_SELECT",
- "ITERATION"
- ],
- "type": "string"
- },
- "name": {
- "description": "Field name to create (e.g., 'size', 'priority').",
- "type": "string"
- },
- "options": {
- "description": "Options for SINGLE_SELECT fields.",
- "items": {
- "type": "string"
- },
- "type": "array"
- }
- },
- "required": [
- "name",
- "data_type"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "fields": {
- "description": "Custom field values to set on the project item (e.g., {'Status': 'In Progress', 'Priority': 'High'}). Field names must match custom fields defined in the project.",
- "type": "object"
- },
- "operation": {
- "description": "Optional operation mode. Use create_fields to create required fields up-front, or create_view to add a project view. When omitted, the tool adds/updates project items.",
- "enum": [
- "create_fields",
- "create_view"
- ],
- "type": "string"
- },
- "project": {
- "description": "Full GitHub project URL (e.g., 'https://github.com/orgs/myorg/projects/42' or 'https://github.com/users/username/projects/5'). Project names or numbers alone are NOT accepted.",
- "pattern": "^https://github\\.com/(orgs|users)/[^/]+/projects/\\d+$",
- "type": "string"
- },
- "view": {
- "additionalProperties": false,
- "description": "View definition to create when operation is create_view. Required when operation='create_view'.",
- "properties": {
- "filter": {
- "type": "string"
- },
- "layout": {
- "enum": [
- "table",
- "board",
- "roadmap"
- ],
- "type": "string"
- },
- "name": {
- "type": "string"
- },
- "visible_fields": {
- "description": "Field IDs to show in the view (table/board only).",
- "items": {
- "type": "number"
- },
- "type": "array"
- }
- },
- "required": [
- "name",
- "layout"
- ],
- "type": "object"
- }
- },
- "required": [
- "project"
- ],
- "type": "object"
- },
- "name": "update_project"
- },
- {
- "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.",
- "inputSchema": {
- "additionalProperties": false,
- "properties": {
- "alternatives": {
- "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
- "type": "string"
- },
- "context": {
- "description": "Additional context about the missing data or where it should come from (max 256 characters).",
- "type": "string"
- },
- "data_type": {
- "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.",
- "type": "string"
- },
- "reason": {
- "description": "Explanation of why this data is needed to complete the task (max 256 characters).",
- "type": "string"
- }
- },
- "required": [],
- "type": "object"
- },
- "name": "missing_data"
- }
- ]
- EOF
- cat > /opt/gh-aw/safeoutputs/validation.json << 'EOF'
- {
- "create_issue": {
- "defaultMax": 1,
- "fields": {
- "body": {
- "required": true,
- "type": "string",
- "sanitize": true,
- "maxLength": 65000
- },
- "labels": {
- "type": "array",
- "itemType": "string",
- "itemSanitize": true,
- "itemMaxLength": 128
- },
- "parent": {
- "issueOrPRNumber": true
- },
- "repo": {
- "type": "string",
- "maxLength": 256
- },
- "temporary_id": {
- "type": "string"
- },
- "title": {
- "required": true,
- "type": "string",
- "sanitize": true,
- "maxLength": 128
- }
- }
- },
- "missing_tool": {
- "defaultMax": 20,
- "fields": {
- "alternatives": {
- "type": "string",
- "sanitize": true,
- "maxLength": 512
- },
- "reason": {
- "required": true,
- "type": "string",
- "sanitize": true,
- "maxLength": 256
- },
- "tool": {
- "type": "string",
- "sanitize": true,
- "maxLength": 128
- }
- }
- },
- "noop": {
- "defaultMax": 1,
- "fields": {
- "message": {
- "required": true,
- "type": "string",
- "sanitize": true,
- "maxLength": 65000
- }
- }
- },
- "update_project": {
- "defaultMax": 10,
- "fields": {
- "content_number": {
- "optionalPositiveInteger": true
- },
- "content_type": {
- "type": "string",
- "enum": [
- "issue",
- "pull_request",
- "draft_issue"
- ]
- },
- "draft_body": {
- "type": "string",
- "sanitize": true,
- "maxLength": 65000
- },
- "draft_title": {
- "type": "string",
- "sanitize": true,
- "maxLength": 256
- },
- "fields": {
- "type": "object"
- },
- "issue": {
- "optionalPositiveInteger": true
- },
- "project": {
- "required": true,
- "type": "string",
- "sanitize": true,
- "maxLength": 512,
- "pattern": "^https://github\\.com/(orgs|users)/[^/]+/projects/\\d+",
- "patternError": "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"
- },
- "pull_request": {
- "optionalPositiveInteger": true
- }
- }
- }
- }
- EOF
- - name: Generate Safe Outputs MCP Server Config
- id: safe-outputs-config
- run: |
- # Generate a secure random API key (360 bits of entropy, 40+ chars)
- # Mask immediately to prevent timing vulnerabilities
- API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${API_KEY}"
-
- PORT=3001
-
- # Set outputs for next steps
- {
- echo "safe_outputs_api_key=${API_KEY}"
- echo "safe_outputs_port=${PORT}"
- } >> "$GITHUB_OUTPUT"
-
- echo "Safe Outputs MCP server will run on port ${PORT}"
-
- - name: Start Safe Outputs MCP HTTP Server
- id: safe-outputs-start
- env:
- DEBUG: '*'
- GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
- GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
- GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
- GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
- GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
- run: |
- # Environment variables are set above to prevent template injection
- export DEBUG
- export GH_AW_SAFE_OUTPUTS_PORT
- export GH_AW_SAFE_OUTPUTS_API_KEY
- export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
- export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
- export GH_AW_MCP_LOG_DIR
-
- bash /opt/gh-aw/actions/start_safe_outputs_server.sh
-
- - name: Start MCP gateway
- id: start-mcp-gateway
- env:
- GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
- GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
- GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}
- GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- run: |
- set -eo pipefail
- mkdir -p /tmp/gh-aw/mcp-config
-
- # Export gateway environment variables for MCP config and gateway script
- export MCP_GATEWAY_PORT="80"
- export MCP_GATEWAY_DOMAIN="host.docker.internal"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
- export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
- mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
- export DEBUG="*"
-
- export GH_AW_ENGINE="copilot"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.0.113'
-
- mkdir -p /home/runner/.copilot
- cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
- {
- "mcpServers": {
- "github": {
- "type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v0.30.3",
- "env": {
- "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN",
- "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
- "GITHUB_READ_ONLY": "1",
- "GITHUB_TOOLSETS": "context,repos,issues,pull_requests"
- }
- },
- "safeoutputs": {
- "type": "http",
- "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
- "headers": {
- "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
- }
- }
- },
- "gateway": {
- "port": $MCP_GATEWAY_PORT,
- "domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
- "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
- }
- }
- MCPCONFIG_EOF
- - name: Generate workflow overview
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
- await generateWorkflowOverview(core);
- - name: Create prompt with built-in context
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GH_AW_GITHUB_ACTOR: ${{ github.actor }}
- GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
- GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
- GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
- GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
- run: |
- bash /opt/gh-aw/actions/create_prompt_first.sh
- cat << 'PROMPT_EOF' > "$GH_AW_PROMPT"
-
- PROMPT_EOF
- cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT"
- cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
-
- GitHub API Access Instructions
-
- The gh CLI is NOT authenticated. Do NOT use gh commands for GitHub operations.
-
-
- To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls.
-
- Discover available tools from the safeoutputs MCP server.
-
- **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped.
-
- **Note**: If you made no other safe output tool calls during this workflow execution, call the "noop" tool to provide a status message indicating completion or that no actions were needed.
-
-
-
- The following GitHub context information is available for this workflow:
- {{#if __GH_AW_GITHUB_ACTOR__ }}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if __GH_AW_GITHUB_REPOSITORY__ }}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if __GH_AW_GITHUB_WORKSPACE__ }}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
- - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
- - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
- - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
- - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
- {{/if}}
- {{#if __GH_AW_GITHUB_RUN_ID__ }}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- PROMPT_EOF
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
-
- PROMPT_EOF
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
- {{#runtime-import test-multiple-tokens.md}}
- PROMPT_EOF
- - name: Substitute placeholders
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_GITHUB_ACTOR: ${{ github.actor }}
- GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
- GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
- GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
- GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
- with:
- script: |
- const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
-
- // Call the substitution function
- return await substitutePlaceholders({
- file: process.env.GH_AW_PROMPT,
- substitutions: {
- GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
- GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
- GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
- GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
- GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE
- }
- });
- - name: Interpolate variables and render templates
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
- await main();
- - name: Validate prompt placeholders
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
- - name: Print prompt
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- run: bash /opt/gh-aw/actions/print_prompt_summary.sh
- - name: Clean git credentials
- run: bash /opt/gh-aw/actions/clean_git_credentials.sh
- - name: Execute GitHub Copilot CLI
- id: agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
- run: |
- set -o pipefail
- sudo -E awf --enable-chroot --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.13.12 --skip-pull \
- -- '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' \
- 2>&1 | tee /tmp/gh-aw/agent-stdio.log
- env:
- COPILOT_AGENT_RUNNER_TYPE: STANDALONE
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
- GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GITHUB_HEAD_REF: ${{ github.head_ref }}
- GITHUB_REF_NAME: ${{ github.ref_name }}
- GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
- GITHUB_WORKSPACE: ${{ github.workspace }}
- XDG_CONFIG_HOME: /home/runner
- - name: Configure Git credentials
- env:
- REPO_NAME: ${{ github.repository }}
- SERVER_URL: ${{ github.server_url }}
- run: |
- git config --global user.email "github-actions[bot]@users.noreply.github.com"
- git config --global user.name "github-actions[bot]"
- # Re-authenticate git with GitHub token
- SERVER_URL_STRIPPED="${SERVER_URL#https://}"
- git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
- echo "Git configured with standard GitHub Actions identity"
- - name: Copy Copilot session state files to logs
- if: always()
- continue-on-error: true
- run: |
- # Copy Copilot session state files to logs folder for artifact collection
- # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
- SESSION_STATE_DIR="$HOME/.copilot/session-state"
- LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
-
- if [ -d "$SESSION_STATE_DIR" ]; then
- echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
- mkdir -p "$LOGS_DIR"
- cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
- echo "Session state files copied successfully"
- else
- echo "No session-state directory found at $SESSION_STATE_DIR"
- fi
- - name: Stop MCP gateway
- if: always()
- continue-on-error: true
- env:
- MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
- GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
- run: |
- bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
- - name: Redact secrets in logs
- if: always()
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs');
- await main();
- env:
- GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
- SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
- SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Upload Safe Outputs
- if: always()
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- with:
- name: safe-output
- path: ${{ env.GH_AW_SAFE_OUTPUTS }}
- if-no-files-found: warn
- - name: Ingest agent output
- id: collect_output
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
- GITHUB_SERVER_URL: ${{ github.server_url }}
- GITHUB_API_URL: ${{ github.api_url }}
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs');
- await main();
- - name: Upload sanitized agent output
- if: always() && env.GH_AW_AGENT_OUTPUT
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- with:
- name: agent-output
- path: ${{ env.GH_AW_AGENT_OUTPUT }}
- if-no-files-found: warn
- - name: Upload engine output files
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- with:
- name: agent_outputs
- path: |
- /tmp/gh-aw/sandbox/agent/logs/
- /tmp/gh-aw/redacted-urls.log
- if-no-files-found: ignore
- - name: Parse agent logs for step summary
- if: always()
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs');
- await main();
- - name: Parse MCP gateway logs for step summary
- if: always()
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs');
- await main();
- - name: Print firewall logs
- if: always()
- continue-on-error: true
- env:
- AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
- run: |
- # Fix permissions on firewall logs so they can be uploaded as artifacts
- # AWF runs with sudo, creating files owned by root
- sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
- awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
- - name: Upload agent artifacts
- if: always()
- continue-on-error: true
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- with:
- name: agent-artifacts
- path: |
- /tmp/gh-aw/aw-prompts/prompt.txt
- /tmp/gh-aw/aw_info.json
- /tmp/gh-aw/mcp-logs/
- /tmp/gh-aw/sandbox/firewall/logs/
- /tmp/gh-aw/agent-stdio.log
- /tmp/gh-aw/agent/
- if-no-files-found: ignore
-
- conclusion:
- needs:
- - activation
- - agent
- - detection
- - safe_outputs
- if: (always()) && (needs.agent.result != 'skipped')
- runs-on: ubuntu-slim
- permissions:
- contents: read
- discussions: write
- issues: write
- pull-requests: write
- outputs:
- noop_message: ${{ steps.noop.outputs.noop_message }}
- tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
- total_count: ${{ steps.missing_tool.outputs.total_count }}
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- sparse-checkout: |
- actions
- persist-credentials: false
- - name: Setup Scripts
- uses: ./actions/setup
- with:
- destination: /opt/gh-aw/actions
- - name: Debug job inputs
- env:
- COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
- AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
- AGENT_CONCLUSION: ${{ needs.agent.result }}
- run: |
- echo "Comment ID: $COMMENT_ID"
- echo "Comment Repo: $COMMENT_REPO"
- echo "Agent Output Types: $AGENT_OUTPUT_TYPES"
- echo "Agent Conclusion: $AGENT_CONCLUSION"
- - name: Download agent output artifact
- continue-on-error: true
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- with:
- name: agent-output
- path: /tmp/gh-aw/safeoutputs/
- - name: Setup agent output environment variable
- run: |
- mkdir -p /tmp/gh-aw/safeoutputs/
- find "/tmp/gh-aw/safeoutputs/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- - name: Process No-Op Messages
- id: noop
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
- GH_AW_NOOP_MAX: 1
- GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
- with:
- github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/noop.cjs');
- await main();
- - name: Record Missing Tool
- id: missing_tool
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
- GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
- with:
- github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/missing_tool.cjs');
- await main();
- - name: Handle Agent Failure
- id: handle_agent_failure
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
- GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
- GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_WORKFLOW_ID: "test-multiple-tokens"
- GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }}
- GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
- with:
- github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs');
- await main();
- - name: Handle No-Op Message
- id: handle_noop_message
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
- GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
- GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
- with:
- github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs');
- await main();
- - name: Update reaction comment with completion status
- id: conclusion
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
- GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
- GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
- GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.result }}
- with:
- github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/notify_comment_error.cjs');
- await main();
-
- detection:
- needs: agent
- if: needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true'
- runs-on: ubuntu-latest
- permissions:
- contents: read
- concurrency:
- group: "gh-aw-copilot-${{ github.workflow }}"
- timeout-minutes: 10
- outputs:
- success: ${{ steps.parse_results.outputs.success }}
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- sparse-checkout: |
- actions
- persist-credentials: false
- - name: Setup Scripts
- uses: ./actions/setup
- with:
- destination: /opt/gh-aw/actions
- - name: Download agent artifacts
- continue-on-error: true
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- with:
- name: agent-artifacts
- path: /tmp/gh-aw/threat-detection/
- - name: Download agent output artifact
- continue-on-error: true
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- with:
- name: agent-output
- path: /tmp/gh-aw/threat-detection/
- - name: Echo agent output types
- env:
- AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
- run: |
- echo "Agent output-types: $AGENT_OUTPUT_TYPES"
- - name: Setup threat detection
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- WORKFLOW_NAME: "Test Multiple Tokens"
- WORKFLOW_DESCRIPTION: "No description provided"
- HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs');
- await main();
- - name: Ensure threat-detection directory and log
- run: |
- mkdir -p /tmp/gh-aw/threat-detection
- touch /tmp/gh-aw/threat-detection/detection.log
- - name: Validate COPILOT_GITHUB_TOKEN secret
- id: validate-secret
- run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
- env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- - name: Install GitHub Copilot CLI
- run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405
- - name: Execute GitHub Copilot CLI
- id: agentic_execution
- # Copilot CLI tool arguments (sorted):
- # --allow-tool shell(cat)
- # --allow-tool shell(grep)
- # --allow-tool shell(head)
- # --allow-tool shell(jq)
- # --allow-tool shell(ls)
- # --allow-tool shell(tail)
- # --allow-tool shell(wc)
- timeout-minutes: 20
- run: |
- set -o pipefail
- COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"
- mkdir -p /tmp/
- mkdir -p /tmp/gh-aw/
- mkdir -p /tmp/gh-aw/agent/
- mkdir -p /tmp/gh-aw/sandbox/agent/logs/
- copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool 'shell(cat)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(jq)' --allow-tool 'shell(ls)' --allow-tool 'shell(tail)' --allow-tool 'shell(wc)' --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$COPILOT_CLI_INSTRUCTION"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"} 2>&1 | tee /tmp/gh-aw/threat-detection/detection.log
- env:
- COPILOT_AGENT_RUNNER_TYPE: STANDALONE
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GITHUB_HEAD_REF: ${{ github.head_ref }}
- GITHUB_REF_NAME: ${{ github.ref_name }}
- GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
- GITHUB_WORKSPACE: ${{ github.workspace }}
- XDG_CONFIG_HOME: /home/runner
- - name: Parse threat detection results
- id: parse_results
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- - name: Upload threat detection log
- if: always()
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- with:
- name: threat-detection.log
- path: /tmp/gh-aw/threat-detection/detection.log
- if-no-files-found: ignore
-
- safe_outputs:
- needs:
- - agent
- - detection
- if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.detection.outputs.success == 'true')
- runs-on: ubuntu-slim
- permissions:
- contents: read
- issues: write
- timeout-minutes: 15
- env:
- GH_AW_ENGINE_ID: "copilot"
- GH_AW_WORKFLOW_ID: "test-multiple-tokens"
- GH_AW_WORKFLOW_NAME: "Test Multiple Tokens"
- outputs:
- create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
- create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
- process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
- process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- sparse-checkout: |
- actions
- persist-credentials: false
- - name: Setup Scripts
- uses: ./actions/setup
- with:
- destination: /opt/gh-aw/actions
- safe-output-projects: 'true'
- - name: Download agent output artifact
- continue-on-error: true
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- with:
- name: agent-output
- path: /tmp/gh-aw/safeoutputs/
- - name: Setup agent output environment variable
- run: |
- mkdir -p /tmp/gh-aw/safeoutputs/
- find "/tmp/gh-aw/safeoutputs/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- - name: Process Safe Outputs
- id: process_safe_outputs
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
- GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"github-token\":\"${{ secrets.AGENT_GITHUB_TOKEN }}\",\"max\":10,\"title_prefix\":\"[dependabot-burner] \"},\"missing_data\":{},\"missing_tool\":{},\"update_project\":{\"github-token\":\"${{ secrets.PROJECT_GITHUB_TOKEN }}\",\"max\":50,\"project\":\"https://github.com/orgs/my-mona-org/projects/1\"}}"
- GH_AW_ASSIGN_COPILOT: "true"
- GH_AW_PROJECT_URL: "https://github.com/orgs/my-mona-org/projects/1"
- GH_AW_PROJECT_GITHUB_TOKEN: ${{ secrets.PROJECT_GITHUB_TOKEN }}
- with:
- github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs');
- await main();
- - name: Assign Copilot to created issues
- if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != ''
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }}
- with:
- github-token: ${{ secrets.AGENT_GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/assign_copilot_to_created_issues.cjs');
- await main();
-
diff --git a/test-multiple-tokens.md b/test-multiple-tokens.md
deleted file mode 100644
index 6d011c3dfce..00000000000
--- a/test-multiple-tokens.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Test Multiple Tokens
-on:
- workflow_dispatch:
-engine: copilot
-safe-outputs:
- create-issue:
- github-token: ${{ secrets.AGENT_GITHUB_TOKEN }}
- title-prefix: '[dependabot-burner] '
- assignees: ['copilot']
- max: 10
- update-project:
- github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }}
- project: "https://github.com/orgs/my-mona-org/projects/1"
- max: 50
----
-
-# Test Multiple GitHub Tokens
-
-Test workflow to investigate why different github-tokens in safe-outputs don't compile.
From 76373d2708b5cc00efa37944a26701d61ce53ab2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Feb 2026 18:33:32 +0000
Subject: [PATCH 6/7] Add comprehensive tests for all github-token precedence
scenarios
- Test safe-outputs level token with handlers
- Test handler-level overriding safe-outputs level
- Test all three levels (handler > safe-outputs > top-level)
- Test project handlers with safe-outputs token
- Test example from issue (safe-outputs + handler tokens)
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
...safe_outputs_handler_manager_token_test.go | 192 ++++++++++++++++++
1 file changed, 192 insertions(+)
diff --git a/pkg/workflow/safe_outputs_handler_manager_token_test.go b/pkg/workflow/safe_outputs_handler_manager_token_test.go
index a19ba91ee8f..039b74049c7 100644
--- a/pkg/workflow/safe_outputs_handler_manager_token_test.go
+++ b/pkg/workflow/safe_outputs_handler_manager_token_test.go
@@ -209,3 +209,195 @@ func TestHandlerManagerMultipleNonProjectTokens(t *testing.T) {
assert.Contains(t, yamlStr, "github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }}",
"Expected PROJECT_GITHUB_TOKEN as the github-script token")
}
+
+// TestGitHubTokenPrecedenceAllLevels verifies token precedence across all configuration levels:
+// handler-level > safe-outputs-level > top-level
+func TestGitHubTokenPrecedenceAllLevels(t *testing.T) {
+ tests := []struct {
+ name string
+ frontmatter map[string]any
+ expectedHandlerTokens map[string]string // handler name -> expected token in config
+ expectedScriptToken string // expected token for github-script step
+ }{
+ {
+ name: "safe-outputs level token used by handlers without handler-level token",
+ frontmatter: map[string]any{
+ "name": "Test Safe-Outputs Token",
+ "safe-outputs": map[string]any{
+ "github-token": "${{ secrets.SAFE_OUTPUTS_TOKEN }}",
+ "create-issue": map[string]any{
+ "title-prefix": "[test] ",
+ },
+ "add-comment": map[string]any{
+ "max": 5,
+ },
+ },
+ },
+ expectedHandlerTokens: map[string]string{
+ // Handlers should not have github-token since they inherit from safe-outputs level
+ "create_issue": "",
+ "add_comment": "",
+ },
+ expectedScriptToken: "${{ secrets.SAFE_OUTPUTS_TOKEN }}",
+ },
+ {
+ name: "handler-level token overrides safe-outputs level token",
+ frontmatter: map[string]any{
+ "name": "Test Handler Override",
+ "safe-outputs": map[string]any{
+ "github-token": "${{ secrets.SAFE_OUTPUTS_TOKEN }}",
+ "create-issue": map[string]any{
+ "github-token": "${{ secrets.ISSUE_TOKEN }}",
+ "title-prefix": "[test] ",
+ },
+ "add-comment": map[string]any{
+ "max": 5,
+ },
+ },
+ },
+ expectedHandlerTokens: map[string]string{
+ "create_issue": "${{ secrets.ISSUE_TOKEN }}",
+ "add_comment": "", // Should not have token, inherits from safe-outputs level
+ },
+ expectedScriptToken: "${{ secrets.SAFE_OUTPUTS_TOKEN }}",
+ },
+ {
+ name: "all three levels: handler > safe-outputs > top-level",
+ frontmatter: map[string]any{
+ "name": "Test All Three Levels",
+ "github-token": "${{ secrets.TOP_LEVEL_TOKEN }}",
+ "safe-outputs": map[string]any{
+ "github-token": "${{ secrets.SAFE_OUTPUTS_TOKEN }}",
+ "create-issue": map[string]any{
+ "github-token": "${{ secrets.ISSUE_TOKEN }}",
+ "title-prefix": "[test] ",
+ },
+ "add-comment": map[string]any{
+ "github-token": "${{ secrets.COMMENT_TOKEN }}",
+ "max": 5,
+ },
+ "update-issue": map[string]any{
+ "target": "issue",
+ },
+ },
+ },
+ expectedHandlerTokens: map[string]string{
+ "create_issue": "${{ secrets.ISSUE_TOKEN }}", // Has handler-level token
+ "add_comment": "${{ secrets.COMMENT_TOKEN }}", // Has handler-level token
+ "update_issue": "", // No handler-level token, inherits safe-outputs level
+ },
+ expectedScriptToken: "${{ secrets.SAFE_OUTPUTS_TOKEN }}",
+ },
+ {
+ name: "project handler with safe-outputs level token",
+ frontmatter: map[string]any{
+ "name": "Test Project with Safe-Outputs Token",
+ "safe-outputs": map[string]any{
+ "github-token": "${{ secrets.SAFE_OUTPUTS_TOKEN }}",
+ "update-project": map[string]any{
+ "github-token": "${{ secrets.PROJECT_TOKEN }}",
+ "project": "https://github.com/orgs/myorg/projects/1",
+ },
+ "create-issue": map[string]any{
+ "title-prefix": "[test] ",
+ },
+ },
+ },
+ expectedHandlerTokens: map[string]string{
+ "update_project": "${{ secrets.PROJECT_TOKEN }}",
+ "create_issue": "", // No handler-level token
+ },
+ expectedScriptToken: "${{ secrets.PROJECT_TOKEN }}", // Project token takes precedence for github-script
+ },
+ {
+ name: "top-level token only (no safe-outputs or handler tokens)",
+ frontmatter: map[string]any{
+ "name": "Test Top-Level Only",
+ "github-token": "${{ secrets.TOP_LEVEL_TOKEN }}",
+ "safe-outputs": map[string]any{
+ "create-issue": map[string]any{
+ "title-prefix": "[test] ",
+ },
+ },
+ },
+ expectedHandlerTokens: map[string]string{
+ "create_issue": "", // No handler-level or safe-outputs token
+ },
+ expectedScriptToken: "${{ secrets.TOP_LEVEL_TOKEN }}",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ compiler := NewCompiler()
+
+ // Parse frontmatter
+ workflowData := &WorkflowData{
+ Name: "test-workflow",
+ SafeOutputs: compiler.extractSafeOutputsConfig(tt.frontmatter),
+ }
+
+ // Set top-level github-token if present
+ if githubToken, ok := tt.frontmatter["github-token"].(string); ok {
+ workflowData.GitHubToken = githubToken
+ }
+
+ // Build the handler manager step
+ steps := compiler.buildHandlerManagerStep(workflowData)
+ yamlStr := strings.Join(steps, "")
+
+ // Check expected github-script token
+ assert.Contains(t, yamlStr, "github-token: "+tt.expectedScriptToken,
+ "Expected github-script step to use token: %s", tt.expectedScriptToken)
+
+ // Check each handler's token in the config JSON
+ for handlerName, expectedToken := range tt.expectedHandlerTokens {
+ if expectedToken != "" {
+ assert.Contains(t, yamlStr, handlerName, "Expected handler %s in config", handlerName)
+ assert.Contains(t, yamlStr, expectedToken, "Expected token %s for handler %s", expectedToken, handlerName)
+ }
+ }
+ })
+ }
+}
+
+// TestSafeOutputsLevelGitHubToken verifies that the safe-outputs level github-token
+// is properly used as a default for handlers without their own token
+func TestSafeOutputsLevelGitHubToken(t *testing.T) {
+ compiler := NewCompiler()
+
+ frontmatter := map[string]any{
+ "name": "Test Safe-Outputs Level Token",
+ "safe-outputs": map[string]any{
+ "github-token": "${{ secrets.SAFE_OUTPUT_GITHUB_TOKEN }}",
+ "create-issue": map[string]any{
+ "title-prefix": "[dependabot-burner] ",
+ "assignees": []string{"copilot"},
+ "max": 10,
+ },
+ "update-project": map[string]any{
+ "github-token": "${{ secrets.PROJECT_GITHUB_TOKEN }}",
+ "project": "https://github.com/orgs/my-mona-org/projects/1",
+ "max": 50,
+ },
+ },
+ }
+
+ workflowData := &WorkflowData{
+ Name: "test-workflow",
+ SafeOutputs: compiler.extractSafeOutputsConfig(frontmatter),
+ }
+
+ // Build the handler manager step
+ steps := compiler.buildHandlerManagerStep(workflowData)
+ yamlStr := strings.Join(steps, "")
+
+ // Verify that both tokens are preserved
+ assert.Contains(t, yamlStr, `create_issue`, "Expected create_issue handler in config")
+ assert.Contains(t, yamlStr, `update_project`, "Expected update_project handler in config")
+ assert.Contains(t, yamlStr, `${{ secrets.PROJECT_GITHUB_TOKEN }}`, "Expected PROJECT_GITHUB_TOKEN in update_project config")
+
+ // Verify that the project token is used for the github-script step (project token takes precedence)
+ assert.Contains(t, yamlStr, "github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }}",
+ "Expected PROJECT_GITHUB_TOKEN as the github-script token (project token has priority)")
+}
From 187b9de7764ca33e1c78f539a6daeda375c3af83 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Feb 2026 18:34:26 +0000
Subject: [PATCH 7/7] Fix naming consistency: SAFE_OUTPUT_GITHUB_TOKEN ->
SAFE_OUTPUTS_GITHUB_TOKEN
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
pkg/workflow/safe_outputs_handler_manager_token_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/workflow/safe_outputs_handler_manager_token_test.go b/pkg/workflow/safe_outputs_handler_manager_token_test.go
index 039b74049c7..17392c5f6dd 100644
--- a/pkg/workflow/safe_outputs_handler_manager_token_test.go
+++ b/pkg/workflow/safe_outputs_handler_manager_token_test.go
@@ -369,7 +369,7 @@ func TestSafeOutputsLevelGitHubToken(t *testing.T) {
frontmatter := map[string]any{
"name": "Test Safe-Outputs Level Token",
"safe-outputs": map[string]any{
- "github-token": "${{ secrets.SAFE_OUTPUT_GITHUB_TOKEN }}",
+ "github-token": "${{ secrets.SAFE_OUTPUTS_GITHUB_TOKEN }}",
"create-issue": map[string]any{
"title-prefix": "[dependabot-burner] ",
"assignees": []string{"copilot"},