From be02a5d3ced38d2de66516ae454b4a2c759e4e83 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 13:40:02 +0000 Subject: [PATCH 1/3] Initial plan From fa1726dc998691c823526c0e9f79ac24fceeda6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 13:59:46 +0000 Subject: [PATCH 2/3] Add title-prefix support to update-issue safe output Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- actions/setup/js/update_issue.cjs | 79 ++++++++++-------- actions/setup/js/update_issue.test.cjs | 84 ++++++++++++++++++++ pkg/parser/schemas/main_workflow_schema.json | 4 + pkg/workflow/compiler_safe_outputs_config.go | 3 +- pkg/workflow/update_issue.go | 13 +-- pkg/workflow/update_issue_test.go | 51 ++++++++++++ 6 files changed, 197 insertions(+), 37 deletions(-) diff --git a/actions/setup/js/update_issue.cjs b/actions/setup/js/update_issue.cjs index 7028a0551a..fa1550aa63 100644 --- a/actions/setup/js/update_issue.cjs +++ b/actions/setup/js/update_issue.cjs @@ -39,45 +39,57 @@ async function executeIssueUpdate(github, context, issueNumber, updateData) { const operation = updateData._operation || "append"; let rawBody = updateData._rawBody; const includeFooter = updateData._includeFooter !== false; // Default to true + const titlePrefix = updateData._titlePrefix || ""; // Remove internal fields - const { _operation, _rawBody, _includeFooter, ...apiData } = updateData; - - // If we have a body, process it with the appropriate operation - if (rawBody !== undefined) { - // Load and apply temporary project URL replacements FIRST - // This resolves any temporary project IDs (e.g., #aw_abc123def456) to actual project URLs - const temporaryProjectMap = loadTemporaryProjectMap(); - if (temporaryProjectMap.size > 0) { - rawBody = replaceTemporaryProjectReferences(rawBody, temporaryProjectMap); - core.debug(`Applied ${temporaryProjectMap.size} temporary project URL replacement(s)`); - } + const { _operation, _rawBody, _includeFooter, _titlePrefix, ...apiData } = updateData; - // Fetch current issue body for all operations (needed for append/prepend/replace-island/replace) + // Fetch current issue if needed (title prefix validation or body update) + if (titlePrefix || rawBody !== undefined) { const { data: currentIssue } = await github.rest.issues.get({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issueNumber, }); - const currentBody = currentIssue.body || ""; - - // Get workflow run URL for AI attribution - const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; - const workflowId = process.env.GH_AW_WORKFLOW_ID || ""; - const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - - // Use helper to update body (handles all operations including replace) - apiData.body = updateBody({ - currentBody, - newContent: rawBody, - operation, - workflowName, - runUrl, - workflowId, - includeFooter, // Pass footer flag to helper - }); - core.info(`Will update body (length: ${apiData.body.length})`); + // Validate title prefix if specified + if (titlePrefix) { + const currentTitle = currentIssue.title || ""; + if (!currentTitle.startsWith(titlePrefix)) { + throw new Error(`Issue title "${currentTitle}" does not start with required prefix "${titlePrefix}"`); + } + core.info(`✓ Title prefix validation passed: "${titlePrefix}"`); + } + + if (rawBody !== undefined) { + // Load and apply temporary project URL replacements FIRST + // This resolves any temporary project IDs (e.g., #aw_abc123def456) to actual project URLs + const temporaryProjectMap = loadTemporaryProjectMap(); + if (temporaryProjectMap.size > 0) { + rawBody = replaceTemporaryProjectReferences(rawBody, temporaryProjectMap); + core.debug(`Applied ${temporaryProjectMap.size} temporary project URL replacement(s)`); + } + + const currentBody = currentIssue.body || ""; + + // Get workflow run URL for AI attribution + const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; + const workflowId = process.env.GH_AW_WORKFLOW_ID || ""; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + + // Use helper to update body (handles all operations including replace) + apiData.body = updateBody({ + currentBody, + newContent: rawBody, + operation, + workflowName, + runUrl, + workflowId, + includeFooter, // Pass footer flag to helper + }); + + core.info(`Will update body (length: ${apiData.body.length})`); + } } const { data: issue } = await github.rest.issues.update({ @@ -111,7 +123,7 @@ function buildIssueUpdateData(item, config) { const updateData = {}; if (item.title !== undefined) { - // Sanitize title for Unicode security (no prefix handling needed for updates) + // Sanitize title for Unicode security updateData.title = sanitizeTitle(item.title); } // Check if body updates are allowed (defaults to true if not specified) @@ -158,6 +170,11 @@ function buildIssueUpdateData(item, config) { // Pass footer config to executeUpdate (default to true) updateData._includeFooter = config.footer !== false; + // Store title prefix for validation in executeIssueUpdate + if (config.title_prefix) { + updateData._titlePrefix = config.title_prefix; + } + return { success: true, data: updateData }; } diff --git a/actions/setup/js/update_issue.test.cjs b/actions/setup/js/update_issue.test.cjs index 515b92eb07..6ebdca8735 100644 --- a/actions/setup/js/update_issue.test.cjs +++ b/actions/setup/js/update_issue.test.cjs @@ -657,3 +657,87 @@ describe("update_issue.cjs - allow_body configuration", () => { expect(result.error).toContain("received 6"); }); }); + +describe("update_issue.cjs - title_prefix configuration", () => { + beforeEach(async () => { + vi.resetAllMocks(); + vi.resetModules(); + }); + + it("should store _titlePrefix in updateData when title_prefix is configured", async () => { + const { buildIssueUpdateData } = await import("./update_issue.cjs"); + + const item = { body: "New content" }; + const config = { title_prefix: "[bot] " }; + + const result = buildIssueUpdateData(item, config); + + expect(result.success).toBe(true); + expect(result.data._titlePrefix).toBe("[bot] "); + }); + + it("should not set _titlePrefix when title_prefix is not configured", async () => { + const { buildIssueUpdateData } = await import("./update_issue.cjs"); + + const item = { body: "New content" }; + const config = {}; + + const result = buildIssueUpdateData(item, config); + + expect(result.success).toBe(true); + expect(result.data._titlePrefix).toBeUndefined(); + }); + + it("should validate title prefix and succeed when issue title starts with prefix", async () => { + // Set up mocks for the full handler flow + mockGithub.rest.issues.get.mockResolvedValueOnce({ + data: { + number: 100, + title: "[bot] Fix something", + body: "Original body", + html_url: "https://github.com/testowner/testrepo/issues/100", + }, + }); + mockGithub.rest.issues.update.mockResolvedValueOnce({ + data: { + number: 100, + title: "[bot] Fix something", + body: "Updated", + html_url: "https://github.com/testowner/testrepo/issues/100", + }, + }); + + const { main } = await import("./update_issue.cjs"); + const handler = await main({ title_prefix: "[bot] ", allow_body: true }); + + const message = { issue_number: 100, body: "Updated" }; + const result = await handler(message, {}); + + expect(result.success).toBe(true); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Title prefix validation passed")); + }); + + it("should reject update when issue title does not start with required prefix", async () => { + // Set up mock to return issue with wrong title prefix + mockGithub.rest.issues.get.mockResolvedValueOnce({ + data: { + number: 100, + title: "Some other issue", + body: "Original body", + html_url: "https://github.com/testowner/testrepo/issues/100", + }, + }); + + const { main } = await import("./update_issue.cjs"); + const handler = await main({ title_prefix: "[bot] ", allow_body: true }); + + const message = { issue_number: 100, body: "Updated" }; + const result = await handler(message, {}); + + expect(result.success).toBe(false); + expect(result.error).toContain("[bot] "); + expect(result.error).toContain("Some other issue"); + // Should not call update API + expect(mockGithub.rest.issues.update).not.toHaveBeenCalled(); + }); +}); diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 49540fe0c3..455ecb8cf6 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -5700,6 +5700,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository issue updates. Takes precedence over trial target repo settings." + }, + "title-prefix": { + "type": "string", + "description": "Required prefix for issue title. Only issues with this title prefix can be updated." } }, "additionalProperties": false diff --git a/pkg/workflow/compiler_safe_outputs_config.go b/pkg/workflow/compiler_safe_outputs_config.go index 35043b1704..01985bcead 100644 --- a/pkg/workflow/compiler_safe_outputs_config.go +++ b/pkg/workflow/compiler_safe_outputs_config.go @@ -309,7 +309,8 @@ var handlerRegistry = map[string]handlerBuilder{ c := cfg.UpdateIssues builder := newHandlerConfigBuilder(). AddIfPositive("max", c.Max). - AddIfNotEmpty("target", c.Target) + AddIfNotEmpty("target", c.Target). + AddIfNotEmpty("title_prefix", c.TitlePrefix) // Boolean pointer fields indicate which fields can be updated if c.Status != nil { builder.AddDefault("allow_status", true) diff --git a/pkg/workflow/update_issue.go b/pkg/workflow/update_issue.go index 72a0f50661..dc7954bee1 100644 --- a/pkg/workflow/update_issue.go +++ b/pkg/workflow/update_issue.go @@ -9,10 +9,11 @@ var updateIssueLog = logger.New("workflow:update_issue") // UpdateIssuesConfig holds configuration for updating GitHub issues from agent output type UpdateIssuesConfig struct { UpdateEntityConfig `yaml:",inline"` - Status *bool `yaml:"status,omitempty"` // Allow updating issue status (open/closed) - presence indicates field can be updated - Title *bool `yaml:"title,omitempty"` // Allow updating issue title - presence indicates field can be updated - Body *bool `yaml:"body,omitempty"` // Allow updating issue body - boolean value controls permission (defaults to true) - Footer *bool `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. + Status *bool `yaml:"status,omitempty"` // Allow updating issue status (open/closed) - presence indicates field can be updated + Title *bool `yaml:"title,omitempty"` // Allow updating issue title - presence indicates field can be updated + Body *bool `yaml:"body,omitempty"` // Allow updating issue body - boolean value controls permission (defaults to true) + Footer *bool `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. + TitlePrefix string `yaml:"title-prefix,omitempty"` // Required title prefix for issue validation - only issues with this prefix can be updated } // parseUpdateIssuesConfig handles update-issue configuration @@ -26,5 +27,7 @@ func (c *Compiler) parseUpdateIssuesConfig(outputMap map[string]any) *UpdateIssu {Name: "body", Mode: FieldParsingBoolValue, Dest: &cfg.Body}, {Name: "footer", Mode: FieldParsingBoolValue, Dest: &cfg.Footer}, } - }, nil) + }, func(configMap map[string]any, cfg *UpdateIssuesConfig) { + cfg.TitlePrefix = parseTitlePrefixFromConfig(configMap) + }) } diff --git a/pkg/workflow/update_issue_test.go b/pkg/workflow/update_issue_test.go index a5c893acdc..3293c83b0b 100644 --- a/pkg/workflow/update_issue_test.go +++ b/pkg/workflow/update_issue_test.go @@ -370,3 +370,54 @@ This workflow tests body: (null) for backward compatibility. t.Fatal("Expected body to be true when set to null (backward compatibility)") } } + +func TestUpdateIssueTitlePrefix(t *testing.T) { + // Test that title-prefix is parsed correctly + tmpDir := testutil.TempDir(t, "output-update-issue-title-prefix-test") + + testContent := `--- +on: + issues: + types: [opened] +permissions: + contents: read + issues: write + pull-requests: read +engine: claude +features: + dangerous-permissions-write: true +strict: false +safe-outputs: + update-issue: + title-prefix: "[bot] " + body: true +--- + +# Test Update Issue Title Prefix + +This workflow tests the update-issue title-prefix configuration. +` + + testFile := filepath.Join(tmpDir, "test-update-issue-title-prefix.md") + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatal(err) + } + + compiler := NewCompiler() + workflowData, err := compiler.ParseWorkflowFile(testFile) + if err != nil { + t.Fatalf("Unexpected error parsing workflow with title-prefix: %v", err) + } + + if workflowData.SafeOutputs == nil { + t.Fatal("Expected output configuration to be parsed") + } + + if workflowData.SafeOutputs.UpdateIssues == nil { + t.Fatal("Expected update-issue configuration to be parsed") + } + + if workflowData.SafeOutputs.UpdateIssues.TitlePrefix != "[bot] " { + t.Fatalf("Expected title-prefix to be '[bot] ', got '%s'", workflowData.SafeOutputs.UpdateIssues.TitlePrefix) + } +} From 27ab649f929af6bd5fe902286ae58cf9d7a68485 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 16:05:44 +0000 Subject: [PATCH 3/3] Update docs: add title-prefix to update-issue safe output documentation Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- docs/public/editor/autocomplete-data.json | 22 +- .../docs/reference/frontmatter-full.md | 480 +++++++++++++++--- .../content/docs/reference/safe-outputs.md | 5 +- 3 files changed, 414 insertions(+), 93 deletions(-) diff --git a/docs/public/editor/autocomplete-data.json b/docs/public/editor/autocomplete-data.json index 61e877b38b..2253e56031 100644 --- a/docs/public/editor/autocomplete-data.json +++ b/docs/public/editor/autocomplete-data.json @@ -34,6 +34,12 @@ "desc": "Optional array of workflow specifications to import (similar to @include directives but defined in frontmatter).", "array": true }, + "inlined-imports": { + "type": "boolean", + "desc": "If true, inline all imports (including those without inputs) at compilation time in the generated lock.yml instead of...", + "enum": [true, false], + "leaf": true + }, "on": { "type": "string|object", "desc": "Workflow triggers that define when the agentic workflow should run.", @@ -739,12 +745,12 @@ "engine": { "type": "string|object", "desc": "AI engine configuration that specifies which AI processor interprets and executes the markdown content of the workflow.", - "enum": ["claude", "codex", "copilot"], + "enum": ["claude", "codex", "copilot", "gemini"], "children": { "id": { "type": "string", - "desc": "AI engine identifier: 'claude' (Claude Code), 'codex' (OpenAI Codex CLI), or 'copilot' (GitHub Copilot CLI)", - "enum": ["claude", "codex", "copilot"], + "desc": "AI engine identifier: 'claude' (Claude Code), 'codex' (OpenAI Codex CLI), 'copilot' (GitHub Copilot CLI), or 'gemini'...", + "enum": ["claude", "codex", "copilot", "gemini"], "leaf": true }, "version": { @@ -793,11 +799,6 @@ "type": "object", "desc": "Custom environment variables to pass to the AI engine, including secret overrides (e.g., OPENAI_API_KEY: ${{ secrets...." }, - "steps": { - "type": "array", - "desc": "Custom GitHub Actions steps for 'custom' engine.", - "array": true - }, "error_patterns": { "type": "array", "desc": "Custom error patterns for validating agent logs", @@ -1167,6 +1168,11 @@ "desc": "If true, only checks if cache entry exists and skips download", "enum": [true, false], "leaf": true + }, + "name": { + "type": "string", + "desc": "Optional custom name for the cache step (overrides auto-generated name)", + "leaf": true } }, "array": true diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 073c4838b3..4c611e6534 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -64,6 +64,13 @@ metadata: # (optional) imports: [] +# If true, inline all imports (including those without inputs) at compilation time +# in the generated lock.yml instead of using runtime-import macros. When enabled, +# the frontmatter hash covers the entire markdown body so any change to the +# content will invalidate the hash. +# (optional) +inlined-imports: true + # Workflow triggers that define when the agentic workflow should run. Supports # standard GitHub Actions trigger events plus special command triggers for # /commands (required) @@ -127,6 +134,55 @@ on: events: [] # Array items: GitHub Actions event name. + # DEPRECATED: Use 'slash_command' instead. Special command trigger for /command + # workflows (e.g., '/my-bot' in issue comments). Creates conditions to match slash + # commands automatically. + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: Null command configuration - defaults to using the workflow filename + # (without .md extension) as the command name + command: null + + # Option 2: Command name as a string (shorthand format, e.g., 'customname' for + # '/customname' triggers). Command names must not start with '/' as the slash is + # automatically added when matching commands. + command: "example-value" + + # Option 3: Command configuration object with custom command name + command: + # Name of the slash command that triggers the workflow (e.g., '/deploy', '/test'). + # Used for command-based workflow activation. + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: Custom command name for slash commands (e.g., 'helper-bot' for + # '/helper-bot' triggers). Command names must not start with '/' as the slash is + # automatically added when matching commands. Defaults to workflow filename + # without .md extension if not specified. + name: "My Workflow" + + # Option 2: Array of command names that trigger this workflow (e.g., ['cmd.add', + # 'cmd.remove'] for '/cmd.add' and '/cmd.remove' triggers). Each command name must + # not start with '/'. + name: [] + # Array items: Command name without leading slash + + # Events where the command should be active. Default is all comment-related events + # ('*'). Use GitHub Actions event names. + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: Single event name or '*' for all events. Use GitHub Actions event + # names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', + # 'pull_request_review_comment', 'discussion', 'discussion_comment'. + events: "*" + + # Option 2: Array of event names where the command should be active (requires at + # least one). Use GitHub Actions event names. + events: [] + # Array items: GitHub Actions event name. + # Push event trigger that runs the workflow when code is pushed to the repository # (optional) # This field supports multiple formats (oneOf): @@ -537,6 +593,62 @@ on: # (optional) min: 1 + # Skip workflow execution for users with specific repository roles. Useful for + # workflows that should only run for external contributors or specific permission + # levels. + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: Single role to skip workflow for (e.g., 'admin'). If the triggering + # user has this role, the workflow will be skipped. + skip-roles: "example-value" + + # Option 2: List of roles to skip workflow for (e.g., ['admin', 'maintainer', + # 'write']). If the triggering user has any of these roles, the workflow will be + # skipped. + skip-roles: [] + # Array items: string + + # Skip workflow execution for specific GitHub users. Useful for preventing + # workflows from running for specific accounts (e.g., bots, specific team + # members). + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: Single GitHub username to skip workflow for (e.g., 'user1'). If the + # triggering user matches, the workflow will be skipped. + skip-bots: "example-value" + + # Option 2: List of GitHub usernames to skip workflow for (e.g., ['user1', + # 'user2']). If the triggering user is in this list, the workflow will be skipped. + skip-bots: [] + # Array items: string + + # Repository access roles required to trigger agentic workflows. Defaults to + # ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any + # authenticated user (⚠️ security consideration). + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: Allow any authenticated user to trigger the workflow (⚠️ disables + # permission checking entirely - use with caution) + roles: "all" + + # Option 2: List of repository permission levels that can trigger the workflow. + # Permission checks are automatically applied to potentially unsafe triggers. + roles: [] + # Array items: Repository permission level: 'admin' (full access), + # 'maintainer'/'maintain' (repository management), 'write' (push access), 'triage' + # (issue management) + + # Allow list of bot identifiers that can trigger the workflow even if they don't + # meet the required role permissions. When the actor is in this list, the bot must + # be active (installed) on the repository to trigger the workflow. + # (optional) + bots: [] + # Array of Bot identifier/name (e.g., 'dependabot[bot]', 'renovate[bot]', + # 'github-actions[bot]') + # Environment name that requires manual approval before the workflow can run. Must # match a valid environment configured in the repository settings. # (optional) @@ -555,6 +667,14 @@ on: # to +1 and -1 strings respectively. reaction: 1 + # Whether to post status comments (started/completed) on the triggering item. When + # true, adds a comment with workflow run link and updates it on completion. When + # false or not specified, no status comments are posted. Must be explicitly set to + # true to enable status comments - there is no automatic bundling with + # ai-reaction. + # (optional) + status-comment: true + # GitHub token permissions for the workflow. Controls what the GITHUB_TOKEN can # access during execution. Use the principle of least privilege - only grant the # minimum permissions needed. @@ -598,10 +718,11 @@ permissions: # (optional) discussions: "read" - # Permission level for OIDC token requests (read/write/none). Allows workflows to - # request JWT tokens for cloud provider authentication. + # Permission level for OIDC token requests (write/none only - read is not + # supported). Allows workflows to request JWT tokens for cloud provider + # authentication. # (optional) - id-token: "read" + id-token: "write" # Permission for repository issues (read: view issues, write: create/update/close # issues, none: no access) @@ -756,9 +877,21 @@ env: "example-value" features: {} +# DEPRECATED: Use 'disable-model-invocation' instead. Controls whether the custom +# agent should infer additional context from the conversation. This field is +# maintained for backward compatibility with existing custom agent files. +# (optional) +infer: true + +# Controls whether the custom agent should disable model invocation. When set to +# true, the agent will not make additional model calls. This is the preferred +# field name for custom agent files (replaces the deprecated 'infer' field). +# (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 workflow components. +# properties. Typically used to provide secrets to MCP servers or custom engines. # Note: For passing secrets to reusable workflows, use the jobs..secrets # field instead. # (optional) @@ -831,7 +964,13 @@ services: # Network access control for AI engines using ecosystem identifiers and domain # allowlists. Supports wildcard patterns like '*.example.com' to match any -# subdomain. Controls web fetch and search capabilities. +# subdomain. Controls web fetch and search capabilities. IMPORTANT: For workflows +# that build/install/test code, always include the language ecosystem identifier +# alongside 'defaults' — 'defaults' alone only covers basic infrastructure, not +# package registries. Key ecosystem identifiers by runtime: 'dotnet' (.NET/NuGet), +# 'python' (pip/PyPI), 'node' (npm/yarn), 'go' (go modules), 'java' +# (Maven/Gradle), 'ruby' (Bundler), 'rust' (Cargo), 'swift' (Swift PM). Example: a +# .NET project needs network: { allowed: [defaults, dotnet] }. # (optional) # This field supports multiple formats (oneOf): @@ -849,7 +988,13 @@ network: allowed: [] # Array of Domain name or ecosystem identifier. Supports wildcards like # '*.example.com' (matches sub.example.com, deep.nested.example.com, and - # example.com itself) and ecosystem names like 'python', 'node'. + # example.com itself). Ecosystem identifiers by runtime: 'dotnet' (.NET/NuGet), + # 'python' (pip/PyPI), 'node' (npm/yarn), 'go' (go modules), 'java' + # (Maven/Gradle), 'ruby' (RubyGems), 'rust' (Cargo), 'swift' (Swift PM), 'php' + # (Composer), 'dart' (pub.dev), 'haskell' (Hackage), 'perl' (CPAN), 'containers' + # (Docker/GHCR), 'github' (GitHub domains), 'terraform' (HashiCorp), + # 'linux-distros' (apt/yum), 'playwright' (browser testing), 'defaults' (basic + # infrastructure). # List of blocked domains or ecosystem identifiers (e.g., 'python', 'node', # 'tracker.example.com'). Blocked domains take precedence over allowed domains. @@ -859,50 +1004,62 @@ network: # '*.example.com' (matches sub.example.com, deep.nested.example.com, and # example.com itself) and ecosystem names like 'python', 'node'. -# Sandbox configuration for AI engines. Controls coding agent sandbox (AWF or Sandbox -# Runtime) and MCP gateway. The MCP gateway is always enabled and cannot be -# disabled. +# Sandbox configuration for AI engines. Controls agent sandbox (AWF) and MCP +# gateway. The MCP gateway is always enabled and cannot be disabled. # (optional) # This field supports multiple formats (oneOf): +# Option 1: String format for sandbox type: 'default' for no sandbox, 'awf' for +# Agent Workflow Firewall. Note: Legacy 'srt' and 'sandbox-runtime' values are +# automatically migrated to 'awf' +sandbox: "default" + # Option 2: Object format for full sandbox configuration with agent and mcp # options sandbox: - # Agent sandbox type: 'awf' uses AWF (Agent Workflow Firewall), 'srt' uses - # Anthropic Sandbox Runtime, or false to disable coding agent sandbox. Defaults to 'awf' - # if not specified. Note: Disabling the coding agent sandbox (false) removes firewall - # protection but keeps the MCP gateway enabled. + # Legacy sandbox type field (use agent instead). Note: Legacy 'srt' and + # 'sandbox-runtime' values are automatically migrated to 'awf' + # (optional) + type: "default" + + # Agent sandbox type: 'awf' uses AWF (Agent Workflow Firewall), or false to + # disable agent sandbox. Defaults to 'awf' if not specified. Note: Disabling the + # agent sandbox (false) removes firewall protection but keeps the MCP gateway + # enabled. # (optional) # This field supports multiple formats (oneOf): - # Option 1: Set to false to disable the coding agent sandbox (firewall). Warning: This + # Option 1: Set to false to disable the agent sandbox (firewall). Warning: This # removes firewall protection but keeps the MCP gateway enabled. Not allowed in # strict mode. agent: true - # Option 2: Sandbox type: 'awf' for Agent Workflow Firewall, 'srt' for Sandbox - # Runtime + # Option 2: Sandbox type: 'awf' for Agent Workflow Firewall agent: "awf" # Option 3: Custom sandbox runtime configuration agent: # Agent identifier (replaces 'type' field in new format): 'awf' for Agent Workflow - # Firewall, 'srt' for Sandbox Runtime + # Firewall # (optional) id: "awf" - # Custom command to replace the default AWF or SRT installation. For AWF: 'docker - # run my-custom-awf-image'. For SRT: 'docker run my-custom-srt-wrapper' + # Legacy: Sandbox type to use (use 'id' instead) + # (optional) + type: "awf" + + # Custom command to replace the default AWF installation. For AWF: 'docker run + # my-custom-awf-image' # (optional) command: "example-value" - # Additional arguments to append to the command (applies to both AWF and SRT, for - # standard and custom commands) + # Additional arguments to append to the command (applies to AWF, for standard and + # custom commands) # (optional) args: [] # Array of strings - # Environment variables to set on the execution step (applies to both AWF and SRT) + # Environment variables to set on the execution step (applies to AWF) # (optional) env: {} @@ -914,8 +1071,8 @@ sandbox: mounts: [] # Array of Mount specification in format 'source:destination:mode' - # Custom Sandbox Runtime configuration (only applies when type is 'srt'). Note: - # Network configuration is controlled by the top-level 'network' field, not here. + # Custom sandbox runtime configuration. Note: Network configuration is controlled + # by the top-level 'network' field, not here. # (optional) config: # Filesystem access control configuration for the agent within the sandbox. @@ -946,6 +1103,43 @@ sandbox: # (optional) enableWeakerNestedSandbox: true + # Legacy custom Sandbox Runtime configuration (use agent.config instead). Note: + # Network configuration is controlled by the top-level 'network' field, not here. + # (optional) + config: + # Filesystem access control configuration for sandboxed workflows. Controls + # read/write permissions and path restrictions for file operations. + # (optional) + filesystem: + # Array of path patterns that deny read access in the sandboxed environment. Takes + # precedence over other read permissions. + # (optional) + denyRead: [] + # Array of strings + + # Array of path patterns that allow write access in the sandboxed environment. + # Paths outside these patterns are read-only. + # (optional) + allowWrite: [] + # Array of strings + + # Array of path patterns that deny write access in the sandboxed environment. + # Takes precedence over other write permissions. + # (optional) + denyWrite: [] + # Array of strings + + # When true, log sandbox violations without blocking execution. Useful for + # debugging and gradual enforcement of sandbox policies. + # (optional) + ignoreViolations: + {} + + # When true, allows nested sandbox processes to run with relaxed restrictions. + # Required for certain containerized tools that spawn subprocesses. + # (optional) + enableWeakerNestedSandbox: true + # MCP Gateway configuration for routing MCP server calls through a unified HTTP # gateway. Requires the 'mcp-gateway' feature flag to be enabled. Per MCP Gateway # Specification v1.0.0: Only container-based execution is supported. @@ -1058,14 +1252,15 @@ post-steps: [] # This field supports multiple formats (oneOf): # Option 1: Simple engine name: 'claude' (default, Claude Code), 'copilot' (GitHub -# Copilot CLI), 'codex' (OpenAI Codex CLI), or 'custom' (user-defined steps) +# Copilot CLI), 'codex' (OpenAI Codex CLI), or 'gemini' (Google Gemini CLI - +# experimental) engine: "claude" # Option 2: Extended engine configuration object with advanced options for model # selection, turn limiting, environment variables, and custom steps engine: # AI engine identifier: 'claude' (Claude Code), 'codex' (OpenAI Codex CLI), - # 'copilot' (GitHub Copilot CLI), or 'custom' (user-defined GitHub Actions steps) + # 'copilot' (GitHub Copilot CLI), or 'gemini' (Google Gemini CLI - experimental) id: "claude" # Optional version of the AI engine action (e.g., 'beta', 'stable', 20). Has @@ -1130,12 +1325,6 @@ engine: env: {} - # Custom GitHub Actions steps for extended engine configuration. Define additional - # workflow steps to run alongside AI processing. - # (optional) - steps: [] - # Array items: - # Custom error patterns for validating agent logs # (optional) error_patterns: [] @@ -1272,10 +1461,8 @@ tools: # (optional) owner: "example-value" - # Optional list of repositories to grant access to. Supports three modes: - # - ["*"] for org-wide access (all repos in the installation) - # - ["repo1", "repo2"] for specific repositories only - # - Empty/omit for current repository only (default) + # Optional list of repositories to grant access to (defaults to current repository + # if not specified) # (optional) repositories: [] # Array of strings @@ -1370,7 +1557,7 @@ tools: args: [] # Array of strings - # GH-AW as an MCP server for workflow introspection and analysis. + # GitHub Agentic Workflows MCP server for workflow introspection and analysis. # Provides tools for checking status, compiling workflows, downloading logs, and # auditing runs. # (optional) @@ -1681,6 +1868,10 @@ cache: # (optional) lookup-only: true + # Optional custom name for the cache step (overrides auto-generated name) + # (optional) + name: "My Workflow" + # Option 2: Multiple cache configurations cache: [] # Array items: object @@ -1802,19 +1993,53 @@ safe-outputs: # Option 2: Enable issue creation with default configuration create-issue: null - # Enable creation of GitHub Copilot coding agent tasks from workflow output. Allows - # workflows to spawn new agent sessions for follow-up work. + # Enable creation of GitHub Copilot coding agent tasks from workflow output. + # Allows workflows to spawn new agent sessions for follow-up work. # (optional) # This field supports multiple formats (oneOf): - # Enable creation of GitHub Copilot coding agent sessions from workflow output. Allows - # workflows to start interactive agent conversations. + # Option 1: DEPRECATED: Use 'create-agent-session' instead. Configuration for + # creating GitHub Copilot coding agent sessions from agentic workflow output using + # gh agent-task CLI. The main job does not need write permissions. + create-agent-task: + # Base branch for the agent session pull request. Defaults to the current branch + # or repository default branch. + # (optional) + base: "example-value" + + # Maximum number of agent sessions to create (default: 1) + # (optional) + max: 1 + + # Target repository in format 'owner/repo' for cross-repository agent session + # creation. Takes precedence over trial target repo settings. + # (optional) + target-repo: "example-value" + + # List of additional repositories in format 'owner/repo' that agent sessions can + # be created in. When specified, the agent can use a 'repo' field in the output to + # specify which repository to create the agent session 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) + github-token: "${{ secrets.GITHUB_TOKEN }}" + + # Option 2: Enable agent session creation with default configuration + create-agent-task: null + + # Enable creation of GitHub Copilot coding agent sessions from workflow output. + # Allows workflows to start interactive agent conversations. # (optional) # This field supports multiple formats (oneOf): - # Option 1: Configuration for creating GitHub Copilot coding agent sessions from agentic - # workflow output using gh agent-task CLI. The main job does not need write - # permissions. + # Option 1: Configuration for creating GitHub Copilot coding agent sessions from + # agentic workflow output using gh agent-task CLI. The main job does not need + # write permissions. create-agent-session: # Base branch for the agent session pull request. Defaults to the current branch # or repository default branch. @@ -2302,6 +2527,11 @@ safe-outputs: # (optional) github-token: "${{ secrets.GITHUB_TOKEN }}" + # If true, emit step summary messages instead of making GitHub API calls for this + # specific output type (preview mode) + # (optional) + staged: true + # Option 2: Enable pull request closing with default configuration close-pull-request: null @@ -2388,6 +2618,13 @@ safe-outputs: allowed-reasons: [] # Array of strings + # Controls whether the workflow requests discussions:write permission for + # add-comment. Default: true (includes discussions:write). Set to false if your + # GitHub App lacks Discussions permission to prevent 422 errors during token + # generation. + # (optional) + discussions: true + # Option 2: Enable issue comment creation with default configuration add-comment: null @@ -2397,11 +2634,13 @@ safe-outputs: # This field supports multiple formats (oneOf): # Option 1: Configuration for creating GitHub pull requests from agentic workflow - # output. Note: The max parameter is not supported for pull requests - workflows - # are always limited to creating 1 pull request per run. This design decision - # prevents workflow runs from creating excessive PRs and maintains repository - # integrity. + # output. Supports creating multiple PRs in a single run when max > 1. create-pull-request: + # Maximum number of pull requests to create (default: 1). Each PR requires + # distinct changes on a separate branch. + # (optional) + max: 1 + # Optional prefix for the pull request title # (optional) title-prefix: "example-value" @@ -2488,9 +2727,8 @@ safe-outputs: # (optional) auto-merge: true - # Base branch for the pull request. Defaults to the PR target branch - # (github.base_ref) in pull request contexts, or the triggering branch - # (github.ref_name) for push events. Useful for cross-repository PRs targeting + # Base branch for the pull request. Defaults to the workflow's branch + # (github.ref_name) if not specified. Useful for cross-repository PRs targeting # non-default branches (e.g., 'vnext', 'release/v1.0'). # (optional) base-branch: "example-value" @@ -2568,16 +2806,28 @@ safe-outputs: # (optional) max: 1 - # Target PR for the review: 'triggering' (default), '*' (any PR), or explicit PR number. - # Required when workflow is not triggered by a pull request (e.g. workflow_dispatch). + # Controls when AI-generated footer is added to the review body. Accepts boolean + # (true/false) or string ('always', 'none', 'if-body'). The 'if-body' mode is + # useful for clean approval reviews without body text. Defaults to 'always'. # (optional) - target: "triggering" + # This field supports multiple formats (oneOf): - # Controls whether AI-generated footer is added to the review body. When false, - # the footer is omitted. Defaults to true. - # (optional) + # Option 1: Controls whether AI-generated footer is added to the review body. true + # maps to 'always', false maps to 'none'. footer: true + # Option 2: Controls when AI-generated footer is added to the review body: + # 'always' (default), 'none' (never), or 'if-body' (only when review has body + # text). + footer: "always" + + # Target PR for the review: '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" + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -2708,6 +2958,13 @@ safe-outputs: allowed: [] # Array of strings + # Optional list of blocked label patterns (supports glob patterns like '~*', + # '*[bot]'). Labels matching these patterns will be rejected. Applied before + # allowed list filtering for security. + # (optional) + blocked: [] + # Array of strings + # Optional maximum number of labels to add (default: 3) # (optional) max: 1 @@ -2751,6 +3008,13 @@ safe-outputs: allowed: [] # Array of strings + # Optional list of blocked label patterns (supports glob patterns like '~*', + # '*[bot]'). Labels matching these patterns will be rejected. Applied before + # allowed list filtering for security. + # (optional) + blocked: [] + # Array of strings + # Optional maximum number of labels to remove (default: 3) # (optional) max: 1 @@ -2849,23 +3113,23 @@ safe-outputs: assign-to-agent: # Default agent name to assign (default: 'copilot') # (optional) - name: "copilot" + name: "My Workflow" # Default AI model to use for the agent (e.g., 'auto', 'claude-sonnet-4.5', # 'claude-opus-4.5', 'claude-opus-4.6', 'gpt-5.1-codex-max', 'gpt-5.2-codex'). # Defaults to 'auto' if not specified. # (optional) - model: "claude-opus-4.6" + model: "example-value" # Default custom agent ID to use when assigning custom agents. This is used for # specialized agent configurations beyond the standard Copilot agent. # (optional) - custom-agent: "agent-id" + custom-agent: "example-value" # Default custom instructions to provide to the agent. These instructions will # guide the agent's behavior when working on the task. # (optional) - custom-instructions: "Focus on performance optimization" + custom-instructions: "example-value" # Optional list of allowed agent names. If specified, only these agents can be # assigned. When configured, existing agent assignees not in the list are removed @@ -2895,7 +3159,7 @@ safe-outputs: # issue (specified by target-repo or the workflow's repository). This allows # issues and code to live in different repositories. # (optional) - pull-request-repo: "owner/repo" + pull-request-repo: "example-value" # List of additional repositories that pull requests can be created in beyond # pull-request-repo. Each entry should be in 'owner/repo' format. The repository @@ -2912,6 +3176,12 @@ safe-outputs: # (optional) ignore-if-error: true + # Base branch for pull request creation in the target repository. Defaults to the + # target repo's default branch. Only relevant when pull-request-repo is + # configured. + # (optional) + base-branch: "example-value" + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -2934,6 +3204,13 @@ safe-outputs: allowed: [] # Array of strings + # Optional list of blocked usernames or patterns (e.g., 'copilot', '*[bot]'). + # Users matching these patterns cannot be assigned. Supports exact matches and + # glob patterns. + # (optional) + blocked: [] + # Array of strings + # Optional maximum number of user assignments (default: 1) # (optional) max: 1 @@ -2948,6 +3225,11 @@ safe-outputs: # (optional) target-repo: "example-value" + # If true, unassign all current assignees before assigning new ones. Useful for + # reassigning issues from one user to another (default: false). + # (optional) + unassign-first: true + # GitHub token to use for this specific output type. Overrides global github-token # if specified. # (optional) @@ -2970,6 +3252,13 @@ safe-outputs: allowed: [] # Array of strings + # Optional list of blocked usernames or patterns (e.g., 'copilot', '*[bot]'). + # Users matching these patterns cannot be unassigned. Supports exact matches and + # glob patterns. + # (optional) + blocked: [] + # Array of strings + # Optional maximum number of unassignment operations (default: 1) # (optional) max: 1 @@ -3079,6 +3368,11 @@ safe-outputs: # (optional) target-repo: "example-value" + # Required prefix for issue title. Only issues with this title prefix can be + # updated. + # (optional) + title-prefix: "example-value" + # Option 2: Enable issue updating with default configuration update-issue: null @@ -3136,8 +3430,13 @@ safe-outputs: push-to-pull-request-branch: null # Option 2: Configuration for pushing changes to a specific branch from agentic - # workflow output + # workflow output. Supports pushing to multiple PRs in a single run when max > 1. push-to-pull-request-branch: + # Maximum number of push operations to perform (default: 1). Each push targets a + # different pull request branch. + # (optional) + max: 1 + # The branch to push changes to (defaults to 'triggering') # (optional) branch: "example-value" @@ -3173,6 +3472,11 @@ safe-outputs: # (optional) github-token: "${{ secrets.GITHUB_TOKEN }}" + # If true, emit step summary messages instead of making GitHub API calls for this + # specific output type (preview mode) + # (optional) + staged: true + # Enable AI agents to minimize (hide) comments on issues or pull requests based on # relevance, spam detection, or moderation rules. # (optional) @@ -3199,6 +3503,13 @@ safe-outputs: allowed-reasons: [] # Array of strings + # Controls whether the workflow requests discussions:write permission for + # hide-comment. Default: true (includes discussions:write). Set to false if your + # GitHub App lacks Discussions permission to prevent 422 errors during token + # generation. + # (optional) + discussions: true + # Dispatch workflow_dispatch events to other workflows. Used by orchestrators to # delegate work to worker workflows with controlled maximum dispatch count. # (optional) @@ -3419,10 +3730,10 @@ safe-outputs: # (optional) owner: "example-value" - # Optional: List of repositories to grant access to. Supports three modes: - # - ["*"] for org-wide access (all repos in the installation) - # - ["repo1", "repo2"] for specific repositories only - # - Empty/omit for current repository only (default) + # Optional: Comma or newline-separated list of repositories to grant access to. If + # owner is set and repositories is empty, access will be scoped to all + # repositories in the provided repository owner's installation. If owner and + # repositories are empty, access will be scoped to only the current repository. # (optional) repositories: [] # Array of strings @@ -3539,6 +3850,18 @@ safe-outputs: # (optional) detection-failure: "example-value" + # Custom footer template for agent failure tracking issues. Available + # placeholders: {workflow_name}, {run_url}. Default: '> Agent failure tracked by + # [{workflow_name}]({run_url})' + # (optional) + agent-failure-issue: "example-value" + + # Custom footer template for comments on agent failure tracking issues. Available + # placeholders: {workflow_name}, {run_url}. Default: '> Agent failure update from + # [{workflow_name}]({run_url})' + # (optional) + agent-failure-comment: "example-value" + # When enabled, workflow completion notifier creates a new comment instead of # editing the activation comment. Creates an append-only timeline of workflow # runs. Default: false @@ -3585,6 +3908,12 @@ safe-outputs: # (optional) footer: true + # When true, creates a parent '[agentics] Failed runs' issue that tracks all + # workflow failures as sub-issues. Helps organize failure tracking but may be + # unnecessary in smaller repositories. Defaults to false. + # (optional) + group-reports: true + # 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 @@ -3600,23 +3929,6 @@ secret-masking: # (optional) steps: [] -# Repository access roles required to trigger agentic workflows. Defaults to -# ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any -# authenticated user (⚠️ security consideration). -# (optional) -# This field supports multiple formats (oneOf): - -# Option 1: Allow any authenticated user to trigger the workflow (⚠️ disables -# permission checking entirely - use with caution) -roles: "all" - -# Option 2: List of repository permission levels that can trigger the workflow. -# Permission checks are automatically applied to potentially unsafe triggers. -roles: [] - # Array items: Repository permission level: 'admin' (full access), - # 'maintainer'/'maintain' (repository management), 'write' (push access), 'triage' - # (issue management) - # Allow list of bot identifiers that can trigger the workflow even if they don't # meet the required role permissions. When the actor is in this list, the bot must # be active (installed) on the repository to trigger the workflow. diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index 6cb16816ad..f713f882d9 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -360,7 +360,7 @@ safe-outputs: ### Issue Updates (`update-issue:`) -Updates issue status, title, or body. Only explicitly enabled fields can be updated. Status must be "open" or "closed". The `operation` field controls how body updates are applied: `append` (default), `prepend`, `replace`, or `replace-island`. +Updates issue status, title, or body. Only explicitly enabled fields can be updated. Status must be "open" or "closed". The `operation` field controls how body updates are applied: `append` (default), `prepend`, `replace`, or `replace-island`. Use `title-prefix` to restrict updates to issues whose titles start with a specific prefix. ```yaml wrap safe-outputs: @@ -368,6 +368,7 @@ safe-outputs: status: # enable status updates title: # enable title updates body: # enable body updates + title-prefix: "[bot] " # only update issues with this title prefix max: 3 # max updates (default: 1) target: "*" # "triggering" (default), "*", or number target-repo: "owner/repo" # cross-repository @@ -378,6 +379,8 @@ safe-outputs: When using `target: "*"`, the agent must provide `issue_number` or `item_number` in the output to identify which issue to update. +**Title Prefix**: When `title-prefix` is set, the update is rejected if the target issue's current title does not start with the specified prefix. This ensures agents can only modify issues that have been explicitly tagged for automated updates. + **Operation Types** (for body updates): - `append` (default): Adds content to the end with separator and attribution