diff --git a/.github/aw/schemas/agentic-workflow.json b/.github/aw/schemas/agentic-workflow.json index 9c72e6d2d15..087adc62168 100644 --- a/.github/aw/schemas/agentic-workflow.json +++ b/.github/aw/schemas/agentic-workflow.json @@ -5,45 +5,30 @@ "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, "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", "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", "minLength": 8, "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", @@ -53,18 +38,9 @@ "minLength": 1 }, "examples": [ - [ - "automation", - "security" - ], - [ - "docs", - "maintenance" - ], - [ - "ci", - "testing" - ] + ["automation", "security"], + ["docs", "maintenance"], + ["ci", "testing"] ] }, "metadata": { @@ -98,9 +74,7 @@ { "type": "object", "description": "Import specification with path and optional inputs", - "required": [ - "path" - ], + "required": ["path"], "additionalProperties": false, "properties": { "path": { @@ -129,21 +103,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", @@ -159,17 +122,12 @@ "examples": [ { "issues": { - "types": [ - "opened" - ] + "types": ["opened"] } }, { "pull_request": { - "types": [ - "opened", - "synchronize" - ] + "types": ["opened", "synchronize"] } }, "workflow_dispatch", @@ -183,13 +141,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", @@ -224,16 +176,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", @@ -242,16 +185,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"] } } ] @@ -290,16 +224,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", @@ -308,16 +233,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"] } } ] @@ -381,37 +297,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"] } ] } @@ -421,37 +325,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"] } ] } @@ -568,37 +460,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"] } ] } @@ -608,37 +488,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"] } ] } @@ -657,26 +525,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": { @@ -712,11 +561,7 @@ "description": "Types of issue comment events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } }, "lock-for-agent": { @@ -735,21 +580,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"] } } } @@ -764,11 +595,7 @@ "description": "Types of discussion comment events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } } } @@ -793,9 +620,7 @@ "description": "Cron expression using standard format (e.g., '0 9 * * 1') or human-friendly format (e.g., 'daily at 02:00', 'daily at 3pm', 'daily at 6am', 'weekly on monday', 'weekly on friday at 5pm', 'every 10 minutes', 'every 2h', 'daily at 02:00 utc+9', 'daily at 3pm utc+9'). Human-friendly formats support: daily/weekly/monthly schedules with optional time, interval schedules (minimum 5 minutes), short duration units (m/h/d/w/mo), 12-hour time format (Npm/Nam where N is 1-12), and UTC timezone offsets (utc+N or utc+HH:MM)." } }, - "required": [ - "cron" - ], + "required": ["cron"], "additionalProperties": false } } @@ -834,11 +659,7 @@ }, "type": { "type": "string", - "enum": [ - "string", - "choice", - "boolean" - ], + "enum": ["string", "choice", "boolean"], "description": "Input type" }, "options": { @@ -872,11 +693,7 @@ "description": "Types of workflow run events", "items": { "type": "string", - "enum": [ - "completed", - "requested", - "in_progress" - ] + "enum": ["completed", "requested", "in_progress"] } }, "branches": { @@ -898,37 +715,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"] } ] } @@ -945,15 +750,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"] } } } @@ -968,11 +765,7 @@ "description": "Types of pull request review comment events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } } } @@ -987,11 +780,7 @@ "description": "Types of branch protection rule events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } } } @@ -1006,12 +795,7 @@ "description": "Types of check run events", "items": { "type": "string", - "enum": [ - "created", - "rerequested", - "completed", - "requested_action" - ] + "enum": ["created", "rerequested", "completed", "requested_action"] } } } @@ -1026,9 +810,7 @@ "description": "Types of check suite events", "items": { "type": "string", - "enum": [ - "completed" - ] + "enum": ["completed"] } } } @@ -1121,11 +903,7 @@ "description": "Types of label events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } } } @@ -1140,9 +918,7 @@ "description": "Types of merge group events", "items": { "type": "string", - "enum": [ - "checks_requested" - ] + "enum": ["checks_requested"] } } } @@ -1157,13 +933,7 @@ "description": "Types of milestone events", "items": { "type": "string", - "enum": [ - "created", - "closed", - "opened", - "edited", - "deleted" - ] + "enum": ["created", "closed", "opened", "edited", "deleted"] } } } @@ -1280,37 +1050,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"] } ] } @@ -1320,37 +1078,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"] } ] } @@ -1369,11 +1115,7 @@ "description": "Types of pull request review events", "items": { "type": "string", - "enum": [ - "submitted", - "edited", - "dismissed" - ] + "enum": ["submitted", "edited", "dismissed"] } } } @@ -1388,10 +1130,7 @@ "description": "Types of registry package events", "items": { "type": "string", - "enum": [ - "published", - "updated" - ] + "enum": ["published", "updated"] } } } @@ -1433,9 +1172,7 @@ "description": "Types of watch events", "items": { "type": "string", - "enum": [ - "started" - ] + "enum": ["started"] } } } @@ -1467,11 +1204,7 @@ }, "type": { "type": "string", - "enum": [ - "string", - "number", - "boolean" - ], + "enum": ["string", "number", "boolean"], "description": "Type of the input parameter" }, "default": { @@ -1513,9 +1246,7 @@ }, { "type": "object", - "required": [ - "query" - ], + "required": ["query"], "properties": { "query": { "type": "string", @@ -1541,9 +1272,7 @@ }, { "type": "object", - "required": [ - "query" - ], + "required": ["query"], "properties": { "query": { "type": "string", @@ -1569,37 +1298,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, @@ -1615,37 +1324,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"] } } ] @@ -1672,12 +1369,7 @@ "oneOf": [ { "type": "string", - "enum": [ - "read-all", - "write-all", - "read", - "write" - ], + "enum": ["read-all", "write-all", "read", "write"], "description": "Simple permissions string: 'read-all' (all read permissions), 'write-all' (all write permissions), 'read' or 'write' (basic level)" }, { @@ -1687,137 +1379,76 @@ "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"] }, "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"] }, "pages": { "type": "string", - "enum": [ - "read", - "write", - "none" - ] + "enum": ["read", "write", "none"] }, "pull-requests": { "type": "string", - "enum": [ - "read", - "write", - "none" - ] + "enum": ["read", "write", "none"] }, "security-events": { "type": "string", - "enum": [ - "read", - "write", - "none" - ] + "enum": ["read", "write", "none"] }, "statuses": { "type": "string", - "enum": [ - "read", - "write", - "none" - ] + "enum": ["read", "write", "none"] }, "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." } } @@ -1827,10 +1458,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", @@ -1872,14 +1500,10 @@ "additionalProperties": false, "oneOf": [ { - "required": [ - "uses" - ] + "required": ["uses"] }, { - "required": [ - "run" - ] + "required": ["run"] } ], "properties": { @@ -2089,35 +1713,22 @@ ], "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", "description": "Deprecated: Use 'timeout-minutes' instead. Workflow timeout in minutes. Defaults to 20 minutes for agentic workflows.", - "examples": [ - 5, - 10, - 30 - ], + "examples": [5, 10, 30], "deprecated": true }, "concurrency": { @@ -2126,10 +1737,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", @@ -2145,9 +1753,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 }}", @@ -2224,9 +1830,7 @@ "description": "A deployment URL" } }, - "required": [ - "name" - ], + "required": ["name"], "additionalProperties": false } ] @@ -2292,9 +1896,7 @@ "description": "Additional Docker container options" } }, - "required": [ - "image" - ], + "required": ["image"], "additionalProperties": false } ] @@ -2362,9 +1964,7 @@ "description": "Additional Docker container options" } }, - "required": [ - "image" - ], + "required": ["image"], "additionalProperties": false } ] @@ -2376,24 +1976,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" @@ -2403,9 +1992,7 @@ "oneOf": [ { "type": "string", - "enum": [ - "defaults" - ], + "enum": ["defaults"], "description": "Use default network permissions (basic infrastructure: certificates, JSON schema, Ubuntu, etc.)" }, { @@ -2436,9 +2023,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)" }, { @@ -2453,27 +2038,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"] } }, "additionalProperties": false @@ -2490,12 +2062,7 @@ "oneOf": [ { "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" }, { @@ -2504,12 +2071,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": { @@ -2517,17 +2079,12 @@ "oneOf": [ { "type": "boolean", - "enum": [ - false - ], + "enum": [false], "description": "Set to false to disable the agent firewall" }, { "type": "string", - "enum": [ - "awf", - "srt" - ], + "enum": ["awf", "srt"], "description": "Sandbox type: 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime" }, { @@ -2536,18 +2093,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": { @@ -2576,12 +2127,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", @@ -2695,15 +2241,9 @@ "description": "Container image for the MCP gateway executable" }, "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"] }, "args": { "type": "array", @@ -2745,42 +2285,30 @@ "additionalProperties": false, "anyOf": [ { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] }, {} ], "not": { "allOf": [ { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] } ] }, "allOf": [ { "if": { - "required": [ - "entrypointArgs" - ] + "required": ["entrypointArgs"] }, "then": { - "required": [ - "container" - ] + "required": ["container"] } } ] @@ -2803,10 +2331,7 @@ "type": "srt", "config": { "filesystem": { - "allowWrite": [ - ".", - "/tmp" - ] + "allowWrite": [".", "/tmp"] } } } @@ -2830,10 +2355,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", @@ -2951,10 +2473,7 @@ "filesystem": { "type": "stdio", "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-filesystem" - ] + "args": ["-y", "@modelcontextprotocol/server-filesystem"] } }, { @@ -3031,24 +2550,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", @@ -3108,30 +2616,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"] } ] } @@ -3139,25 +2633,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 ] @@ -3184,36 +2667,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": { @@ -3270,16 +2727,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.", @@ -3321,10 +2771,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", @@ -3400,10 +2847,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, @@ -3447,11 +2891,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", @@ -3470,14 +2910,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"] } }, { @@ -3485,16 +2918,9 @@ "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] }, "args": { "type": "array", @@ -3517,10 +2943,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Go version (e.g., \"1.21\", 1.21)" }, "go-mod-file": { @@ -3546,10 +2969,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Node.js version for TypeScript (e.g., \"22\", 22)" } }, @@ -3567,10 +2987,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Python version (e.g., \"3.12\", 3.12)" } }, @@ -3588,10 +3005,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Java version (e.g., \"21\", 21)" } }, @@ -3609,10 +3023,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Rust version (e.g., \"stable\", \"1.75\")" } }, @@ -3630,10 +3041,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": ".NET version for C# (e.g., \"8.0\", 8.0)" } }, @@ -3851,29 +3259,16 @@ }, "mode": { "type": "string", - "enum": [ - "stdio", - "http", - "remote", - "local" - ], + "enum": ["stdio", "http", "remote", "local"], "description": "MCP server mode" }, "type": { "type": "string", - "enum": [ - "stdio", - "http", - "remote", - "local" - ], + "enum": ["stdio", "http", "remote", "local"], "description": "MCP server type" }, "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Version of the MCP server" }, "toolsets": { @@ -3971,25 +3366,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 } @@ -4048,10 +3435,7 @@ "description": "If true, only checks if cache entry exists and skips download" } }, - "required": [ - "key", - "path" - ], + "required": ["key", "path"], "additionalProperties": false } } @@ -4065,18 +3449,13 @@ { "create-issue": { "title-prefix": "[AI] ", - "labels": [ - "automation", - "ai-generated" - ] + "labels": ["automation", "ai-generated"] } }, { "create-pull-request": { "title-prefix": "[Bot] ", - "labels": [ - "bot" - ] + "labels": ["bot"] } }, { @@ -4168,25 +3547,16 @@ "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] " } ] @@ -4275,16 +3645,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", @@ -4357,17 +3720,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" } ] @@ -4420,10 +3778,7 @@ "required-category": "Ideas" }, { - "required-labels": [ - "resolved", - "completed" - ], + "required-labels": ["resolved", "completed"], "max": 1 } ] @@ -4520,10 +3875,7 @@ "required-title-prefix": "[refactor] " }, { - "required-labels": [ - "automated", - "stale" - ], + "required-labels": ["automated", "stale"], "max": 10 } ] @@ -4576,10 +3928,7 @@ "required-title-prefix": "[bot] " }, { - "required-labels": [ - "automated", - "outdated" - ], + "required-labels": ["automated", "outdated"], "max": 5 } ] @@ -4624,13 +3973,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"] } } }, @@ -4697,11 +4040,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": { @@ -4736,19 +4075,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" } ] @@ -4774,10 +4107,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", @@ -4999,10 +4329,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": { @@ -5184,11 +4511,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": { @@ -5229,13 +4552,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"] } } }, @@ -5377,10 +4694,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", @@ -5396,11 +4710,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", @@ -5409,25 +4719,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", @@ -5435,21 +4737,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": { @@ -5596,11 +4887,7 @@ }, "type": { "type": "string", - "enum": [ - "string", - "boolean", - "choice" - ], + "enum": ["string", "boolean", "choice"], "description": "Input parameter type", "default": "string" }, @@ -5637,65 +4924,42 @@ "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})"] }, "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})."] } }, "additionalProperties": false @@ -5774,9 +5038,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)" }, { @@ -5784,13 +5046,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 @@ -5811,10 +5067,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 wildcard '*'. (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 wildcard '*' in allowed domains, (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://githubnext.github.io/gh-aw/reference/frontmatter/#strict-mode-strict", - "examples": [ - true, - false - ] + "examples": [true, false] }, "safe-inputs": { "type": "object", @@ -5823,9 +5076,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", @@ -5839,13 +5090,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." }, @@ -5895,69 +5140,46 @@ "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": [ - "run" - ], + "required": ["run"], "not": { "anyOf": [ { - "required": [ - "script" - ] + "required": ["script"] }, { - "required": [ - "py" - ] + "required": ["py"] } ] } }, { - "required": [ - "py" - ], + "required": ["py"], "not": { "anyOf": [ { - "required": [ - "script" - ] + "required": ["script"] }, { - "required": [ - "run" - ] + "required": ["run"] } ] } @@ -6015,18 +5237,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", @@ -6063,9 +5276,7 @@ } } }, - "required": [ - "slash_command" - ] + "required": ["slash_command"] }, { "properties": { @@ -6075,9 +5286,7 @@ } } }, - "required": [ - "command" - ] + "required": ["command"] } ] } @@ -6096,9 +5305,7 @@ } } }, - "required": [ - "issue_comment" - ] + "required": ["issue_comment"] }, { "properties": { @@ -6108,9 +5315,7 @@ } } }, - "required": [ - "pull_request_review_comment" - ] + "required": ["pull_request_review_comment"] }, { "properties": { @@ -6120,9 +5325,7 @@ } } }, - "required": [ - "label" - ] + "required": ["label"] } ] } @@ -6156,12 +5359,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)" }, { @@ -6170,26 +5368,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", @@ -6227,9 +5412,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 } ], @@ -6284,9 +5467,7 @@ "description": "Human-readable description of what this pattern matches" } }, - "required": [ - "pattern" - ], + "required": ["pattern"], "additionalProperties": false } }, @@ -6302,9 +5483,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 } ] @@ -6315,10 +5494,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "stdio", - "local" - ], + "enum": ["stdio", "local"], "description": "MCP connection type for stdio (local is an alias for stdio)" }, "registry": { @@ -6338,17 +5514,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", @@ -6412,70 +5580,49 @@ "$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) Dependency: 'network' requires 'container' (validated in 'allOf'). (4) Type constraint: When 'type' is 'stdio' or 'local', either 'command' or 'container' is required.", "anyOf": [ { - "required": [ - "type" - ] + "required": ["type"] }, { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] } ], "not": { "allOf": [ { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] } ] }, "allOf": [ { "if": { - "required": [ - "network" - ] + "required": ["network"] }, "then": { - "required": [ - "container" - ] + "required": ["container"] } }, { "if": { "properties": { "type": { - "enum": [ - "stdio", - "local" - ] + "enum": ["stdio", "local"] } } }, "then": { "anyOf": [ { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] } ] } @@ -6488,9 +5635,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "http" - ], + "enum": ["http"], "description": "MCP connection type for HTTP" }, "registry": { @@ -6520,20 +5665,14 @@ } } }, - "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", @@ -6594,16 +5733,12 @@ "additionalProperties": false, "anyOf": [ { - "required": [ - "uses" - ] + "required": ["uses"] }, { - "required": [ - "run" - ] + "required": ["run"] } ] } } -} \ No newline at end of file +} diff --git a/.github/workflows/smoke-copilot-no-firewall.lock.yml b/.github/workflows/smoke-copilot-no-firewall.lock.yml index 5f6a2dd0d44..ee43d577efb 100644 --- a/.github/workflows/smoke-copilot-no-firewall.lock.yml +++ b/.github/workflows/smoke-copilot-no-firewall.lock.yml @@ -601,6 +601,20 @@ jobs: fi echo 'Verified: safeinputs and safeoutputs are present in configuration' + # Verify gatewayed servers (external MCP servers proxied through gateway) + echo 'Validating gatewayed servers...' + + # Validate github server + /tmp/gh-aw/actions/validate_gatewayed_server.sh "github" "/home/runner/.copilot/mcp-config.json" "http://localhost:8080" + + # Validate playwright server + /tmp/gh-aw/actions/validate_gatewayed_server.sh "playwright" "/home/runner/.copilot/mcp-config.json" "http://localhost:8080" + + # Validate serena server + /tmp/gh-aw/actions/validate_gatewayed_server.sh "serena" "/home/runner/.copilot/mcp-config.json" "http://localhost:8080" + + echo 'All gatewayed servers validated successfully' + max_retries=30 retry_count=0 gateway_url="http://localhost:8080" diff --git a/actions/setup/sh/validate_gatewayed_server.sh b/actions/setup/sh/validate_gatewayed_server.sh new file mode 100755 index 00000000000..3bea9c52d13 --- /dev/null +++ b/actions/setup/sh/validate_gatewayed_server.sh @@ -0,0 +1,73 @@ +#!/bin/bash +set -e + +# validate_gatewayed_server.sh - Validate that an MCP server is correctly gatewayed +# +# Usage: validate_gatewayed_server.sh SERVER_NAME MCP_CONFIG_PATH GATEWAY_URL +# +# Arguments: +# SERVER_NAME : Name of the MCP server to validate (e.g., "github", "playwright") +# MCP_CONFIG_PATH : Path to the MCP configuration file +# GATEWAY_URL : Expected gateway base URL (e.g., "http://localhost:8080") +# +# Validation checks: +# 1. Server exists in MCP config +# 2. Server has HTTP URL +# 3. Server type is "http" +# 4. URL points to gateway +# +# Exit codes: +# 0 - Server is correctly gatewayed +# 1 - Validation failed + +# Parse arguments +if [ "$#" -ne 3 ]; then + echo "Usage: $0 SERVER_NAME MCP_CONFIG_PATH GATEWAY_URL" >&2 + exit 1 +fi + +SERVER_NAME="$1" +MCP_CONFIG_PATH="$2" +GATEWAY_URL="$3" + +# Check if server exists in config +if ! grep -q "\"${SERVER_NAME}\"" "$MCP_CONFIG_PATH"; then + echo "ERROR: ${SERVER_NAME} server not found in MCP configuration" >&2 + exit 1 +fi + +# Extract server configuration +server_config=$(jq -r ".mcpServers.\"${SERVER_NAME}\"" "$MCP_CONFIG_PATH") +if [ "$server_config" = "null" ]; then + echo "ERROR: ${SERVER_NAME} server configuration is null" >&2 + exit 1 +fi + +# Extract URL and type +server_url=$(echo "$server_config" | jq -r '.url // empty') +server_type=$(echo "$server_config" | jq -r '.type // empty') + +# Verify URL exists +if [ -z "$server_url" ] || [ "$server_url" = "null" ]; then + echo "ERROR: ${SERVER_NAME} server does not have HTTP URL (not gatewayed correctly)" >&2 + echo "Config: $server_config" >&2 + exit 1 +fi + +# Verify type is "http" +if [ "$server_type" != "http" ]; then + echo "ERROR: ${SERVER_NAME} server type is not \"http\" (expected for gatewayed servers)" >&2 + echo "Type: $server_type" >&2 + exit 1 +fi + +# Verify URL points to gateway +if ! echo "$server_url" | grep -q "$GATEWAY_URL"; then + echo "ERROR: ${SERVER_NAME} server URL does not point to gateway" >&2 + echo "Expected gateway URL: $GATEWAY_URL" >&2 + echo "Actual URL: $server_url" >&2 + exit 1 +fi + +# Success +echo "✓ ${SERVER_NAME} server is correctly gatewayed" diff --git a/pkg/cli/actions_build_command_test.go b/pkg/cli/actions_build_command_test.go index 0ba165a07a8..1d52576e1fd 100644 --- a/pkg/cli/actions_build_command_test.go +++ b/pkg/cli/actions_build_command_test.go @@ -128,10 +128,10 @@ func TestGetActionDirectories(t *testing.T) { func TestValidateActionYml(t *testing.T) { tests := []struct { - name string + name string actionYmlContent string - expectError bool - errorContains string + expectError bool + errorContains string }{ { name: "valid node20 action", diff --git a/pkg/cli/add_command_test.go b/pkg/cli/add_command_test.go index b14331345a0..515a933147c 100644 --- a/pkg/cli/add_command_test.go +++ b/pkg/cli/add_command_test.go @@ -25,53 +25,53 @@ func TestNewAddCommand(t *testing.T) { // Verify flags are registered flags := cmd.Flags() - + // Check number flag numberFlag := flags.Lookup("number") assert.NotNil(t, numberFlag, "Should have 'number' flag") assert.Equal(t, "c", numberFlag.Shorthand, "Number flag shorthand should be 'c'") - + // Check name flag nameFlag := flags.Lookup("name") assert.NotNil(t, nameFlag, "Should have 'name' flag") assert.Equal(t, "n", nameFlag.Shorthand, "Name flag shorthand should be 'n'") - + // Check engine flag engineFlag := flags.Lookup("engine") assert.NotNil(t, engineFlag, "Should have 'engine' flag") - + // Check repo flag repoFlag := flags.Lookup("repo") assert.NotNil(t, repoFlag, "Should have 'repo' flag") assert.Equal(t, "r", repoFlag.Shorthand, "Repo flag shorthand should be 'r'") - + // Check PR flags createPRFlag := flags.Lookup("create-pull-request") assert.NotNil(t, createPRFlag, "Should have 'create-pull-request' flag") prFlag := flags.Lookup("pr") assert.NotNil(t, prFlag, "Should have 'pr' flag (alias)") - + // Check force flag forceFlag := flags.Lookup("force") assert.NotNil(t, forceFlag, "Should have 'force' flag") - + // Check append flag appendFlag := flags.Lookup("append") assert.NotNil(t, appendFlag, "Should have 'append' flag") - + // Check no-gitattributes flag noGitattributesFlag := flags.Lookup("no-gitattributes") assert.NotNil(t, noGitattributesFlag, "Should have 'no-gitattributes' flag") - + // Check dir flag dirFlag := flags.Lookup("dir") assert.NotNil(t, dirFlag, "Should have 'dir' flag") assert.Equal(t, "d", dirFlag.Shorthand, "Dir flag shorthand should be 'd'") - + // Check no-stop-after flag noStopAfterFlag := flags.Lookup("no-stop-after") assert.NotNil(t, noStopAfterFlag, "Should have 'no-stop-after' flag") - + // Check stop-after flag stopAfterFlag := flags.Lookup("stop-after") assert.NotNil(t, stopAfterFlag, "Should have 'stop-after' flag") @@ -174,7 +174,7 @@ func TestAddCommandBooleanFlags(t *testing.T) { flags := cmd.Flags() boolFlags := []string{"create-pull-request", "pr", "force", "no-gitattributes", "no-stop-after"} - + for _, flagName := range boolFlags { t.Run(flagName, func(t *testing.T) { flag := flags.Lookup(flagName) @@ -186,17 +186,17 @@ func TestAddCommandBooleanFlags(t *testing.T) { func TestAddCommandArgs(t *testing.T) { cmd := NewAddCommand(validateEngineStub) - + // Test that Args validator is set (MinimumNArgs(1)) require.NotNil(t, cmd.Args, "Args validator should be set") - + // Verify it requires at least 1 arg err := cmd.Args(cmd, []string{}) assert.Error(t, err, "Should error with no arguments") - + err = cmd.Args(cmd, []string{"workflow1"}) assert.NoError(t, err, "Should not error with 1 argument") - + err = cmd.Args(cmd, []string{"workflow1", "workflow2"}) assert.NoError(t, err, "Should not error with multiple arguments") } diff --git a/pkg/cli/generate_action_metadata_command_test.go b/pkg/cli/generate_action_metadata_command_test.go index 3ed16b52cf4..59dffbc90c9 100644 --- a/pkg/cli/generate_action_metadata_command_test.go +++ b/pkg/cli/generate_action_metadata_command_test.go @@ -97,9 +97,9 @@ module.exports = async function test(input1, required_input) { func TestExtractDescription(t *testing.T) { tests := []struct { - name string - content string - shouldMatch string + name string + content string + shouldMatch string shouldBeEmpty bool }{ { @@ -173,9 +173,9 @@ func TestGenerateHumanReadableName(t *testing.T) { func TestExtractInputs(t *testing.T) { tests := []struct { - name string - content string - minInputs int + name string + content string + minInputs int }{ { name: "action with inputs", @@ -206,9 +206,9 @@ module.exports = async function test() {};`, func TestExtractOutputs(t *testing.T) { tests := []struct { - name string - content string - minOutputs int + name string + content string + minOutputs int }{ { name: "action with outputs", @@ -240,16 +240,16 @@ module.exports = async function test() { func TestExtractDependencies(t *testing.T) { tests := []struct { - name string - content string - minDeps int + name string + content string + minDeps int }{ { name: "action with require statements", content: `const core = require('@actions/core'); const github = require('@actions/github'); module.exports = async function test() {};`, - minDeps: 0, // extractDependencies may not parse these, accept whatever it returns + minDeps: 0, // extractDependencies may not parse these, accept whatever it returns }, { name: "action without dependencies", @@ -315,16 +315,16 @@ func TestGenerateActionYml(t *testing.T) { assert.Error(t, err, "Expected an error") } else { assert.NoError(t, err, "Should not error") - + // Verify action.yml was created ymlPath := filepath.Join(actionDir, "action.yml") assert.FileExists(t, ymlPath, "action.yml should be created") - + // Verify file has content content, err := os.ReadFile(ymlPath) require.NoError(t, err, "Should be able to read action.yml") assert.NotEmpty(t, content, "action.yml should have content") - + // Verify required fields are present contentStr := string(content) assert.Contains(t, contentStr, "name:", "Should contain name field") @@ -366,16 +366,16 @@ func TestGenerateReadme(t *testing.T) { assert.Error(t, err, "Expected an error") } else { assert.NoError(t, err, "Should not error") - + // Verify README.md was created readmePath := filepath.Join(actionDir, "README.md") assert.FileExists(t, readmePath, "README.md should be created") - + // Verify file has content content, err := os.ReadFile(readmePath) require.NoError(t, err, "Should be able to read README.md") assert.NotEmpty(t, content, "README.md should have content") - + // Verify it contains the action name contentStr := string(content) assert.Contains(t, contentStr, tt.metadata.Name, "Should contain action name") diff --git a/pkg/cli/logs_command_test.go b/pkg/cli/logs_command_test.go index 3ca10d912fd..00685f6c464 100644 --- a/pkg/cli/logs_command_test.go +++ b/pkg/cli/logs_command_test.go @@ -17,57 +17,57 @@ func TestNewLogsCommand(t *testing.T) { // Verify flags are registered flags := cmd.Flags() - + // Check count flag countFlag := flags.Lookup("count") assert.NotNil(t, countFlag, "Should have 'count' flag") assert.Equal(t, "c", countFlag.Shorthand, "Count flag shorthand should be 'c'") - + // Check start-date flag startDateFlag := flags.Lookup("start-date") assert.NotNil(t, startDateFlag, "Should have 'start-date' flag") - + // Check end-date flag endDateFlag := flags.Lookup("end-date") assert.NotNil(t, endDateFlag, "Should have 'end-date' flag") - + // Check engine flag engineFlag := flags.Lookup("engine") assert.NotNil(t, engineFlag, "Should have 'engine' flag") - + // Check firewall flags firewallFlag := flags.Lookup("firewall") assert.NotNil(t, firewallFlag, "Should have 'firewall' flag") noFirewallFlag := flags.Lookup("no-firewall") assert.NotNil(t, noFirewallFlag, "Should have 'no-firewall' flag") - + // Check output flag outputFlag := flags.Lookup("output") assert.NotNil(t, outputFlag, "Should have 'output' flag") assert.Equal(t, "o", outputFlag.Shorthand, "Output flag shorthand should be 'o'") - + // Check ref flag refFlag := flags.Lookup("ref") assert.NotNil(t, refFlag, "Should have 'ref' flag") - + // Check run ID filters afterRunIDFlag := flags.Lookup("after-run-id") assert.NotNil(t, afterRunIDFlag, "Should have 'after-run-id' flag") beforeRunIDFlag := flags.Lookup("before-run-id") assert.NotNil(t, beforeRunIDFlag, "Should have 'before-run-id' flag") - + // Check tool-graph flag toolGraphFlag := flags.Lookup("tool-graph") assert.NotNil(t, toolGraphFlag, "Should have 'tool-graph' flag") - + // Check parse flag parseFlag := flags.Lookup("parse") assert.NotNil(t, parseFlag, "Should have 'parse' flag") - + // Check json flag jsonFlag := flags.Lookup("json") assert.NotNil(t, jsonFlag, "Should have 'json' flag") - + // Check repo flag repoFlag := flags.Lookup("repo") assert.NotNil(t, repoFlag, "Should have 'repo' flag") @@ -105,7 +105,7 @@ func TestLogsCommandBooleanFlags(t *testing.T) { flags := cmd.Flags() boolFlags := []string{"firewall", "no-firewall", "tool-graph", "parse", "json"} - + for _, flagName := range boolFlags { t.Run(flagName, func(t *testing.T) { flag := flags.Lookup(flagName) @@ -138,14 +138,14 @@ func TestLogsCommandStructure(t *testing.T) { func TestLogsCommandArgs(t *testing.T) { cmd := NewLogsCommand() - + // Logs command accepts 0 or 1 argument (workflow is optional) // Only test if Args validator is set if cmd.Args != nil { // Verify it accepts no arguments err := cmd.Args(cmd, []string{}) assert.NoError(t, err, "Should not error with no arguments") - + // Verify it accepts 1 argument err = cmd.Args(cmd, []string{"workflow1"}) assert.NoError(t, err, "Should not error with 1 argument") @@ -155,14 +155,14 @@ func TestLogsCommandArgs(t *testing.T) { func TestLogsCommandMutuallyExclusiveFlags(t *testing.T) { cmd := NewLogsCommand() flags := cmd.Flags() - + // firewall and no-firewall are mutually exclusive firewallFlag := flags.Lookup("firewall") noFirewallFlag := flags.Lookup("no-firewall") - + require.NotNil(t, firewallFlag, "firewall flag should exist") require.NotNil(t, noFirewallFlag, "no-firewall flag should exist") - + // Both flags exist and are boolean assert.Equal(t, "bool", firewallFlag.Value.Type(), "firewall should be boolean") assert.Equal(t, "bool", noFirewallFlag.Value.Type(), "no-firewall should be boolean") @@ -171,13 +171,13 @@ func TestLogsCommandMutuallyExclusiveFlags(t *testing.T) { func TestLogsCommandCountFlag(t *testing.T) { cmd := NewLogsCommand() flags := cmd.Flags() - + countFlag := flags.Lookup("count") require.NotNil(t, countFlag, "count flag should exist") - + // Count flag should be an integer assert.Equal(t, "int", countFlag.Value.Type(), "count should be integer type") - + // Count flag has shorthand assert.Equal(t, "c", countFlag.Shorthand, "count shorthand should be 'c'") } @@ -185,7 +185,7 @@ func TestLogsCommandCountFlag(t *testing.T) { func TestLogsCommandDateFlags(t *testing.T) { cmd := NewLogsCommand() flags := cmd.Flags() - + tests := []struct { flagName string flagType string @@ -193,7 +193,7 @@ func TestLogsCommandDateFlags(t *testing.T) { {"start-date", "string"}, {"end-date", "string"}, } - + for _, tt := range tests { t.Run(tt.flagName, func(t *testing.T) { flag := flags.Lookup(tt.flagName) @@ -206,14 +206,14 @@ func TestLogsCommandDateFlags(t *testing.T) { func TestLogsCommandRunIDFilters(t *testing.T) { cmd := NewLogsCommand() flags := cmd.Flags() - + tests := []struct { flagName string }{ {"after-run-id"}, {"before-run-id"}, } - + for _, tt := range tests { t.Run(tt.flagName, func(t *testing.T) { flag := flags.Lookup(tt.flagName) @@ -226,23 +226,23 @@ func TestLogsCommandRunIDFilters(t *testing.T) { func TestLogsCommandOutputFlag(t *testing.T) { cmd := NewLogsCommand() flags := cmd.Flags() - + outputFlag := flags.Lookup("output") require.NotNil(t, outputFlag, "output flag should exist") - + // Output flag should be a string assert.Equal(t, "string", outputFlag.Value.Type(), "output should be string type") - + // Output flag has shorthand assert.Equal(t, "o", outputFlag.Shorthand, "output shorthand should be 'o'") - + // Output flag has default value assert.Equal(t, ".github/aw/logs", outputFlag.DefValue, "output default should be '.github/aw/logs'") } func TestLogsCommandHelpText(t *testing.T) { cmd := NewLogsCommand() - + // Verify long description contains expected sections expectedSections := []string{ "Download workflow run logs", @@ -250,7 +250,7 @@ func TestLogsCommandHelpText(t *testing.T) { "Examples:", "gh aw logs", } - + for _, section := range expectedSections { assert.Contains(t, cmd.Long, section, "Long description should contain: %s", section) } diff --git a/pkg/cli/remove_command_test.go b/pkg/cli/remove_command_test.go index 48ca6429478..a19fa76c0e0 100644 --- a/pkg/cli/remove_command_test.go +++ b/pkg/cli/remove_command_test.go @@ -156,7 +156,7 @@ func TestRemoveWorkflows_PatternMatching(t *testing.T) { // Note: We can't fully test removal without mocking stdin for confirmation // This test just verifies the pattern matching logic doesn't error err = RemoveWorkflows(tt.pattern, false) - + // The function will either find matches or not, but shouldn't error assert.NoError(t, err, "RemoveWorkflows should not error for pattern: %s", tt.pattern) }) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 9c72e6d2d15..087adc62168 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -5,45 +5,30 @@ "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, "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", "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", "minLength": 8, "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", @@ -53,18 +38,9 @@ "minLength": 1 }, "examples": [ - [ - "automation", - "security" - ], - [ - "docs", - "maintenance" - ], - [ - "ci", - "testing" - ] + ["automation", "security"], + ["docs", "maintenance"], + ["ci", "testing"] ] }, "metadata": { @@ -98,9 +74,7 @@ { "type": "object", "description": "Import specification with path and optional inputs", - "required": [ - "path" - ], + "required": ["path"], "additionalProperties": false, "properties": { "path": { @@ -129,21 +103,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", @@ -159,17 +122,12 @@ "examples": [ { "issues": { - "types": [ - "opened" - ] + "types": ["opened"] } }, { "pull_request": { - "types": [ - "opened", - "synchronize" - ] + "types": ["opened", "synchronize"] } }, "workflow_dispatch", @@ -183,13 +141,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", @@ -224,16 +176,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", @@ -242,16 +185,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"] } } ] @@ -290,16 +224,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", @@ -308,16 +233,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"] } } ] @@ -381,37 +297,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"] } ] } @@ -421,37 +325,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"] } ] } @@ -568,37 +460,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"] } ] } @@ -608,37 +488,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"] } ] } @@ -657,26 +525,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": { @@ -712,11 +561,7 @@ "description": "Types of issue comment events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } }, "lock-for-agent": { @@ -735,21 +580,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"] } } } @@ -764,11 +595,7 @@ "description": "Types of discussion comment events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } } } @@ -793,9 +620,7 @@ "description": "Cron expression using standard format (e.g., '0 9 * * 1') or human-friendly format (e.g., 'daily at 02:00', 'daily at 3pm', 'daily at 6am', 'weekly on monday', 'weekly on friday at 5pm', 'every 10 minutes', 'every 2h', 'daily at 02:00 utc+9', 'daily at 3pm utc+9'). Human-friendly formats support: daily/weekly/monthly schedules with optional time, interval schedules (minimum 5 minutes), short duration units (m/h/d/w/mo), 12-hour time format (Npm/Nam where N is 1-12), and UTC timezone offsets (utc+N or utc+HH:MM)." } }, - "required": [ - "cron" - ], + "required": ["cron"], "additionalProperties": false } } @@ -834,11 +659,7 @@ }, "type": { "type": "string", - "enum": [ - "string", - "choice", - "boolean" - ], + "enum": ["string", "choice", "boolean"], "description": "Input type" }, "options": { @@ -872,11 +693,7 @@ "description": "Types of workflow run events", "items": { "type": "string", - "enum": [ - "completed", - "requested", - "in_progress" - ] + "enum": ["completed", "requested", "in_progress"] } }, "branches": { @@ -898,37 +715,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"] } ] } @@ -945,15 +750,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"] } } } @@ -968,11 +765,7 @@ "description": "Types of pull request review comment events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } } } @@ -987,11 +780,7 @@ "description": "Types of branch protection rule events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } } } @@ -1006,12 +795,7 @@ "description": "Types of check run events", "items": { "type": "string", - "enum": [ - "created", - "rerequested", - "completed", - "requested_action" - ] + "enum": ["created", "rerequested", "completed", "requested_action"] } } } @@ -1026,9 +810,7 @@ "description": "Types of check suite events", "items": { "type": "string", - "enum": [ - "completed" - ] + "enum": ["completed"] } } } @@ -1121,11 +903,7 @@ "description": "Types of label events", "items": { "type": "string", - "enum": [ - "created", - "edited", - "deleted" - ] + "enum": ["created", "edited", "deleted"] } } } @@ -1140,9 +918,7 @@ "description": "Types of merge group events", "items": { "type": "string", - "enum": [ - "checks_requested" - ] + "enum": ["checks_requested"] } } } @@ -1157,13 +933,7 @@ "description": "Types of milestone events", "items": { "type": "string", - "enum": [ - "created", - "closed", - "opened", - "edited", - "deleted" - ] + "enum": ["created", "closed", "opened", "edited", "deleted"] } } } @@ -1280,37 +1050,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"] } ] } @@ -1320,37 +1078,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"] } ] } @@ -1369,11 +1115,7 @@ "description": "Types of pull request review events", "items": { "type": "string", - "enum": [ - "submitted", - "edited", - "dismissed" - ] + "enum": ["submitted", "edited", "dismissed"] } } } @@ -1388,10 +1130,7 @@ "description": "Types of registry package events", "items": { "type": "string", - "enum": [ - "published", - "updated" - ] + "enum": ["published", "updated"] } } } @@ -1433,9 +1172,7 @@ "description": "Types of watch events", "items": { "type": "string", - "enum": [ - "started" - ] + "enum": ["started"] } } } @@ -1467,11 +1204,7 @@ }, "type": { "type": "string", - "enum": [ - "string", - "number", - "boolean" - ], + "enum": ["string", "number", "boolean"], "description": "Type of the input parameter" }, "default": { @@ -1513,9 +1246,7 @@ }, { "type": "object", - "required": [ - "query" - ], + "required": ["query"], "properties": { "query": { "type": "string", @@ -1541,9 +1272,7 @@ }, { "type": "object", - "required": [ - "query" - ], + "required": ["query"], "properties": { "query": { "type": "string", @@ -1569,37 +1298,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, @@ -1615,37 +1324,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"] } } ] @@ -1672,12 +1369,7 @@ "oneOf": [ { "type": "string", - "enum": [ - "read-all", - "write-all", - "read", - "write" - ], + "enum": ["read-all", "write-all", "read", "write"], "description": "Simple permissions string: 'read-all' (all read permissions), 'write-all' (all write permissions), 'read' or 'write' (basic level)" }, { @@ -1687,137 +1379,76 @@ "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"] }, "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"] }, "pages": { "type": "string", - "enum": [ - "read", - "write", - "none" - ] + "enum": ["read", "write", "none"] }, "pull-requests": { "type": "string", - "enum": [ - "read", - "write", - "none" - ] + "enum": ["read", "write", "none"] }, "security-events": { "type": "string", - "enum": [ - "read", - "write", - "none" - ] + "enum": ["read", "write", "none"] }, "statuses": { "type": "string", - "enum": [ - "read", - "write", - "none" - ] + "enum": ["read", "write", "none"] }, "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." } } @@ -1827,10 +1458,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", @@ -1872,14 +1500,10 @@ "additionalProperties": false, "oneOf": [ { - "required": [ - "uses" - ] + "required": ["uses"] }, { - "required": [ - "run" - ] + "required": ["run"] } ], "properties": { @@ -2089,35 +1713,22 @@ ], "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", "description": "Deprecated: Use 'timeout-minutes' instead. Workflow timeout in minutes. Defaults to 20 minutes for agentic workflows.", - "examples": [ - 5, - 10, - 30 - ], + "examples": [5, 10, 30], "deprecated": true }, "concurrency": { @@ -2126,10 +1737,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", @@ -2145,9 +1753,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 }}", @@ -2224,9 +1830,7 @@ "description": "A deployment URL" } }, - "required": [ - "name" - ], + "required": ["name"], "additionalProperties": false } ] @@ -2292,9 +1896,7 @@ "description": "Additional Docker container options" } }, - "required": [ - "image" - ], + "required": ["image"], "additionalProperties": false } ] @@ -2362,9 +1964,7 @@ "description": "Additional Docker container options" } }, - "required": [ - "image" - ], + "required": ["image"], "additionalProperties": false } ] @@ -2376,24 +1976,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" @@ -2403,9 +1992,7 @@ "oneOf": [ { "type": "string", - "enum": [ - "defaults" - ], + "enum": ["defaults"], "description": "Use default network permissions (basic infrastructure: certificates, JSON schema, Ubuntu, etc.)" }, { @@ -2436,9 +2023,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)" }, { @@ -2453,27 +2038,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"] } }, "additionalProperties": false @@ -2490,12 +2062,7 @@ "oneOf": [ { "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" }, { @@ -2504,12 +2071,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": { @@ -2517,17 +2079,12 @@ "oneOf": [ { "type": "boolean", - "enum": [ - false - ], + "enum": [false], "description": "Set to false to disable the agent firewall" }, { "type": "string", - "enum": [ - "awf", - "srt" - ], + "enum": ["awf", "srt"], "description": "Sandbox type: 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime" }, { @@ -2536,18 +2093,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": { @@ -2576,12 +2127,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", @@ -2695,15 +2241,9 @@ "description": "Container image for the MCP gateway executable" }, "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"] }, "args": { "type": "array", @@ -2745,42 +2285,30 @@ "additionalProperties": false, "anyOf": [ { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] }, {} ], "not": { "allOf": [ { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] } ] }, "allOf": [ { "if": { - "required": [ - "entrypointArgs" - ] + "required": ["entrypointArgs"] }, "then": { - "required": [ - "container" - ] + "required": ["container"] } } ] @@ -2803,10 +2331,7 @@ "type": "srt", "config": { "filesystem": { - "allowWrite": [ - ".", - "/tmp" - ] + "allowWrite": [".", "/tmp"] } } } @@ -2830,10 +2355,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", @@ -2951,10 +2473,7 @@ "filesystem": { "type": "stdio", "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-filesystem" - ] + "args": ["-y", "@modelcontextprotocol/server-filesystem"] } }, { @@ -3031,24 +2550,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", @@ -3108,30 +2616,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"] } ] } @@ -3139,25 +2633,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 ] @@ -3184,36 +2667,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": { @@ -3270,16 +2727,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.", @@ -3321,10 +2771,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", @@ -3400,10 +2847,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, @@ -3447,11 +2891,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", @@ -3470,14 +2910,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"] } }, { @@ -3485,16 +2918,9 @@ "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] }, "args": { "type": "array", @@ -3517,10 +2943,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Go version (e.g., \"1.21\", 1.21)" }, "go-mod-file": { @@ -3546,10 +2969,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Node.js version for TypeScript (e.g., \"22\", 22)" } }, @@ -3567,10 +2987,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Python version (e.g., \"3.12\", 3.12)" } }, @@ -3588,10 +3005,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Java version (e.g., \"21\", 21)" } }, @@ -3609,10 +3023,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Rust version (e.g., \"stable\", \"1.75\")" } }, @@ -3630,10 +3041,7 @@ "type": "object", "properties": { "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": ".NET version for C# (e.g., \"8.0\", 8.0)" } }, @@ -3851,29 +3259,16 @@ }, "mode": { "type": "string", - "enum": [ - "stdio", - "http", - "remote", - "local" - ], + "enum": ["stdio", "http", "remote", "local"], "description": "MCP server mode" }, "type": { "type": "string", - "enum": [ - "stdio", - "http", - "remote", - "local" - ], + "enum": ["stdio", "http", "remote", "local"], "description": "MCP server type" }, "version": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "description": "Version of the MCP server" }, "toolsets": { @@ -3971,25 +3366,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 } @@ -4048,10 +3435,7 @@ "description": "If true, only checks if cache entry exists and skips download" } }, - "required": [ - "key", - "path" - ], + "required": ["key", "path"], "additionalProperties": false } } @@ -4065,18 +3449,13 @@ { "create-issue": { "title-prefix": "[AI] ", - "labels": [ - "automation", - "ai-generated" - ] + "labels": ["automation", "ai-generated"] } }, { "create-pull-request": { "title-prefix": "[Bot] ", - "labels": [ - "bot" - ] + "labels": ["bot"] } }, { @@ -4168,25 +3547,16 @@ "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] " } ] @@ -4275,16 +3645,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", @@ -4357,17 +3720,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" } ] @@ -4420,10 +3778,7 @@ "required-category": "Ideas" }, { - "required-labels": [ - "resolved", - "completed" - ], + "required-labels": ["resolved", "completed"], "max": 1 } ] @@ -4520,10 +3875,7 @@ "required-title-prefix": "[refactor] " }, { - "required-labels": [ - "automated", - "stale" - ], + "required-labels": ["automated", "stale"], "max": 10 } ] @@ -4576,10 +3928,7 @@ "required-title-prefix": "[bot] " }, { - "required-labels": [ - "automated", - "outdated" - ], + "required-labels": ["automated", "outdated"], "max": 5 } ] @@ -4624,13 +3973,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"] } } }, @@ -4697,11 +4040,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": { @@ -4736,19 +4075,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" } ] @@ -4774,10 +4107,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", @@ -4999,10 +4329,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": { @@ -5184,11 +4511,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": { @@ -5229,13 +4552,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"] } } }, @@ -5377,10 +4694,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", @@ -5396,11 +4710,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", @@ -5409,25 +4719,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", @@ -5435,21 +4737,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": { @@ -5596,11 +4887,7 @@ }, "type": { "type": "string", - "enum": [ - "string", - "boolean", - "choice" - ], + "enum": ["string", "boolean", "choice"], "description": "Input parameter type", "default": "string" }, @@ -5637,65 +4924,42 @@ "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})"] }, "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})."] } }, "additionalProperties": false @@ -5774,9 +5038,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)" }, { @@ -5784,13 +5046,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 @@ -5811,10 +5067,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 wildcard '*'. (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 wildcard '*' in allowed domains, (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://githubnext.github.io/gh-aw/reference/frontmatter/#strict-mode-strict", - "examples": [ - true, - false - ] + "examples": [true, false] }, "safe-inputs": { "type": "object", @@ -5823,9 +5076,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", @@ -5839,13 +5090,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." }, @@ -5895,69 +5140,46 @@ "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": [ - "run" - ], + "required": ["run"], "not": { "anyOf": [ { - "required": [ - "script" - ] + "required": ["script"] }, { - "required": [ - "py" - ] + "required": ["py"] } ] } }, { - "required": [ - "py" - ], + "required": ["py"], "not": { "anyOf": [ { - "required": [ - "script" - ] + "required": ["script"] }, { - "required": [ - "run" - ] + "required": ["run"] } ] } @@ -6015,18 +5237,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", @@ -6063,9 +5276,7 @@ } } }, - "required": [ - "slash_command" - ] + "required": ["slash_command"] }, { "properties": { @@ -6075,9 +5286,7 @@ } } }, - "required": [ - "command" - ] + "required": ["command"] } ] } @@ -6096,9 +5305,7 @@ } } }, - "required": [ - "issue_comment" - ] + "required": ["issue_comment"] }, { "properties": { @@ -6108,9 +5315,7 @@ } } }, - "required": [ - "pull_request_review_comment" - ] + "required": ["pull_request_review_comment"] }, { "properties": { @@ -6120,9 +5325,7 @@ } } }, - "required": [ - "label" - ] + "required": ["label"] } ] } @@ -6156,12 +5359,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)" }, { @@ -6170,26 +5368,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", @@ -6227,9 +5412,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 } ], @@ -6284,9 +5467,7 @@ "description": "Human-readable description of what this pattern matches" } }, - "required": [ - "pattern" - ], + "required": ["pattern"], "additionalProperties": false } }, @@ -6302,9 +5483,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 } ] @@ -6315,10 +5494,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "stdio", - "local" - ], + "enum": ["stdio", "local"], "description": "MCP connection type for stdio (local is an alias for stdio)" }, "registry": { @@ -6338,17 +5514,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", @@ -6412,70 +5580,49 @@ "$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) Dependency: 'network' requires 'container' (validated in 'allOf'). (4) Type constraint: When 'type' is 'stdio' or 'local', either 'command' or 'container' is required.", "anyOf": [ { - "required": [ - "type" - ] + "required": ["type"] }, { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] } ], "not": { "allOf": [ { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] } ] }, "allOf": [ { "if": { - "required": [ - "network" - ] + "required": ["network"] }, "then": { - "required": [ - "container" - ] + "required": ["container"] } }, { "if": { "properties": { "type": { - "enum": [ - "stdio", - "local" - ] + "enum": ["stdio", "local"] } } }, "then": { "anyOf": [ { - "required": [ - "command" - ] + "required": ["command"] }, { - "required": [ - "container" - ] + "required": ["container"] } ] } @@ -6488,9 +5635,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "http" - ], + "enum": ["http"], "description": "MCP connection type for HTTP" }, "registry": { @@ -6520,20 +5665,14 @@ } } }, - "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", @@ -6594,16 +5733,12 @@ "additionalProperties": false, "anyOf": [ { - "required": [ - "uses" - ] + "required": ["uses"] }, { - "required": [ - "run" - ] + "required": ["run"] } ] } } -} \ No newline at end of file +} diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 13d93ac5ac5..e0eb875943e 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -401,7 +401,7 @@ func (c *Compiler) CompileWorkflowData(workflowData *WorkflowData, markdownPath log.Print("Validation completed - no lock file generated (--no-emit enabled)") } else { log.Printf("Writing output to: %s", lockFile) - + // Check if we need to force write to update timestamp shouldForceWrite := false if existingLockInfo, err := os.Stat(lockFile); err == nil { @@ -419,7 +419,7 @@ func (c *Compiler) CompileWorkflowData(workflowData *WorkflowData, markdownPath } } } - + if err := os.WriteFile(lockFile, []byte(yamlContent), 0644); err != nil { formattedErr := console.FormatError(console.CompilerError{ Position: console.ErrorPosition{ @@ -432,7 +432,7 @@ func (c *Compiler) CompileWorkflowData(workflowData *WorkflowData, markdownPath }) return errors.New(formattedErr) } - + if shouldForceWrite { log.Print("Updated lock file timestamp to match content generation") } diff --git a/pkg/workflow/gateway.go b/pkg/workflow/gateway.go index f42f2729267..b95b2d0e89d 100644 --- a/pkg/workflow/gateway.go +++ b/pkg/workflow/gateway.go @@ -44,7 +44,7 @@ func getMCPGatewayConfig(workflowData *WorkflowData) *MCPGatewayRuntimeConfig { } // generateMCPGatewaySteps generates the steps to start and verify the MCP gateway -func generateMCPGatewaySteps(workflowData *WorkflowData, mcpServersConfig map[string]any) []GitHubActionStep { +func generateMCPGatewaySteps(workflowData *WorkflowData, mcpServersConfig map[string]any, gatewayedServers []string) []GitHubActionStep { if !isMCPGatewayEnabled(workflowData) { return nil } @@ -54,8 +54,8 @@ func generateMCPGatewaySteps(workflowData *WorkflowData, mcpServersConfig map[st return nil } - gatewayLog.Printf("Generating MCP gateway steps: port=%d, container=%s, command=%s, servers=%d", - config.Port, config.Container, config.Command, len(mcpServersConfig)) + gatewayLog.Printf("Generating MCP gateway steps: port=%d, container=%s, command=%s, servers=%d, gatewayed=%v", + config.Port, config.Container, config.Command, len(mcpServersConfig), gatewayedServers) var steps []GitHubActionStep @@ -64,7 +64,7 @@ func generateMCPGatewaySteps(workflowData *WorkflowData, mcpServersConfig map[st steps = append(steps, startStep) // Step 2: Health check to verify gateway is running - healthCheckStep := generateMCPGatewayHealthCheckStep(config) + healthCheckStep := generateMCPGatewayHealthCheckStep(config, gatewayedServers) steps = append(steps, healthCheckStep) return steps @@ -312,8 +312,8 @@ func generateDefaultAWMGCommands(config *MCPGatewayRuntimeConfig, mcpConfigPath } // generateMCPGatewayHealthCheckStep generates the step that pings the gateway to verify it's running -func generateMCPGatewayHealthCheckStep(config *MCPGatewayRuntimeConfig) GitHubActionStep { - gatewayLog.Print("Generating MCP gateway health check step") +func generateMCPGatewayHealthCheckStep(config *MCPGatewayRuntimeConfig, gatewayedServers []string) GitHubActionStep { + gatewayLog.Printf("Generating MCP gateway health check step with %d gatewayed servers", len(gatewayedServers)) port, err := validateAndNormalizePort(config.Port) if err != nil { @@ -349,6 +349,34 @@ func generateMCPGatewayHealthCheckStep(config *MCPGatewayRuntimeConfig) GitHubAc " fi", " echo 'Verified: safeinputs and safeoutputs are present in configuration'", " ", + } + + // Add validation for gatewayed servers (external MCP servers proxied through gateway) + if len(gatewayedServers) > 0 { + stepLines = append(stepLines, + " # Verify gatewayed servers (external MCP servers proxied through gateway)", + " echo 'Validating gatewayed servers...'", + " ", + ) + for _, serverName := range gatewayedServers { + // Skip internal servers (safeinputs/safeoutputs) as they're not proxied + if serverName == "safe-inputs" || serverName == "safe-outputs" { + continue + } + gatewayLog.Printf("Adding validation for gatewayed server: %s", serverName) + stepLines = append(stepLines, + fmt.Sprintf(" # Validate %s server", serverName), + fmt.Sprintf(" /tmp/gh-aw/actions/validate_gatewayed_server.sh \"%s\" \"%s\" \"%s\"", serverName, mcpConfigPath, gatewayURL), + " ", + ) + } + stepLines = append(stepLines, + " echo 'All gatewayed servers validated successfully'", + " ", + ) + } + + stepLines = append(stepLines, " max_retries=30", " retry_count=0", fmt.Sprintf(" gateway_url=\"%s\"", gatewayURL), @@ -400,7 +428,7 @@ func generateMCPGatewayHealthCheckStep(config *MCPGatewayRuntimeConfig) GitHubAc " echo 'Gateway logs:'", fmt.Sprintf(" cat %s/gateway.log || echo 'No gateway logs found'", MCPGatewayLogsFolder), " exit 1", - } + ) return GitHubActionStep(stepLines) } diff --git a/pkg/workflow/gateway_test.go b/pkg/workflow/gateway_test.go index 64e02e85a23..133ec057a09 100644 --- a/pkg/workflow/gateway_test.go +++ b/pkg/workflow/gateway_test.go @@ -195,16 +195,18 @@ func TestGetMCPGatewayConfig(t *testing.T) { func TestGenerateMCPGatewaySteps(t *testing.T) { tests := []struct { - name string - data *WorkflowData - mcpServers map[string]any - expectSteps int + name string + data *WorkflowData + mcpServers map[string]any + gatewayedServers []string + expectSteps int }{ { - name: "gateway disabled returns no steps", - data: &WorkflowData{}, - mcpServers: map[string]any{}, - expectSteps: 0, + name: "gateway disabled returns no steps", + data: &WorkflowData{}, + mcpServers: map[string]any{}, + gatewayedServers: []string{}, + expectSteps: 0, }, { name: "gateway enabled returns two steps", @@ -221,13 +223,14 @@ func TestGenerateMCPGatewaySteps(t *testing.T) { mcpServers: map[string]any{ "github": map[string]any{}, }, - expectSteps: 2, + gatewayedServers: []string{"github"}, + expectSteps: 2, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - steps := generateMCPGatewaySteps(tt.data, tt.mcpServers) + steps := generateMCPGatewaySteps(tt.data, tt.mcpServers, tt.gatewayedServers) assert.Len(t, steps, tt.expectSteps) }) } @@ -257,7 +260,7 @@ func TestGenerateMCPGatewayHealthCheckStep(t *testing.T) { Port: 8080, } - step := generateMCPGatewayHealthCheckStep(config) + step := generateMCPGatewayHealthCheckStep(config, []string{"github", "playwright"}) stepStr := strings.Join(step, "\n") assert.Contains(t, stepStr, "Verify MCP Gateway Health") @@ -281,6 +284,68 @@ func TestGenerateMCPGatewayHealthCheckStep(t *testing.T) { assert.Contains(t, stepStr, "✓ MCP server connectivity test passed") } +func TestGenerateMCPGatewayHealthCheckStep_ValidatesGatewayedServers(t *testing.T) { + config := &MCPGatewayRuntimeConfig{ + Port: 8080, + } + + // Test with multiple gatewayed servers + gatewayedServers := []string{"github", "playwright", "serena"} + step := generateMCPGatewayHealthCheckStep(config, gatewayedServers) + stepStr := strings.Join(step, "\n") + + // Should include gateway validation section + assert.Contains(t, stepStr, "Validating gatewayed servers...") + + // Should validate each gatewayed server using the shell script + for _, serverName := range gatewayedServers { + // Verify the script is called with correct arguments + assert.Contains(t, stepStr, fmt.Sprintf("# Validate %s server", serverName)) + assert.Contains(t, stepStr, fmt.Sprintf("/tmp/gh-aw/actions/validate_gatewayed_server.sh \"%s\"", serverName)) + assert.Contains(t, stepStr, "http://localhost:8080") + } + + // Should have completion message + assert.Contains(t, stepStr, "All gatewayed servers validated successfully") +} + +func TestGenerateMCPGatewayHealthCheckStep_NoGatewayedServers(t *testing.T) { + config := &MCPGatewayRuntimeConfig{ + Port: 8080, + } + + // Test with no gatewayed servers (only internal servers) + step := generateMCPGatewayHealthCheckStep(config, []string{}) + stepStr := strings.Join(step, "\n") + + // Should NOT include gateway validation section + assert.NotContains(t, stepStr, "Validating gatewayed servers...") + assert.NotContains(t, stepStr, "All gatewayed servers validated successfully") + + // Should still have basic health check + assert.Contains(t, stepStr, "Verify MCP Gateway Health") + assert.Contains(t, stepStr, "Waiting for MCP Gateway to be ready...") +} + +func TestGenerateMCPGatewayHealthCheckStep_SkipsInternalServers(t *testing.T) { + config := &MCPGatewayRuntimeConfig{ + Port: 8080, + } + + // Test with internal servers that should be skipped + gatewayedServers := []string{"safe-inputs", "safe-outputs", "github"} + step := generateMCPGatewayHealthCheckStep(config, gatewayedServers) + stepStr := strings.Join(step, "\n") + + // Should NOT validate safe-inputs or safe-outputs as gatewayed + assert.NotContains(t, stepStr, "# Validate safe-inputs server") + assert.NotContains(t, stepStr, "# Validate safe-outputs server") + + // Should validate github as gatewayed + assert.Contains(t, stepStr, "# Validate github server") + assert.Contains(t, stepStr, "/tmp/gh-aw/actions/validate_gatewayed_server.sh \"github\"") +} + func TestGetMCPGatewayURL(t *testing.T) { tests := []struct { name string @@ -680,7 +745,7 @@ func TestGenerateMCPGatewayHealthCheckStepWithInvalidPort(t *testing.T) { Port: tt.port, } - step := generateMCPGatewayHealthCheckStep(config) + step := generateMCPGatewayHealthCheckStep(config, []string{}) stepStr := strings.Join(step, "\n") // Should still generate valid step with default port diff --git a/pkg/workflow/mcp_renderer.go b/pkg/workflow/mcp_renderer.go index 1f2fa55e49e..d0cb43186e0 100644 --- a/pkg/workflow/mcp_renderer.go +++ b/pkg/workflow/mcp_renderer.go @@ -239,7 +239,7 @@ func (r *MCPConfigRendererUnified) renderSafeInputsTOML(yaml *strings.Builder, s yaml.WriteString(" \n") yaml.WriteString(" [mcp_servers." + constants.SafeInputsMCPServerID + "]\n") yaml.WriteString(" type = \"http\"\n") - + // Determine host based on whether agent is disabled host := "host.docker.internal" if workflowData != nil && workflowData.SandboxConfig != nil && workflowData.SandboxConfig.Agent != nil && workflowData.SandboxConfig.Agent.Disabled { @@ -249,7 +249,7 @@ func (r *MCPConfigRendererUnified) renderSafeInputsTOML(yaml *strings.Builder, s } else { mcpRendererLog.Print("Using host.docker.internal for safe-inputs (agent enabled)") } - + yaml.WriteString(" url = \"http://" + host + ":$GH_AW_SAFE_INPUTS_PORT\"\n") yaml.WriteString(" headers = { Authorization = \"Bearer $GH_AW_SAFE_INPUTS_API_KEY\" }\n") // Note: env_vars is not supported for HTTP transport in MCP configuration diff --git a/pkg/workflow/mcp_servers.go b/pkg/workflow/mcp_servers.go index 974e19a8a7e..a23eea362e6 100644 --- a/pkg/workflow/mcp_servers.go +++ b/pkg/workflow/mcp_servers.go @@ -442,8 +442,15 @@ func (c *Compiler) generateMCPSetup(yaml *strings.Builder, tools map[string]any, engine.RenderMCPConfig(yaml, tools, mcpTools, workflowData) // Generate MCP gateway steps if configured (after Setup MCPs completes) - // Note: Currently passing nil for mcpServersConfig as the gateway is configured via sandbox.mcp - gatewaySteps := generateMCPGatewaySteps(workflowData, nil) + // Filter out internal servers (safe-inputs, safe-outputs) from gatewayed servers + // as they are not proxied through the gateway + gatewayedServers := make([]string, 0, len(mcpTools)) + for _, toolName := range mcpTools { + if toolName != "safe-inputs" && toolName != "safe-outputs" { + gatewayedServers = append(gatewayedServers, toolName) + } + } + gatewaySteps := generateMCPGatewaySteps(workflowData, nil, gatewayedServers) for _, step := range gatewaySteps { for _, line := range step { yaml.WriteString(line + "\n")