diff --git a/docs/public/editor/autocomplete-data.json b/docs/public/editor/autocomplete-data.json index c1b1597ca35..f5776fc686a 100644 --- a/docs/public/editor/autocomplete-data.json +++ b/docs/public/editor/autocomplete-data.json @@ -1944,9 +1944,21 @@ "leaf": true }, "runs-on": { - "type": "string", + "type": "string|array|object", "desc": "Runner specification for all safe-outputs jobs (activation, create-issue, add-comment, etc.).", - "leaf": true + "children": { + "group": { + "type": "string", + "desc": "Runner group name for self-hosted runners or GitHub-hosted runner groups", + "leaf": true + }, + "labels": { + "type": "array", + "desc": "List of runner labels for self-hosted runners or GitHub-hosted runner selection", + "array": true + } + }, + "array": true }, "steps": { "type": "array", diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 789f70450e0..7bff2c00ed5 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -946,24 +946,21 @@ on: skip-author-associations: {} - # Exact-match allowlist of repository roles required to trigger agentic workflows. - # Each entry is matched literally — there is no privilege hierarchy. Defaults to + # Repository access roles required to trigger agentic workflows. Defaults to # ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any # authenticated user (⚠️ security consideration). - # ⚠️ roles: [write] rejects actors with 'admin' or 'maintainer' roles because - # matching is exact, not a minimum threshold. List every role you want to accept. # (optional) # Accepted formats: - # Format 1: Single repository role that can trigger the workflow. Use + # Format 1: Single repository permission level that can trigger the workflow. Use # 'all' to allow any authenticated user (⚠️ disables permission checking entirely # - use with caution) roles: "admin" - # Format 2: Exact-match allowlist of repository roles that can trigger the workflow. + # Format 2: List of repository permission levels that can trigger the workflow. # Permission checks are automatically applied to potentially unsafe triggers. roles: [] - # Array items: Repository role (exact match): 'admin' (full access), + # Array items: Repository permission level: 'admin' (full access), # 'maintainer'/'maintain' (repository management), 'write' (push access), 'triage' # (issue management), 'read' (read-only access) @@ -1411,6 +1408,11 @@ permissions: # (optional) checks: "read" + # Permission level for Copilot requests (write/none only). Set to write to allow + # Copilot inference via the GitHub Actions token. + # (optional) + copilot-requests: "write" + # Permission for repository contents (read: view files, write: modify # files/branches, none: no access) # (optional) @@ -1542,13 +1544,39 @@ runs-on: # Runner for all framework/generated jobs (activation, pre-activation, # safe-outputs, unlock, APM, etc.). Provides a compile-stable override for -# generated job runners without requiring a safe-outputs section. Overridden by +# generated job runners without requiring a safe-outputs section. Supports the +# same string, array, and runner-group object forms as runs-on. Overridden by # safe-outputs.runs-on when both are set. Defaults to 'ubuntu-slim'. Use this when # your infrastructure does not provide the default runner or when you need # consistent runner selection across all jobs. # (optional) +# Accepted formats: + +# Format 1: Simple runner label string. Use for standard GitHub-hosted runners +# (e.g., 'ubuntu-latest', 'windows-latest', 'macos-latest') or self-hosted runner +# labels. Most common form for agentic workflows. runs-on-slim: "example-value" +# Format 2: Array of runner labels for selection with fallbacks. GitHub Actions +# will use the first available runner that matches any label in the array. Useful +# for high-availability setups or when multiple runner types are acceptable. +runs-on-slim: [] + # Array items: string + +# Format 3: Runner group configuration for GitHub-hosted runners. Use this form to +# target specific runner groups (e.g., larger runners with more CPU/memory) or +# self-hosted runner pools with specific label requirements. Agentic workflows may +# benefit from larger runners for complex AI processing tasks. +runs-on-slim: + # Runner group name for self-hosted runners or GitHub-hosted runner groups + # (optional) + group: "example-value" + + # List of runner labels for self-hosted runners or GitHub-hosted runner selection + # (optional) + labels: [] + # Array of strings + # Workflow timeout in minutes (GitHub Actions standard field). Defaults to 20 # minutes for agentic workflows. Has sensible defaults and can typically be # omitted. Custom runners support longer timeouts beyond the GitHub-hosted runner @@ -1560,8 +1588,7 @@ runs-on-slim: "example-value" # Format 1: integer timeout-minutes: 1 -# Format 2: GitHub Actions expression that resolves to an integer (e.g. '${{ -# inputs.timeout }}') +# Format 2: GitHub Actions expression that resolves to an integer at runtime timeout-minutes: "example-value" # Concurrency control to limit concurrent workflow runs (GitHub Actions standard @@ -1636,17 +1663,15 @@ env: "example-value" features: {} -# Named model alias definitions with ordered fallback lists, resolved recursively -# by AWF. Each key is an alias name (use empty string "" for the default policy). -# Each value is an ordered list of vendor/modelid glob patterns or other alias -# names to try in sequence. Entries defined here are merged on top of the builtin -# aliases; the main workflow file always wins over imported aliases. Builtin -# aliases include: sonnet, sonnet-6x, haiku, opus, gpt-5, gpt-5-mini, gpt-5-codex, -# gemini-flash, gemini-pro, small, mini, large, auto, any, agent, copilot, claude, -# codex, gemini. +# Custom model pricing data in the same structure as models.json. Merged with the +# built-in models.json at runtime; frontmatter entries override matching models +# and fill gaps for unknown models. Useful for custom or private models, or to +# adjust pricing for AI Credits cost accounting. # (optional) models: - {} + # Provider-keyed map of model pricing data. + providers: + {} # A/B testing experiments. Each key is an experiment name; the value is either an # array of two or more variant strings (bare-array form) or an object with a @@ -1667,17 +1692,12 @@ experiments: # Storage backend for experiment state. 'repo' (default) persists state to a git # branch named 'experiments/{sanitizedWorkflowID}' (workflow ID lowercased with # hyphens removed, e.g. 'my-workflow' -> 'experiments/myworkflow') for durability - # across cache evictions. 'cache' uses GitHub Actions cache (legacy behavior). + # across cache evictions. 'cache' uses GitHub Actions cache (legacy behaviour). # Repo storage is recommended because experiment data is valuable and more durable # than cache. # (optional) storage: "cache" -# Controls whether the custom agent should disable model invocation. When set to -# true, the agent will not make additional model calls. -# (optional) -disable-model-invocation: true - # Secret values passed to workflow execution. Secrets can be defined as simple # strings (GitHub Actions expressions) or objects with 'value' and 'description' # properties. Typically used to provide secrets to MCP servers or custom engines. @@ -2092,18 +2112,6 @@ engine: # (optional) permission-mode: "auto" - # Maximum number of chat iterations per run. Helps prevent runaway loops and - # control costs. Has sensible defaults and can typically be omitted. Note: Only - # supported by the claude engine. - # (optional) - # Accepted formats: - - # Format 1: Maximum number of chat iterations per run as an integer value - max-turns: 1 - - # Format 2: Maximum number of chat iterations per run as a string value - max-turns: "example-value" - # Maximum number of continuations for multi-run autopilot mode. Default is 1 # (single run, no autopilot). Values greater than 1 enable --autopilot mode for # the copilot engine with --max-autopilot-continues set to this value. Note: Only @@ -2229,9 +2237,9 @@ engine: # (optional) api-target: "example-value" - # Custom model token weights for legacy Effective Tokens (ET) computation. Overrides or extends - # the built-in model multipliers from model_multipliers.json. Useful for custom - # models or adjusted cost ratios. + # Custom model token weights for AI Credits cost ratio adjustment. Overrides or + # extends built-in model cost defaults. Useful for custom models or adjusted cost + # ratios. # (optional) token-weights: # Per-model cost multipliers relative to the reference model (claude-sonnet-4.5 = @@ -2306,9 +2314,11 @@ engine: # (optional) copilot-sdk: true - # Custom Node.js Copilot SDK driver script filename (copilot engine only). This is - # only used when copilot-sdk: true is set and must be a safe basename ending with - # .js, .cjs, or .mjs. + # Custom Copilot SDK driver script or command (copilot engine only). Setting this + # field implies `copilot-sdk: true`. Accepts a relative path from the workspace + # root with a supported language extension (.js, .cjs, .mjs, .py, .ts, .mts, .rb), + # e.g. `.github/drivers/my_driver.py`, or a bare command name without an extension + # for an arbitrary executable in PATH. # (optional) copilot-sdk-driver: "example-value" @@ -2532,42 +2542,57 @@ engine: # the default engine when engine.id is not specified. model: "example-value" -# Explicit AI Credits budget control for firewall cost enforcement. Omit this -# field to leave AIC budget protection unset. Set to -1 to disable budget -# enforcement. +# AWF turn cap (`max_turns`) applied consistently across all agentic engines. +# Supports GitHub Actions expressions (e.g. '${{ inputs.max-turns }}') for +# reusable workflow_call workflows. # (optional) # Accepted formats: -# Format 1: Maximum AI Credits (AIC) budget for AWF API proxy enforcement. -max-ai-credits: 1 +# Format 1: integer +max-turns: 1 -# Format 2: Maximum AI Credits (AIC) budget as a numeric string with optional -# K/M suffix or GitHub Actions expression. -max-ai-credits: "example-value" +# Format 2: GitHub Actions expression that resolves to an integer at runtime +max-turns: "example-value" -# Explicit daily AI Credits guardrail per workflow across the previous 24-hour -# window. Omit to disable, or set to -1 to explicitly disable. +# Copilot SDK safeguard threshold for repeated tool denials before stopping +# inference. Defaults to 5 when omitted. Supports GitHub Actions expressions (for +# example, '${{ inputs.max-tool-denials }}'). Supported only with engine 'copilot' +# and engine.copilot-sdk: true. # (optional) # Accepted formats: -# Format 1: Maximum daily AI Credits (AIC) budget. -max-daily-ai-credits: 1 +# Format 1: integer +max-tool-denials: 1 -# Format 2: Maximum daily AI Credits (AIC) budget as a numeric string with -# optional K/M suffix or GitHub Actions expression. -max-daily-ai-credits: "example-value" +# Format 2: GitHub Actions expression that resolves to an integer at runtime +max-tool-denials: "example-value" -# DEPRECATED: Legacy alias for AWF invocation cap (`apiProxy.maxRuns`). -# Use `max-turns` instead. Defaults to 500 when omitted. +# Per-run AI Credits budget control for firewall cost enforcement. Enabled by +# default at 1000 (1k) when omitted. Set to -1 to disable both budget enforcement +# and token steering. Supports GitHub Actions expressions. # (optional) # Accepted formats: -# Format 1: Maximum number of LLM invocations allowed per run. -max-turns: 1 +# Format 1: Configuration object -# Format 2: Maximum number of LLM invocations allowed per run as a numeric string -# or GitHub Actions expression. -max-turns: "example-value" +# Format 2: integer +max-ai-credits: 1 + +# Format 3: string +max-ai-credits: "example-value" + +# 24-hour AI Credits guardrail for runs triggered by the same user. Omit the field +# to leave the guardrail disabled. Supports GitHub Actions expressions. +# (optional) +# Accepted formats: + +# Format 1: Configuration object + +# Format 2: integer +max-daily-ai-credits: 1 + +# Format 3: string +max-daily-ai-credits: "example-value" # MCP server definitions # (optional) @@ -3100,10 +3125,9 @@ tools: # (optional) restore-only: true - # Cache restore key scope: 'workflow' (default, restores from same workflow key - # lineage) or 'repo' (adds repository-wide restore keys). Scope broadens - # cross-workflow matching but cache visibility remains branch-scoped (with - # GitHub's default-branch restore fallback behavior). + # Cache restore key scope: 'workflow' (default, only restores from same workflow) + # or 'repo' (restores from any workflow in the repository). Use 'repo' with + # caution as it allows cross-workflow cache sharing. # (optional) scope: "workflow" @@ -3170,6 +3194,24 @@ tools: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable (true) or disable (false) comment-memory. comment-memory: true @@ -3509,6 +3551,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable issue creation with default configuration create-issue: null @@ -3560,6 +3620,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable agent session creation with default configuration create-agent-task: null @@ -3611,6 +3689,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable agent session creation with default configuration create-agent-session: null @@ -3723,6 +3819,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable project management with default configuration (max=10) update-project: null @@ -3817,6 +3931,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable project creation with default configuration (max=1) create-project: null @@ -3862,6 +3994,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable project status updates with default configuration (max=1) create-project-status-update: null @@ -3988,6 +4138,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable discussion creation with default configuration create-discussion: null @@ -4033,11 +4201,35 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that discussions can be + # closed in. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # If true, emit step summary messages instead of making GitHub API calls for this # specific output type (preview mode) # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable discussion closing with default configuration close-discussion: null @@ -4089,6 +4281,12 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that discussions can be + # updated in. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # Controls whether AI-generated footer is added when updating the discussion body. # When false, the visible footer content is omitted. Defaults to true. Only # applies when 'body' is enabled. @@ -4100,6 +4298,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -4159,6 +4375,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Reason for closing the issue (default: completed) # (optional) state-reason: "completed" @@ -4204,6 +4438,12 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that pull requests can be + # closed in. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -4214,6 +4454,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable pull request closing with default configuration close-pull-request: null @@ -4255,6 +4513,12 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that pull requests can be + # marked as ready in. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -4265,6 +4529,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable marking pull requests as ready for review with default # configuration mark-pull-request-as-ready-for-review: null @@ -4318,16 +4600,33 @@ safe-outputs: # When true, minimizes/hides all previous comments from the same agentic workflow # (identified by tracker-id) before creating the new comment. Supports literal - # boolean or GitHub Actions expression (e.g. '${{ inputs.hide-older-comments }}'). - # Default: false. + # boolean, GitHub Actions expression (e.g. '${{ inputs.hide-older-comments }}'), + # or object form for advanced matching. Default: false. # (optional) # Accepted formats: - # Format 1: boolean - hide-older-comments: true + # Format 1: A boolean value that may also be specified as a GitHub Actions + # expression string that resolves to a boolean at runtime (e.g. '${{ + # inputs.my-flag }}'). - # Format 2: GitHub Actions expression that resolves to a boolean at runtime - hide-older-comments: "example-value" + # Format 2: object + hide-older-comments: + # Enable or disable hide-older-comments when using object form. Defaults to true + # when omitted. + # (optional) + # Accepted formats: + + # Format 1: boolean + enabled: true + + # Format 2: GitHub Actions expression that resolves to a boolean at runtime + enabled: "example-value" + + # Additional workflow-id values to fully match when selecting older comments to + # hide. + # (optional) + match: [] + # Array of strings # List of allowed reasons for hiding older comments when hide-older-comments is # enabled. Default: all reasons allowed (spam, abuse, off_topic, outdated, @@ -4382,6 +4681,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable issue comment creation with default configuration add-comment: null @@ -4507,7 +4824,9 @@ safe-outputs: allow-empty: true # Target repository in format 'owner/repo' for cross-repository pull request - # creation. Takes precedence over trial target repo settings. + # creation, or '*' to let the agent choose the target repository at runtime + # (requires checkout: configs with path: for each possible target). Takes + # precedence over trial target repo settings. # (optional) target-repo: "example-value" @@ -4624,6 +4943,20 @@ safe-outputs: # (optional) auto-close-issue: null + # When true, automatically close older open pull requests with the same + # workflow-id marker (or close-older-key when set) with a comment linking to the + # new PR. Maximum 10 PRs will be closed. Only runs if PR creation succeeds. + # (optional) + close-older-pull-requests: null + + # Optional explicit deduplication key for close-older-pull-requests matching. When + # set, a `` marker is embedded in the PR body and + # used as the primary key for searching and filtering older PRs instead of the + # workflow-id markers. The value is normalized to identifier style (lowercase + # alphanumeric, dashes, underscores). + # (optional) + close-older-key: "example-value" + # Token used to push an empty commit after PR creation to trigger CI events. Works # around the GITHUB_TOKEN limitation where pushes don't trigger workflow runs. # Defaults to the magic secret GH_AW_CI_TRIGGER_TOKEN if set in the repository. @@ -4741,27 +5074,29 @@ safe-outputs: # (optional) signed-commits: true - # When true, automatically close older open pull requests from the same workflow - # (identified by the workflow-id marker in the PR body) with a comment linking to - # the new PR. Searches for open PRs containing the workflow-id marker. Maximum 10 - # pull requests will be closed. Only runs if PR creation succeeds. - # (optional) - close-older-pull-requests: true - - # Optional explicit deduplication key for close-older matching. When set, a `` marker is embedded in the PR body and used as the - # primary key for searching and filtering older pull requests instead of the - # workflow-id markers. This gives deterministic isolation across caller workflows - # and is stable across workflow renames. The value is normalized to identifier - # style (lowercase alphanumeric, dashes, underscores). - # (optional) - close-older-key: "example-value" - # If true, emit step summary messages instead of making GitHub API calls for this # specific output type (preview mode) # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # When true, adds workflows: write to the GitHub App token permissions. Required # when allowed-files targets .github/workflows/ paths. Requires # safe-outputs.github-app to be configured because the workflows permission is a @@ -4823,6 +5158,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -4918,6 +5271,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -4977,6 +5348,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -4991,14 +5380,16 @@ safe-outputs: # Format 2: Enable with default configuration reply-to-pull-request-review-comment: null - # Enable AI agents to resolve review threads on the triggering pull request after - # addressing feedback. + # Enable AI agents to resolve review threads on pull requests after addressing + # feedback. Supports cross-repository resolution via target, target-repo, and + # allowed-repos. # (optional) # Accepted formats: - # Format 1: Configuration for resolving review threads on pull requests. - # Resolution is scoped to the triggering PR only — threads on other PRs cannot be - # resolved. + # Format 1: Configuration for resolving review threads on pull requests. By + # default, resolution is scoped to the triggering PR only. When target, + # target-repo, or allowed-repos are specified, cross-repository thread resolution + # is supported. resolve-pull-request-review-thread: # Maximum number of review threads to resolve (default: 10) Supports integer or # GitHub Actions expression (e.g. '${{ inputs.max }}'). @@ -5011,6 +5402,26 @@ safe-outputs: # Format 2: GitHub Actions expression that resolves to an integer at runtime max: "example-value" + # Target PR for thread resolution: 'triggering' (default, current PR), '*' (any + # PR, requires pull_request_number in agent output), or explicit PR number (e.g. + # ${{ github.event.inputs.pr_number }}). Required when workflow is not triggered + # by a pull request (e.g. workflow_dispatch). + # (optional) + target: "example-value" + + # Target repository in format 'owner/repo' for cross-repository PR review thread + # resolution. Takes precedence over trial target repo settings. + # (optional) + target-repo: "example-value" + + # List of additional repositories in format 'owner/repo' that PR review threads + # can be resolved in. When specified, the agent can use a 'repo' field in the + # output to specify which repository to resolve threads in. The target repository + # (current or target-repo) is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -5021,6 +5432,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -5082,6 +5511,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable code scanning alert creation with default configuration # (unlimited findings) create-code-scanning-alert: null @@ -5114,6 +5561,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable code scanning autofix creation with default configuration (max: # 10) autofix-code-scanning-alert: null @@ -5152,6 +5617,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # GitHub App credentials for minting an installation access token scoped to # checks:write for this handler. When set, a short-lived token is minted before # the handler runs and revoked afterwards. @@ -5439,6 +5922,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Enable AI agents to remove labels from GitHub issues or pull requests. # (optional) # Accepted formats: @@ -5512,6 +6013,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Enable AI agents to request reviews from users or teams on pull requests based # on code changes or expertise matching. # (optional) @@ -5562,6 +6081,12 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that reviewers can be + # added in. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -5572,6 +6097,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -5611,11 +6154,22 @@ safe-outputs: # Format 2: GitHub Actions expression that resolves to an integer at runtime max: "example-value" + # Target for milestone assignment: 'triggering' (default, current issue/PR), '*' + # (any issue/PR), or explicit number. + # (optional) + target: "example-value" + # Target repository in format 'owner/repo' for cross-repository milestone # assignment. Takes precedence over trial target repo settings. # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that milestone + # assignments can target. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -5626,6 +6180,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -5698,6 +6270,12 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that agents can be + # assigned in. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # Target repository where the pull request should be created, in format # 'owner/repo'. If omitted, the PR will be created in the same repository as the # issue (specified by target-repo or the workflow's repository). This allows @@ -5736,6 +6314,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Enable AI agents to assign issues or pull requests to specific GitHub users # based on workflow logic or expertise matching. # (optional) @@ -5802,6 +6398,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -5874,6 +6488,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -5907,6 +6539,11 @@ safe-outputs: # Format 2: GitHub Actions expression that resolves to an integer at runtime max: "example-value" + # Target for sub-issue linking: 'triggering' (default, current issue), '*' (any + # issue), or explicit issue number. + # (optional) + target: "example-value" + # Optional list of labels that parent issues must have to be eligible for linking # (optional) parent-required-labels: [] @@ -5930,6 +6567,12 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that sub-issue linking + # can target. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -5940,6 +6583,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Enable AI agents to edit and update existing GitHub issue content, titles, # labels, assignees, and metadata. # (optional) @@ -6011,6 +6672,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -6078,6 +6757,12 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that pull requests can be + # updated in. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -6088,6 +6773,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -6236,6 +6939,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Token used to push an empty commit after pushing changes to trigger CI events. # Works around the GITHUB_TOKEN limitation where pushes don't trigger workflow # runs. Defaults to the magic secret GH_AW_CI_TRIGGER_TOKEN if set in the @@ -6259,7 +6980,9 @@ safe-outputs: signed-commits: true # Target repository in format 'owner/repo' for cross-repository push to pull - # request branch. Takes precedence over trial target repo settings. + # request branch, or '*' to let the agent choose the target repository at runtime + # (requires checkout: configs with path: for each possible target). Takes + # precedence over trial target repo settings. # (optional) target-repo: "example-value" @@ -6397,11 +7120,22 @@ safe-outputs: # Format 2: GitHub Actions expression that resolves to an integer at runtime max: "example-value" + # Target for comment hiding: 'triggering' (default, current issue/PR), '*' (any + # item), or explicit number. + # (optional) + target: "example-value" + # Target repository in format 'owner/repo' for cross-repository comment hiding. # Takes precedence over trial target repo settings. # (optional) target-repo: "example-value" + # List of additional repositories in format 'owner/repo' that comment hiding can + # target. The target repository is always implicitly allowed. + # (optional) + allowed-repos: [] + # Array of strings + # List of allowed reasons for hiding comments. Default: all reasons allowed (spam, # abuse, off_topic, outdated, resolved, low_quality). # (optional) @@ -6420,6 +7154,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -6487,6 +7239,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -6553,6 +7323,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # All of these labels must be present on the target item for this operation to # proceed # (optional) @@ -6599,6 +7387,20 @@ safe-outputs: # (optional) target-repo: "example-value" + # List of repositories in format 'owner/repo' that cross-repository workflow + # dispatch may target. Supports arrays and GitHub Actions expressions resolving to + # a comma-separated list (e.g. '${{ inputs['allowed-repos'] }}'). + # (optional) + # Accepted formats: + + # Format 1: array + allowed-repos: [] + # Array items: string + + # Format 2: GitHub Actions expression resolving to a comma-separated list of + # repository slugs (e.g. '${{ inputs['allowed-repos'] }}') + allowed-repos: "example-value" + # Git ref (branch, tag, or SHA) to use when dispatching the workflow. For # workflow_call relay scenarios this is auto-injected by the compiler from # needs.activation.outputs.target_ref. Overrides the caller's GITHUB_REF. @@ -6610,6 +7412,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Shorthand array format: list of workflow names (without .md extension) # to allow dispatching dispatch-workflow: [] @@ -6658,6 +7478,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Shorthand array format: list of workflow names (without .md extension) # to allow calling call-workflow: [] @@ -6713,6 +7551,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable missing tool reporting with default configuration missing-tool: null @@ -6772,6 +7628,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable missing data reporting with default configuration missing-data: null @@ -6813,6 +7687,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable noop output with default configuration (max: 1) noop: null @@ -6861,6 +7753,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable asset publishing with default configuration upload-asset: null @@ -6927,7 +7837,7 @@ safe-outputs: # Default values injected when the model omits a field # (optional) defaults: - # Behavior when no files match: 'error' (default) or 'ignore' + # Behaviour when no files match: 'error' (default) or 'ignore' # (optional) if-no-files: "error" @@ -6977,6 +7887,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable release updates with default configuration update-release: null @@ -7251,11 +8179,52 @@ safe-outputs: # (optional) post-steps: [] + # Per-run AI Credits budget for threat-detection engine execution. Accepts numeric + # values only (expressions are not supported). When unset, gh-aw emits a runtime + # default expression `${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' + # }}`. + # (optional) + # Accepted formats: + + # Format 1: Configuration object + + # Format 2: integer + max-ai-credits: 1 + + # Format 3: string + max-ai-credits: "example-value" + # Runner specification for the detection job. Overrides agent.runs-on for the - # detection job only. Defaults to agent.runs-on. + # detection job only. Supports string, array, or runner-group object forms. + # Defaults to agent.runs-on. # (optional) + # Accepted formats: + + # Format 1: Simple runner label string. Use for standard GitHub-hosted runners + # (e.g., 'ubuntu-latest', 'windows-latest', 'macos-latest') or self-hosted runner + # labels. Most common form for agentic workflows. runs-on: "example-value" + # Format 2: Array of runner labels for selection with fallbacks. GitHub Actions + # will use the first available runner that matches any label in the array. Useful + # for high-availability setups or when multiple runner types are acceptable. + runs-on: [] + # Array items: string + + # Format 3: Runner group configuration for GitHub-hosted runners. Use this form to + # target specific runner groups (e.g., larger runners with more CPU/memory) or + # self-hosted runner pools with specific label requirements. Agentic workflows may + # benefit from larger runners for complex AI processing tasks. + runs-on: + # Runner group name for self-hosted runners or GitHub-hosted runner groups + # (optional) + group: "example-value" + + # List of runner labels for self-hosted runners or GitHub-hosted runner selection + # (optional) + labels: [] + # Array of strings + # When true (default), detection failures produce warnings and allow safe outputs # to proceed with a caution notice and 'needs-review' label. When false, detection # failures block safe outputs entirely. Accepts a boolean literal or a GitHub @@ -7501,7 +8470,7 @@ safe-outputs: # for workflows with many sequential safe output operations (e.g. # push_to_pull_request_branch against large repositories). # (optional) - timeout-minutes: 120 + timeout-minutes: 1 # Explicit additional custom workflow jobs that the consolidated safe_outputs job # should depend on. @@ -7529,12 +8498,37 @@ safe-outputs: url: "example-value" # Runner specification for all safe-outputs jobs (activation, create-issue, - # add-comment, etc.). Single runner label (e.g., 'ubuntu-slim', 'ubuntu-latest', - # 'windows-latest', 'self-hosted'). Defaults to 'ubuntu-slim'. See + # add-comment, etc.). Supports string, array, or runner-group object forms. + # Defaults to 'ubuntu-slim'. See # https://github.blog/changelog/2025-10-28-1-vcpu-linux-runner-now-available-in-github-actions-in-public-preview/ # (optional) + # Accepted formats: + + # Format 1: Simple runner label string. Use for standard GitHub-hosted runners + # (e.g., 'ubuntu-latest', 'windows-latest', 'macos-latest') or self-hosted runner + # labels. Most common form for agentic workflows. runs-on: "example-value" + # Format 2: Array of runner labels for selection with fallbacks. GitHub Actions + # will use the first available runner that matches any label in the array. Useful + # for high-availability setups or when multiple runner types are acceptable. + runs-on: [] + # Array items: string + + # Format 3: Runner group configuration for GitHub-hosted runners. Use this form to + # target specific runner groups (e.g., larger runners with more CPU/memory) or + # self-hosted runner pools with specific label requirements. Agentic workflows may + # benefit from larger runners for complex AI processing tasks. + runs-on: + # Runner group name for self-hosted runners or GitHub-hosted runner groups + # (optional) + group: "example-value" + + # List of runner labels for self-hosted runners or GitHub-hosted runner selection + # (optional) + labels: [] + # Array of strings + # Custom steps to inject into all safe-output jobs. These steps run after checking # out the repository and setting up the action, and before any safe-output code # executes. @@ -7601,6 +8595,24 @@ safe-outputs: # (optional) staged: true + # Internal hidden feature. Optional list of declarative sample payloads that + # exercise this safe-output handler. Used by the hidden `gh aw compile + # --use-samples` flag to replace the agentic step with a deterministic replay + # through the safe-outputs MCP server. Each entry should conform to the + # corresponding MCP tool inputSchema; recognized sidecar keys (currently `patch` + # for create-pull-request and push-to-pull-request-branch) are stripped before + # schema validation and consumed by the replay driver. + # (optional) + # Accepted formats: + + # Format 1: array + samples: [] + # Array items: object + + # Format 2: object + samples: + {} + # Format 2: Enable report_incomplete with default configuration report-incomplete: null diff --git a/docs/src/content/docs/reference/frontmatter.md b/docs/src/content/docs/reference/frontmatter.md index aa64f6a1d8e..98d548d8b33 100644 --- a/docs/src/content/docs/reference/frontmatter.md +++ b/docs/src/content/docs/reference/frontmatter.md @@ -195,7 +195,7 @@ runs-on-slim: ubuntu-slim # Defaults to ubuntu-slim (framework jobs o timeout-minutes: 30 # Defaults to 20 minutes ``` -`runs-on` applies to the main agent job only. `runs-on-slim` applies to all framework/generated jobs (activation, safe-outputs, unlock, etc.), accepts the same string, array, or runner-group object forms as `runs-on`, and defaults to `ubuntu-slim`. `safe-outputs.runs-on` takes precedence over `runs-on-slim` for safe-output jobs specifically. +`runs-on` applies to the main agent job only. `runs-on-slim` applies to all framework/generated jobs (activation, safe-outputs, unlock, etc.), accepts the same string, array, or runner-group object forms as `runs-on`, and defaults to `ubuntu-slim`. `safe-outputs.runs-on` and `safe-outputs.threat-detection.runs-on` also accept the same runner forms and take precedence where applicable. `timeout-minutes` accepts an integer or a GitHub Actions expression string (e.g. `${{ inputs.timeout }}`), letting a reusable `workflow_call` workflow parameterize its own timeout from caller inputs. It applies to the workflow being compiled, **not** to plain caller jobs that invoke a reusable workflow with job-level `uses:` — GitHub rejects `timeout-minutes` there. diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index 7ec695aa2bd..90c082c735f 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -1681,7 +1681,7 @@ Specify a custom runner for safe output jobs (default: `ubuntu-slim`): ```aw --- safe-outputs: - runs-on: ubuntu-22.04 + runs-on: [self-hosted, linux, x64] create-issue: {} --- ``` diff --git a/docs/src/content/docs/reference/self-hosted-runners.md b/docs/src/content/docs/reference/self-hosted-runners.md index 313647a1351..af58648b0ce 100644 --- a/docs/src/content/docs/reference/self-hosted-runners.md +++ b/docs/src/content/docs/reference/self-hosted-runners.md @@ -93,7 +93,7 @@ runs-on: [self-hosted, linux, x64] safe-outputs: create-issue: {} threat-detection: - runs-on: ubuntu-latest + runs-on: [self-hosted, linux, x64] --- ``` @@ -107,8 +107,9 @@ Framework jobs — activation, pre-activation, safe-outputs, unlock, APM, update --- on: issues runs-on: [self-hosted, linux, x64] -runs-on-slim: self-hosted +runs-on-slim: [self-hosted, linux, x64] safe-outputs: + runs-on: [self-hosted, linux, x64] create-issue: {} --- ``` diff --git a/pkg/parser/schema_location_test.go b/pkg/parser/schema_location_test.go index 4a67cffb0ad..38af5c5c851 100644 --- a/pkg/parser/schema_location_test.go +++ b/pkg/parser/schema_location_test.go @@ -442,6 +442,41 @@ func TestValidateMainWorkflowFrontmatterWithSchemaAndLocation_AcceptsRunsOnSlimO } } +func TestValidateMainWorkflowFrontmatterWithSchemaAndLocation_AcceptsSafeOutputsRunsOnArrayForm(t *testing.T) { + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "create-issue": map[string]any{}, + "runs-on": []any{"self-hosted", "linux", "x64"}, + }, + } + + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/test/workflow.md") + if err != nil { + t.Fatalf("ValidateMainWorkflowFrontmatterWithSchemaAndLocation() unexpected error = %v", err) + } +} + +func TestValidateMainWorkflowFrontmatterWithSchemaAndLocation_AcceptsThreatDetectionRunsOnObjectForm(t *testing.T) { + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "create-issue": map[string]any{}, + "threat-detection": map[string]any{ + "runs-on": map[string]any{ + "group": "arc-custom", + "labels": []any{"linux", "x64"}, + }, + }, + }, + } + + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/test/workflow.md") + if err != nil { + t.Fatalf("ValidateMainWorkflowFrontmatterWithSchemaAndLocation() unexpected error = %v", err) + } +} + func TestValidateMainWorkflowFrontmatterWithSchemaAndLocation_AcceptsAllowedBaseBranchesInCreatePullRequest(t *testing.T) { frontmatter := map[string]any{ "on": map[string]any{ diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index b5040a2d38e..017514c28cc 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -9848,8 +9848,16 @@ "description": "Per-run AI Credits budget for threat-detection engine execution. Accepts numeric values only (expressions are not supported). When unset, gh-aw emits a runtime default expression `${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}`." }, "runs-on": { - "type": "string", - "description": "Runner specification for the detection job. Overrides agent.runs-on for the detection job only. Defaults to agent.runs-on." + "$ref": "#/$defs/github_actions_runs_on", + "description": "Runner specification for the detection job. Overrides agent.runs-on for the detection job only. Supports string, array, or runner-group object forms. Defaults to agent.runs-on.", + "examples": [ + "ubuntu-latest", + ["self-hosted", "linux", "x64"], + { + "group": "larger-runners", + "labels": ["ubuntu-latest-8-cores"] + } + ] }, "continue-on-error": { "$ref": "#/$defs/templatable_boolean", @@ -10322,8 +10330,16 @@ ] }, "runs-on": { - "type": "string", - "description": "Runner specification for all safe-outputs jobs (activation, create-issue, add-comment, etc.). Single runner label (e.g., 'ubuntu-slim', 'ubuntu-latest', 'windows-latest', 'self-hosted'). Defaults to 'ubuntu-slim'. See https://github.blog/changelog/2025-10-28-1-vcpu-linux-runner-now-available-in-github-actions-in-public-preview/" + "$ref": "#/$defs/github_actions_runs_on", + "description": "Runner specification for all safe-outputs jobs (activation, create-issue, add-comment, etc.). Supports string, array, or runner-group object forms. Defaults to 'ubuntu-slim'. See https://github.blog/changelog/2025-10-28-1-vcpu-linux-runner-now-available-in-github-actions-in-public-preview/", + "examples": [ + "ubuntu-slim", + ["self-hosted", "linux", "x64"], + { + "group": "larger-runners", + "labels": ["ubuntu-latest-8-cores"] + } + ] }, "steps": { "type": "array", diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 028c95f4ff8..36d93def314 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -485,7 +485,7 @@ func resolveCentralSlashRunsOn(workflowDataList []*WorkflowData) string { resolved := constants.DefaultActivationJobRunnerImage if wd.SafeOutputs != nil && strings.TrimSpace(wd.SafeOutputs.RunsOn) != "" { - resolved = strings.TrimSpace(wd.SafeOutputs.RunsOn) + resolved = formatRunsOnSnippetForInlineValue(wd.SafeOutputs.RunsOn) } else if strings.TrimSpace(wd.RunsOnSlim) != "" { resolved = formatRunsOnSnippetForInlineValue(wd.RunsOnSlim) } diff --git a/pkg/workflow/frontmatter_parsing.go b/pkg/workflow/frontmatter_parsing.go index ba49622afeb..cb32f6762fe 100644 --- a/pkg/workflow/frontmatter_parsing.go +++ b/pkg/workflow/frontmatter_parsing.go @@ -34,6 +34,16 @@ func ParseFrontmatterConfig(frontmatter map[string]any) (*FrontmatterConfig, err if err := validateRunsOnValue(config.RunsOnSlim); err != nil { return nil, err } + if safeOutputsRaw, ok := frontmatter["safe-outputs"].(map[string]any); ok { + if err := validateRunsOnValue(safeOutputsRaw["runs-on"]); err != nil { + return nil, err + } + if threatRaw, ok := safeOutputsRaw["threat-detection"].(map[string]any); ok { + if err := validateRunsOnValue(threatRaw["runs-on"]); err != nil { + return nil, err + } + } + } // Parse typed Runtimes field if runtimes exist if len(config.Runtimes) > 0 { diff --git a/pkg/workflow/frontmatter_types_test.go b/pkg/workflow/frontmatter_types_test.go index efcee762cff..49345fb7f61 100644 --- a/pkg/workflow/frontmatter_types_test.go +++ b/pkg/workflow/frontmatter_types_test.go @@ -397,6 +397,40 @@ func TestParseFrontmatterConfig(t *testing.T) { reconstructed2 := config2.ToMap() assert.Equal(t, reconstructed, reconstructed2) }) + + t.Run("handles safe-outputs runs-on forms", func(t *testing.T) { + tests := []struct { + name string + safeOutputs any + expectedRunsOn string + }{ + { + name: "safe-outputs.runs-on string form", + safeOutputs: map[string]any{ + "runs-on": "ubuntu-latest", + }, + expectedRunsOn: "runs-on: ubuntu-latest", + }, + { + name: "safe-outputs.runs-on array form", + safeOutputs: map[string]any{ + "runs-on": []any{"self-hosted", "linux"}, + }, + expectedRunsOn: "runs-on:\n - self-hosted\n - linux", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := ParseFrontmatterConfig(map[string]any{ + "safe-outputs": tt.safeOutputs, + }) + require.NoError(t, err) + require.NotNil(t, config.SafeOutputs) + assert.Equal(t, tt.expectedRunsOn, config.SafeOutputs.RunsOn) + }) + } + }) } }) diff --git a/pkg/workflow/imports.go b/pkg/workflow/imports.go index 69bc768c46a..dd6de131297 100644 --- a/pkg/workflow/imports.go +++ b/pkg/workflow/imports.go @@ -435,7 +435,7 @@ func mergeSafeOutputConfig(result *SafeOutputsConfig, config map[string]any, c * if result.MaximumPatchSize == 0 && importedConfig.MaximumPatchSize > 0 { result.MaximumPatchSize = importedConfig.MaximumPatchSize } - if result.RunsOn == "" && importedConfig.RunsOn != "" { + if isEmptyRunsOnValue(result.RunsOn) && !isEmptyRunsOnValue(importedConfig.RunsOn) { result.RunsOn = importedConfig.RunsOn } if len(importedConfig.Needs) > 0 { diff --git a/pkg/workflow/runs_on_snippet.go b/pkg/workflow/runs_on_snippet.go new file mode 100644 index 00000000000..a2e17b2b35a --- /dev/null +++ b/pkg/workflow/runs_on_snippet.go @@ -0,0 +1,70 @@ +package workflow + +import ( + "strings" + + "github.com/goccy/go-yaml" +) + +func runsOnMarshalOptions() []yaml.EncodeOption { + opts := append([]yaml.EncodeOption{}, DefaultMarshalOptions...) + return append(opts, yaml.IndentSequence(true)) +} + +// renderRunsOnSnippet serializes a runs-on value into a "runs-on: ..." YAML snippet. +// Returns empty string for empty/unset values. +func renderRunsOnSnippet(value any) string { + if isEmptyRunsOnValue(value) { + return "" + } + + var yamlBytes []byte + var err error + if valueMap, ok := value.(map[string]any); ok { + orderedValue := OrderMapFields(valueMap, []string{}) + yamlBytes, err = yaml.MarshalWithOptions(yaml.MapSlice{{Key: "runs-on", Value: orderedValue}}, runsOnMarshalOptions()...) + } else { + yamlBytes, err = yaml.MarshalWithOptions(map[string]any{"runs-on": value}, runsOnMarshalOptions()...) + } + if err != nil { + return "" + } + + return strings.TrimSuffix(string(yamlBytes), "\n") +} + +func normalizeRunsOnSnippet(value string) string { + value = strings.TrimSpace(value) + if value == "" { + return "" + } + + snippet := value + if !strings.HasPrefix(snippet, "runs-on:") { + snippet = "runs-on: " + snippet + } + + var snippetMap map[string]any + if err := yaml.Unmarshal([]byte(snippet), &snippetMap); err == nil { + if runsOnValue, ok := snippetMap["runs-on"]; ok { + if rendered := renderRunsOnSnippet(runsOnValue); rendered != "" { + return rendered + } + } + } + return ensureRunsOnContinuationIndent(snippet) +} + +func ensureRunsOnContinuationIndent(snippet string) string { + lines := strings.Split(snippet, "\n") + if len(lines) <= 1 { + return snippet + } + for i := 1; i < len(lines); i++ { + if strings.TrimSpace(lines[i]) == "" || strings.HasPrefix(lines[i], " ") { + continue + } + lines[i] = " " + lines[i] + } + return strings.Join(lines, "\n") +} diff --git a/pkg/workflow/runs_on_unmarshal.go b/pkg/workflow/runs_on_unmarshal.go new file mode 100644 index 00000000000..3e981b94a86 --- /dev/null +++ b/pkg/workflow/runs_on_unmarshal.go @@ -0,0 +1,42 @@ +package workflow + +import "encoding/json" + +// UnmarshalJSON supports string/array/object forms for safe-outputs.runs-on while +// storing a normalized runs-on YAML snippet for downstream rendering. +func (c *SafeOutputsConfig) UnmarshalJSON(data []byte) error { + type alias SafeOutputsConfig + aux := &struct { + RunsOn any `json:"runs-on,omitempty"` + *alias + }{ + alias: (*alias)(c), + } + + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + c.RunsOn = renderRunsOnSnippet(aux.RunsOn) + return nil +} + +// UnmarshalJSON supports string/array/object forms for +// safe-outputs.threat-detection.runs-on while storing a normalized runs-on YAML +// snippet for downstream rendering. +func (c *ThreatDetectionConfig) UnmarshalJSON(data []byte) error { + type alias ThreatDetectionConfig + aux := &struct { + RunsOn any `json:"runs-on,omitempty"` + *alias + }{ + alias: (*alias)(c), + } + + if err := json.Unmarshal(data, aux); err != nil { + return err + } + + c.RunsOn = renderRunsOnSnippet(aux.RunsOn) + return nil +} diff --git a/pkg/workflow/runs_on_validation.go b/pkg/workflow/runs_on_validation.go index 9af09569cf0..ac94445f7e4 100644 --- a/pkg/workflow/runs_on_validation.go +++ b/pkg/workflow/runs_on_validation.go @@ -39,25 +39,36 @@ const macOSRunnerFAQURL = "https://github.github.com/gh-aw/reference/faq/#why-ar // // Returns an error with a FAQ link if a macOS runner is detected, nil otherwise. func validateRunsOn(frontmatter map[string]any, markdownPath string) error { - runsOn, exists := frontmatter["runs-on"] - if !exists { - return nil + runsOnValidationLog.Printf("Validating runs-on configuration") + + type runnerField struct { + name string + value any } - runsOnValidationLog.Printf("Validating runs-on configuration") + runsOnFields := []runnerField{ + {name: "runs-on", value: frontmatter["runs-on"]}, + {name: "runs-on-slim", value: frontmatter["runs-on-slim"]}, + } + if safeOutputs, ok := frontmatter["safe-outputs"].(map[string]any); ok { + runsOnFields = append(runsOnFields, runnerField{name: "safe-outputs.runs-on", value: safeOutputs["runs-on"]}) + if threatDetection, ok := safeOutputs["threat-detection"].(map[string]any); ok { + runsOnFields = append(runsOnFields, runnerField{name: "safe-outputs.threat-detection.runs-on", value: threatDetection["runs-on"]}) + } + } - labels := extractRunnerLabels(runsOn) - for _, label := range labels { - lower := strings.ToLower(label) - if strings.HasPrefix(lower, "macos-") || strings.EqualFold(lower, "macos") { - return formatCompilerError(markdownPath, "error", - fmt.Sprintf("runner '%s' is not supported in agentic workflows.\n\n"+ - "macOS runners are not supported because agentic workflows rely on containers "+ - "for the secure Agent Workflow Firewall sandbox, and GitHub-hosted macOS runners "+ - "do not support container jobs.\n\n"+ - "Use 'ubuntu-latest' (default) or another Linux-based runner instead.\n\n"+ - "See %s for details.", - label, macOSRunnerFAQURL), nil) + for _, field := range runsOnFields { + labels := extractRunnerLabels(field.value) + for _, label := range labels { + lower := strings.ToLower(label) + if strings.HasPrefix(lower, "macos-") || strings.EqualFold(lower, "macos") { + return formatCompilerError(markdownPath, "error", + fmt.Sprintf("%s includes unsupported runner '%s'.\n\n"+ + "Agentic workflows require Linux containers and container jobs. Use a Linux runner label or runner-group configuration instead.\n\n"+ + "Example: runs-on: [self-hosted, linux, x64]\n\n"+ + "See %s for details.", + field.name, label, macOSRunnerFAQURL), nil) + } } } diff --git a/pkg/workflow/runs_on_validation_test.go b/pkg/workflow/runs_on_validation_test.go index 00bdc80f79a..622684aac0e 100644 --- a/pkg/workflow/runs_on_validation_test.go +++ b/pkg/workflow/runs_on_validation_test.go @@ -119,6 +119,40 @@ func TestValidateRunsOn(t *testing.T) { errorInMsg: "containers", description: "Error should explain containers requirement", }, + { + name: "macos in runs-on-slim array", + frontmatter: map[string]any{"runs-on-slim": []any{"self-hosted", "macos-14"}}, + wantErr: true, + errorInMsg: "runs-on-slim", + description: "runs-on-slim array containing macos runner should be rejected", + }, + { + name: "macos in safe-outputs.runs-on array", + frontmatter: map[string]any{ + "safe-outputs": map[string]any{ + "runs-on": []any{"self-hosted", "macos-latest"}, + }, + }, + wantErr: true, + errorInMsg: "safe-outputs.runs-on", + description: "safe-outputs.runs-on array containing macos runner should be rejected", + }, + { + name: "macos in safe-outputs.threat-detection.runs-on labels", + frontmatter: map[string]any{ + "safe-outputs": map[string]any{ + "threat-detection": map[string]any{ + "runs-on": map[string]any{ + "group": "runner-group", + "labels": []any{"linux", "macos-latest"}, + }, + }, + }, + }, + wantErr: true, + errorInMsg: "safe-outputs.threat-detection.runs-on", + description: "threat-detection runs-on labels containing macos runner should be rejected", + }, } for _, tt := range tests { diff --git a/pkg/workflow/safe_outputs_config.go b/pkg/workflow/safe_outputs_config.go index a052ba7148e..7cf4beb7d8b 100644 --- a/pkg/workflow/safe_outputs_config.go +++ b/pkg/workflow/safe_outputs_config.go @@ -527,9 +527,7 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut // Handle runs-on configuration if runsOn, exists := outputMap["runs-on"]; exists { - if runsOnStr, ok := runsOn.(string); ok { - config.RunsOn = runsOnStr - } + config.RunsOn = renderRunsOnSnippet(runsOn) } // Handle timeout-minutes configuration diff --git a/pkg/workflow/safe_outputs_import_test.go b/pkg/workflow/safe_outputs_import_test.go index bcb95cac7ca..4e2ae9576a2 100644 --- a/pkg/workflow/safe_outputs_import_test.go +++ b/pkg/workflow/safe_outputs_import_test.go @@ -788,7 +788,7 @@ This workflow uses the imported meta configuration. // Note: When main workflow has safe-outputs section, extractSafeOutputsConfig sets MaximumPatchSize default (4096) // before merge happens, so imported value is not used. User should specify max-patch-size in main workflow. assert.Equal(t, 4096, workflowData.SafeOutputs.MaximumPatchSize, "MaximumPatchSize defaults to 4096 when main has safe-outputs") - assert.Equal(t, "ubuntu-latest", workflowData.SafeOutputs.RunsOn, "RunsOn should be imported") + assert.Equal(t, "runs-on: ubuntu-latest", workflowData.SafeOutputs.RunsOn, "RunsOn should be imported") } // TestSafeOutputsImportMetaFieldsMainTakesPrecedence tests that main workflow meta fields take precedence over imports @@ -929,7 +929,7 @@ This workflow uses only imported safe-outputs configuration. assert.Equal(t, "${{ secrets.IMPORT_TOKEN }}", workflowData.SafeOutputs.GitHubToken, "GitHubToken should be imported") assert.Equal(t, 4096, workflowData.SafeOutputs.MaximumPatchSize, "MaximumPatchSize should be imported") assert.True(t, workflowData.SafeOutputs.Staged, "Staged should be imported and set to true") - assert.Equal(t, "ubuntu-22.04", workflowData.SafeOutputs.RunsOn, "RunsOn should be imported") + assert.Equal(t, "runs-on: ubuntu-22.04", workflowData.SafeOutputs.RunsOn, "RunsOn should be imported") } // TestSafeOutputsImportJobsFromSharedWorkflow tests that safe-outputs.jobs can be imported from shared workflows diff --git a/pkg/workflow/safe_outputs_runs_on_test.go b/pkg/workflow/safe_outputs_runs_on_test.go index 5c97f98349c..cd47b518f1e 100644 --- a/pkg/workflow/safe_outputs_runs_on_test.go +++ b/pkg/workflow/safe_outputs_runs_on_test.go @@ -48,6 +48,38 @@ safe-outputs: This is a test workflow.`, expectedRunsOn: "runs-on: windows-latest", }, + { + name: "custom runs-on array", + frontmatter: `--- +on: push +safe-outputs: + create-issue: + title-prefix: "[ai] " + runs-on: [self-hosted, linux, x64] +--- + +# Test Workflow + +This is a test workflow.`, + expectedRunsOn: "runs-on:\n - self-hosted\n - linux\n - x64", + }, + { + name: "custom runs-on object", + frontmatter: `--- +on: push +safe-outputs: + create-issue: + title-prefix: "[ai] " + runs-on: + group: runner-group + labels: [linux, x64] +--- + +# Test Workflow + +This is a test workflow.`, + expectedRunsOn: "runs-on:\n group: runner-group\n labels:\n - linux\n - x64", + }, } for _, tt := range tests { @@ -271,7 +303,7 @@ safe-outputs: # Test Workflow This is a test workflow.`, - expectedRunsOn: "runs-on:\n - self-hosted\n - ubuntu2404\n - x64\n - host", + expectedRunsOn: "runs-on:\n - self-hosted\n - ubuntu2404\n - x64\n - host", checkJobPatterns: []string{"\n activation:", "\n safe_outputs:"}, }, { @@ -289,7 +321,7 @@ safe-outputs: # Test Workflow This is a test workflow.`, - expectedRunsOn: "runs-on:\n group: runner-group\n labels:\n - ubuntu2404\n - x64", + expectedRunsOn: "runs-on:\n group: runner-group\n labels:\n - ubuntu2404\n - x64", checkJobPatterns: []string{"\n activation:", "\n safe_outputs:"}, }, { @@ -372,14 +404,14 @@ func TestFormatFrameworkJobRunsOn(t *testing.T) { name: "safe-outputs.runs-on takes precedence over runs-on-slim", data: &WorkflowData{ RunsOnSlim: "runs-on: ubuntu-22.04", - SafeOutputs: &SafeOutputsConfig{RunsOn: "self-hosted"}, + SafeOutputs: &SafeOutputsConfig{RunsOn: "runs-on: self-hosted"}, }, expectedRunsOn: "runs-on: self-hosted", }, { name: "safe-outputs.runs-on used when runs-on-slim is empty", data: &WorkflowData{ - SafeOutputs: &SafeOutputsConfig{RunsOn: "windows-latest"}, + SafeOutputs: &SafeOutputsConfig{RunsOn: "runs-on: windows-latest"}, }, expectedRunsOn: "runs-on: windows-latest", }, @@ -392,20 +424,32 @@ func TestFormatFrameworkJobRunsOn(t *testing.T) { expectedRunsOn: "runs-on: " + constants.DefaultActivationJobRunnerImage, }, { - name: "runs-on-slim array snippet indents continuation lines by 4 spaces", + name: "safe-outputs.runs-on array snippet preserves valid YAML nesting", + data: &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{RunsOn: "runs-on:\n- self-hosted\n- linux"}, + }, + expectedRunsOn: "runs-on:\n - self-hosted\n - linux", + }, + { + name: "safe-outputs.runs-on object snippet preserves valid YAML nesting", + data: &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{RunsOn: "runs-on:\n group: runner-group\n labels:\n - linux"}, + }, + expectedRunsOn: "runs-on:\n group: runner-group\n labels:\n - linux", + }, + { + name: "runs-on-slim array snippet preserves valid YAML nesting", data: &WorkflowData{ RunsOnSlim: "runs-on:\n- self-hosted\n- ubuntu2404", }, - expectedRunsOn: "runs-on:\n - self-hosted\n - ubuntu2404", + expectedRunsOn: "runs-on:\n - self-hosted\n - ubuntu2404", }, { - // Object continuation lines start at 2-space (DefaultMarshalOptions) so - // indentYAMLLines(" ") produces 2+4=6 spaces for each continuation line. - name: "runs-on-slim group+labels object snippet indents continuation lines by 4 spaces", + name: "runs-on-slim group+labels object snippet preserves valid YAML nesting", data: &WorkflowData{ RunsOnSlim: "runs-on:\n group: runner-group\n labels:\n - ubuntu2404", }, - expectedRunsOn: "runs-on:\n group: runner-group\n labels:\n - ubuntu2404", + expectedRunsOn: "runs-on:\n group: runner-group\n labels:\n - ubuntu2404", }, } diff --git a/pkg/workflow/safe_outputs_runtime.go b/pkg/workflow/safe_outputs_runtime.go index d28d1120951..5517a5202c2 100644 --- a/pkg/workflow/safe_outputs_runtime.go +++ b/pkg/workflow/safe_outputs_runtime.go @@ -24,12 +24,14 @@ var safeOutputsRuntimeLog = logger.New("workflow:safe_outputs_runtime") // 3. DefaultActivationJobRunnerImage — compiled-in default func (c *Compiler) formatFrameworkJobRunsOn(data *WorkflowData) string { if data != nil && data.SafeOutputs != nil && data.SafeOutputs.RunsOn != "" { - safeOutputsRuntimeLog.Printf("Framework job runs-on from safe-outputs: %s", data.SafeOutputs.RunsOn) - return "runs-on: " + data.SafeOutputs.RunsOn + snippet := normalizeRunsOnSnippet(data.SafeOutputs.RunsOn) + safeOutputsRuntimeLog.Printf("Framework job runs-on from safe-outputs: %s", snippet) + return c.indentYAMLLines(snippet, " ") } if data != nil && data.RunsOnSlim != "" { - safeOutputsRuntimeLog.Printf("Framework job runs-on from runs-on-slim: %s", data.RunsOnSlim) - return c.indentYAMLLines(data.RunsOnSlim, " ") + snippet := normalizeRunsOnSnippet(data.RunsOnSlim) + safeOutputsRuntimeLog.Printf("Framework job runs-on from runs-on-slim: %s", snippet) + return c.indentYAMLLines(snippet, " ") } safeOutputsRuntimeLog.Printf("Framework job runs-on using default: %s", constants.DefaultActivationJobRunnerImage) return "runs-on: " + constants.DefaultActivationJobRunnerImage diff --git a/pkg/workflow/threat_detection.go b/pkg/workflow/threat_detection.go index f37b1372085..fff53a14d83 100644 --- a/pkg/workflow/threat_detection.go +++ b/pkg/workflow/threat_detection.go @@ -199,9 +199,7 @@ func (c *Compiler) parseThreatDetectionObjectConfig(configMap map[string]any) *T // Parse runs-on field if runOn, exists := configMap["runs-on"]; exists { - if runOnStr, ok := runOn.(string); ok { - threatConfig.RunsOn = runOnStr - } + threatConfig.RunsOn = renderRunsOnSnippet(runOn) } // Parse continue-on-error field (default: true). @@ -993,7 +991,7 @@ func (c *Compiler) buildDetectionJob(data *WorkflowData) (*Job, error) { // not need the same custom runner as safe-outputs. runsOn := "runs-on: ubuntu-latest" if data.SafeOutputs.ThreatDetection.RunsOn != "" { - runsOn = "runs-on: " + data.SafeOutputs.ThreatDetection.RunsOn + runsOn = normalizeRunsOnSnippet(data.SafeOutputs.ThreatDetection.RunsOn) } // Detection job condition: always run if agent job was not skipped AND produced outputs or a patch. diff --git a/pkg/workflow/threat_detection_test.go b/pkg/workflow/threat_detection_test.go index 9f882a634ff..40bc59ce992 100644 --- a/pkg/workflow/threat_detection_test.go +++ b/pkg/workflow/threat_detection_test.go @@ -153,7 +153,32 @@ func TestParseThreatDetectionConfig(t *testing.T) { }, }, expectedConfig: &ThreatDetectionConfig{ - RunsOn: "self-hosted", + RunsOn: "runs-on: self-hosted", + }, + }, + { + name: "object with runs-on array override", + outputMap: map[string]any{ + "threat-detection": map[string]any{ + "runs-on": []any{"self-hosted", "linux", "x64"}, + }, + }, + expectedConfig: &ThreatDetectionConfig{ + RunsOn: "runs-on:\n - self-hosted\n - linux\n - x64", + }, + }, + { + name: "object with runs-on group+labels override", + outputMap: map[string]any{ + "threat-detection": map[string]any{ + "runs-on": map[string]any{ + "group": "runner-group", + "labels": []any{"linux", "x64"}, + }, + }, + }, + expectedConfig: &ThreatDetectionConfig{ + RunsOn: "runs-on:\n group: runner-group\n labels:\n - linux\n - x64", }, }, {