From 553ff956b604749b3d688be14a3c4071ea42dfa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:57:36 +0000 Subject: [PATCH 1/2] Initial plan From 3584dfff63b5f7fac77a1bf556f403b13435cd31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 06:06:49 +0000 Subject: [PATCH 2/2] Clarify safe-jobs internal implementation vs safe-outputs.jobs user API - Updated code comments in pkg/workflow/safe_jobs.go to clearly distinguish internal implementation details from user-facing API - Added comprehensive architecture documentation at the top of safe_jobs.go - Updated safe_jobs_test.go comments to clarify internal testing approach - Enhanced documentation in docs/src/content/docs/reference/safe-outputs.md to emphasize user-facing API - Updated compilation-process.md to use correct user-facing syntax (safe-outputs.jobs:) - Added $comment to schema (pkg/parser/schemas/main_workflow_schema.json) documenting internal implementation detail - Rebuilt binary with updated embedded schema The top-level "safe-jobs" key is correctly an internal implementation detail used only for parsing. Users should use "safe-outputs.jobs:" which is fully supported, documented, and defined in the schema. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../docs/reference/compilation-process.md | 2 +- .../content/docs/reference/safe-outputs.md | 2 +- pkg/parser/schemas/main_workflow_schema.json | 25 ++++++------- pkg/workflow/safe_jobs.go | 36 ++++++++++++++++--- pkg/workflow/safe_jobs_test.go | 8 +++-- 5 files changed, 52 insertions(+), 21 deletions(-) diff --git a/docs/src/content/docs/reference/compilation-process.md b/docs/src/content/docs/reference/compilation-process.md index 4c7aef6923..da488f66ac 100644 --- a/docs/src/content/docs/reference/compilation-process.md +++ b/docs/src/content/docs/reference/compilation-process.md @@ -267,7 +267,7 @@ Pre-activation runs checks sequentially. Any failure sets `activated=false`, pre **Maintainability**: Use imports for shared configuration, document complex workflows with `description:`, compile frequently during development, version control lock files and action pins (`.github/aw/actions-lock.json`). -**Performance**: Enable caching (`cache:` and `cache-memory:`), minimize imports to essentials, optimize tool configurations with restricted `allowed:` lists, use safe-jobs for custom logic. +**Performance**: Enable caching (`cache:` and `cache-memory:`), minimize imports to essentials, optimize tool configurations with restricted `allowed:` lists, use custom safe output jobs (`safe-outputs.jobs:`) for custom logic. **Debugging**: Enable verbose logging (`--verbose`), check job dependency graphs in headers, inspect artifacts and firewall logs (`gh aw logs`), validate without file generation (`--no-emit`). diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index caf1ae84d1..01f4aba5e1 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -82,7 +82,7 @@ The agent requests issue creation; a separate job with `issues: write` creates i Create custom post-processing jobs registered as Model Context Protocol (MCP) tools. Support standard GitHub Actions properties and auto-access agent output via `$GH_AW_AGENT_OUTPUT`. See [Custom Safe Output Jobs](/gh-aw/guides/custom-safe-outputs/). > [!NOTE] -> **Internal Implementation**: Custom safe output jobs are internally referred to as "safe-jobs" in the compiler code (`pkg/workflow/safe_jobs.go`), but they are user-facing only through the `safe-outputs.jobs:` configuration. The top-level `safe-jobs:` key is deprecated and not supported. +> **Internal Implementation Note**: Custom safe output jobs are internally referred to as "safe-jobs" in the compiler code (`pkg/workflow/safe_jobs.go`) for parsing purposes. However, users should ONLY configure these jobs via the `safe-outputs.jobs:` frontmatter syntax. The top-level `safe-jobs:` key is an internal implementation detail and is not supported in user workflows. ### Issue Creation (`create-issue:`) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 3ed640e89c..fe10748b23 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2504,7 +2504,7 @@ [ { "name": "Verify Post-Steps Execution", - "run": "echo \"✅ Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n" + "run": "echo \"\u2705 Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n" }, { "name": "Upload Test Results", @@ -5647,7 +5647,8 @@ "additionalProperties": false } }, - "additionalProperties": false + "additionalProperties": false, + "$comment": "INTERNAL IMPLEMENTATION: This user-facing 'safe-outputs.jobs:' configuration is transformed internally into a 'safe-jobs' key for parsing by the compiler (see pkg/workflow/safe_jobs.go). The top-level 'safe-jobs:' key is NOT supported in user workflows and is used only internally." }, "messages": { "type": "object", @@ -5675,8 +5676,8 @@ }, "staged-title": { "type": "string", - "description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '🎭 Preview: {operation}'", - "examples": ["🎭 Preview: {operation}", "## Staged Mode: {operation}"] + "description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '\ud83c\udfad Preview: {operation}'", + "examples": ["\ud83c\udfad Preview: {operation}", "## Staged Mode: {operation}"] }, "staged-description": { "type": "string", @@ -5690,18 +5691,18 @@ }, "run-success": { "type": "string", - "description": "Custom message template for successful workflow completion. Available placeholders: {workflow_name}, {run_url}. Default: '✅ Agentic [{workflow_name}]({run_url}) completed successfully.'", - "examples": ["✅ Agentic [{workflow_name}]({run_url}) completed successfully.", "✅ [{workflow_name}]({run_url}) finished."] + "description": "Custom message template for successful workflow completion. Available placeholders: {workflow_name}, {run_url}. Default: '\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.'", + "examples": ["\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.", "\u2705 [{workflow_name}]({run_url}) finished."] }, "run-failure": { "type": "string", - "description": "Custom message template for failed workflow. Available placeholders: {workflow_name}, {run_url}, {status}. Default: '❌ Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.'", - "examples": ["❌ Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", "❌ [{workflow_name}]({run_url}) {status}."] + "description": "Custom message template for failed workflow. Available placeholders: {workflow_name}, {run_url}, {status}. Default: '\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.'", + "examples": ["\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", "\u274c [{workflow_name}]({run_url}) {status}."] }, "detection-failure": { "type": "string", - "description": "Custom message template for detection job failure. Available placeholders: {workflow_name}, {run_url}. Default: '⚠️ Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.'", - "examples": ["⚠️ Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", "⚠️ Detection job failed in [{workflow_name}]({run_url})."] + "description": "Custom message template for detection job failure. Available placeholders: {workflow_name}, {run_url}. Default: '\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.'", + "examples": ["\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", "\u26a0\ufe0f Detection job failed in [{workflow_name}]({run_url})."] }, "append-only-comments": { "type": "boolean", @@ -5781,12 +5782,12 @@ "additionalProperties": false }, "roles": { - "description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (⚠️ security consideration).", + "description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (\u26a0\ufe0f security consideration).", "oneOf": [ { "type": "string", "enum": ["all"], - "description": "Allow any authenticated user to trigger the workflow (⚠️ disables permission checking entirely - use with caution)" + "description": "Allow any authenticated user to trigger the workflow (\u26a0\ufe0f disables permission checking entirely - use with caution)" }, { "type": "array", diff --git a/pkg/workflow/safe_jobs.go b/pkg/workflow/safe_jobs.go index f13f407d73..bdae81eaae 100644 --- a/pkg/workflow/safe_jobs.go +++ b/pkg/workflow/safe_jobs.go @@ -9,6 +9,24 @@ import ( "github.com/githubnext/gh-aw/pkg/stringutil" ) +// Package workflow implements custom safe output jobs for GitHub Agentic Workflows. +// +// ARCHITECTURE OVERVIEW: +// +// User-Facing API: +// - Users configure custom safe output jobs via "safe-outputs.jobs:" in workflow frontmatter +// - This is documented in docs/src/content/docs/reference/safe-outputs.md +// - Schema definition in pkg/parser/schemas/main_workflow_schema.json under safe-outputs.properties.jobs +// +// Internal Implementation: +// - The internal "safe-jobs" key is used ONLY for parsing and is NOT user-facing +// - extractSafeJobsFromFrontmatter() reads "safe-outputs.jobs:" and transforms it for parsing +// - parseSafeJobsConfig() expects the internal "safe-jobs" key format +// - The top-level "safe-jobs:" key is NOT supported in user workflows +// +// This separation allows the code to use a consistent internal format while maintaining +// a clean user-facing API under the safe-outputs namespace. + var safeJobsLog = logger.New("workflow:safe_jobs") // SafeJobConfig defines a safe job configuration with GitHub Actions job properties @@ -35,8 +53,13 @@ func HasSafeJobsEnabled(safeJobs map[string]*SafeJobConfig) bool { } // parseSafeJobsConfig parses safe-jobs configuration from a frontmatter map. -// This is an internal helper function that expects a map with a "safe-jobs" key. -// User workflows should use "safe-outputs.jobs" syntax; the top-level "safe-jobs" key is NOT supported. +// +// INTERNAL USE ONLY: This is an internal helper function that expects a map with a "safe-jobs" key +// for parsing purposes. This key is NOT user-facing and NOT in the schema. +// +// Users should configure custom safe output jobs via the "safe-outputs.jobs:" frontmatter syntax, +// which is then extracted by extractSafeJobsFromFrontmatter() and transformed into the internal +// "safe-jobs" format for parsing. The top-level "safe-jobs:" key is not supported in user workflows. func (c *Compiler) parseSafeJobsConfig(frontmatter map[string]any) map[string]*SafeJobConfig { safeJobsSection, exists := frontmatter["safe-jobs"] if !exists { @@ -292,8 +315,13 @@ func (c *Compiler) buildSafeJobs(data *WorkflowData, threatDetectionEnabled bool return safeJobNames, nil } -// extractSafeJobsFromFrontmatter extracts safe-jobs configuration from frontmatter. -// Only checks the safe-outputs.jobs location. The old top-level "safe-jobs" syntax is NOT supported. +// extractSafeJobsFromFrontmatter extracts custom safe output jobs from the user-facing +// "safe-outputs.jobs:" frontmatter configuration and transforms them into the internal +// SafeJobConfig format. +// +// USER-FACING API: Users configure custom safe output jobs via "safe-outputs.jobs:" in frontmatter. +// INTERNAL DETAIL: The extracted configuration is temporarily wrapped with a "safe-jobs" key +// for internal parsing by parseSafeJobsConfig(). This internal key is not user-facing. func extractSafeJobsFromFrontmatter(frontmatter map[string]any) map[string]*SafeJobConfig { // Check location: safe-outputs.jobs if safeOutputs, exists := frontmatter["safe-outputs"]; exists { diff --git a/pkg/workflow/safe_jobs_test.go b/pkg/workflow/safe_jobs_test.go index 4ef24ffad0..ee1df45bbb 100644 --- a/pkg/workflow/safe_jobs_test.go +++ b/pkg/workflow/safe_jobs_test.go @@ -8,9 +8,11 @@ import ( func TestParseSafeJobsConfig(t *testing.T) { c := NewCompiler(false, "", "test") - // Test parseSafeJobsConfig internal function which expects a "safe-jobs" key. - // Note: User workflows should use "safe-outputs.jobs" syntax; this test validates - // the internal parsing logic used by extractSafeJobsFromFrontmatter and safe_outputs.go. + // Test parseSafeJobsConfig internal function which expects a "safe-jobs" key for parsing. + // NOTE: The "safe-jobs" key is INTERNAL ONLY and not user-facing. Users configure custom + // safe output jobs via "safe-outputs.jobs:" in frontmatter. This test validates the internal + // parsing logic used by extractSafeJobsFromFrontmatter() which transforms the user-facing + // "safe-outputs.jobs:" configuration into the internal "safe-jobs" format. frontmatter := map[string]any{ "safe-jobs": map[string]any{ "deploy": map[string]any{