diff --git a/.github/workflows/dependabot-burner.lock.yml b/.github/workflows/dependabot-burner.lock.yml index 71bd6568a3..19aecf8c62 100644 --- a/.github/workflows/dependabot-burner.lock.yml +++ b/.github/workflows/dependabot-burner.lock.yml @@ -1304,7 +1304,8 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"expires\":48,\"max\":5},\"create_project_status_update\":{\"max\":1},\"missing_data\":{},\"missing_tool\":{},\"update_project\":{\"max\":10,\"project\":\"https://github.com/orgs/githubnext/projects/144\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"expires\":48,\"max\":5},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG: "{\"create_project_status_update\":{\"max\":1},\"update_project\":{\"max\":10,\"project\":\"https://github.com/orgs/githubnext/projects/144\"}}" GH_AW_PROJECT_URL: "https://github.com/orgs/githubnext/projects/144" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/security-alert-burndown.lock.yml b/.github/workflows/security-alert-burndown.lock.yml index c861667be2..76c1e83676 100644 --- a/.github/workflows/security-alert-burndown.lock.yml +++ b/.github/workflows/security-alert-burndown.lock.yml @@ -1183,7 +1183,8 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"max\":1},\"missing_data\":{},\"missing_tool\":{},\"update_project\":{\"max\":100,\"project\":\"https://github.com/orgs/githubnext/projects/144\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG: "{\"update_project\":{\"max\":100,\"project\":\"https://github.com/orgs/githubnext/projects/144\"}}" GH_AW_ASSIGN_COPILOT: "true" GH_AW_PROJECT_URL: "https://github.com/orgs/githubnext/projects/144" with: diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index d2f230a202..1f53bd1d35 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -1656,7 +1656,8 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-project\"],\"target-repo\":\"github-agentic-workflows/demo-repository\"},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1,\"target-repo\":\"github-agentic-workflows/demo-repository\"},\"create_project_status_update\":{\"github-token\":\"${{ secrets.SMOKE_PROJECT_GITHUB_TOKEN }}\",\"max\":1,\"project\":\"https://github.com/orgs/github-agentic-workflows/projects/1\"},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke-project\"],\"target-repo\":\"github-agentic-workflows/demo-repository\"},\"update_project\":{\"github-token\":\"${{ secrets.SMOKE_PROJECT_GITHUB_TOKEN }}\",\"max\":20,\"project\":\"https://github.com/orgs/github-agentic-workflows/projects/1\",\"views\":[{\"name\":\"Smoke Test Board\",\"layout\":\"board\",\"filter\":\"is:open\"},{\"name\":\"Smoke Test Table\",\"layout\":\"table\"}]}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-project\"],\"target-repo\":\"github-agentic-workflows/demo-repository\"},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1,\"target-repo\":\"github-agentic-workflows/demo-repository\"},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke-project\"],\"target-repo\":\"github-agentic-workflows/demo-repository\"}}" + GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG: "{\"create_project_status_update\":{\"github-token\":\"${{ secrets.SMOKE_PROJECT_GITHUB_TOKEN }}\",\"max\":1,\"project\":\"https://github.com/orgs/github-agentic-workflows/projects/1\"},\"update_project\":{\"github-token\":\"${{ secrets.SMOKE_PROJECT_GITHUB_TOKEN }}\",\"max\":20,\"project\":\"https://github.com/orgs/github-agentic-workflows/projects/1\",\"views\":[{\"name\":\"Smoke Test Board\",\"layout\":\"board\",\"filter\":\"is:open\"},{\"name\":\"Smoke Test Table\",\"layout\":\"table\"}]}}" GH_AW_PROJECT_URL: "https://github.com/orgs/github-agentic-workflows/projects/1" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-project-url-default.lock.yml b/.github/workflows/test-project-url-default.lock.yml index d8da718246..37bddd33d6 100644 --- a/.github/workflows/test-project-url-default.lock.yml +++ b/.github/workflows/test-project-url-default.lock.yml @@ -1187,7 +1187,8 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_project_status_update\":{\"max\":1,\"project\":\"https://github.com/orgs/\\u003cORG\\u003e/projects/\\u003cNUMBER\\u003e\"},\"missing_data\":{},\"missing_tool\":{},\"update_project\":{\"max\":5,\"project\":\"https://github.com/orgs/\\u003cORG\\u003e/projects/\\u003cNUMBER\\u003e\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG: "{\"create_project_status_update\":{\"max\":1,\"project\":\"https://github.com/orgs/\\u003cORG\\u003e/projects/\\u003cNUMBER\\u003e\"},\"update_project\":{\"max\":5,\"project\":\"https://github.com/orgs/\\u003cORG\\u003e/projects/\\u003cNUMBER\\u003e\"}}" GH_AW_PROJECT_URL: "https://github.com/orgs//projects/" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/pkg/workflow/compiler_safe_outputs_config.go b/pkg/workflow/compiler_safe_outputs_config.go index 48ec03b897..05181139a6 100644 --- a/pkg/workflow/compiler_safe_outputs_config.go +++ b/pkg/workflow/compiler_safe_outputs_config.go @@ -93,8 +93,8 @@ func (b *handlerConfigBuilder) Build() map[string]any { // handlerBuilder is a function that builds a handler config from SafeOutputsConfig type handlerBuilder func(*SafeOutputsConfig) map[string]any -// handlerRegistry maps handler names to their builder functions -var handlerRegistry = map[string]handlerBuilder{ +// regularHandlerRegistry maps regular (non-project) handler names to their builder functions +var regularHandlerRegistry = map[string]handlerBuilder{ "create_issue": func(cfg *SafeOutputsConfig) map[string]any { if cfg.CreateIssues == nil { return nil @@ -422,8 +422,11 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("github-token", c.GitHubToken). Build() }, - // Note: create_project, update_project and create_project_status_update are handled by the unified handler, - // not the separate project handler manager, so they are included in this registry. +} + +// projectHandlerRegistry maps project handler names to their builder functions +// These handlers require GH_AW_PROJECT_GITHUB_TOKEN and are loaded separately +var projectHandlerRegistry = map[string]handlerBuilder{ "create_project": func(cfg *SafeOutputsConfig) map[string]any { if cfg.CreateProjects == nil { return nil @@ -479,34 +482,55 @@ func (c *Compiler) addHandlerManagerConfigEnvVar(steps *[]string, data *Workflow } compilerSafeOutputsConfigLog.Print("Building handler manager configuration for safe-outputs") - config := make(map[string]map[string]any) - // Build configuration for each handler using the registry - for handlerName, builder := range handlerRegistry { + // Build regular handlers config + regularConfig := make(map[string]map[string]any) + for handlerName, builder := range regularHandlerRegistry { + handlerConfig := builder(data.SafeOutputs) + if handlerConfig != nil { + compilerSafeOutputsConfigLog.Printf("Adding regular handler configuration: %s", handlerName) + regularConfig[handlerName] = handlerConfig + } + } + + // Build project handlers config + projectConfig := make(map[string]map[string]any) + for handlerName, builder := range projectHandlerRegistry { handlerConfig := builder(data.SafeOutputs) - // Include handler if: - // 1. It returns a non-nil config (explicitly enabled, even if empty) - // 2. For auto-enabled handlers, include even with empty config if handlerConfig != nil { - compilerSafeOutputsConfigLog.Printf("Adding %s handler configuration", handlerName) - config[handlerName] = handlerConfig + compilerSafeOutputsConfigLog.Printf("Adding project handler configuration: %s", handlerName) + projectConfig[handlerName] = handlerConfig } } - // Only add the env var if there are handlers to configure - if len(config) > 0 { - compilerSafeOutputsConfigLog.Printf("Marshaling handler config with %d handlers", len(config)) - configJSON, err := json.Marshal(config) + // Add regular handlers config env var + if len(regularConfig) > 0 { + compilerSafeOutputsConfigLog.Printf("Marshaling regular handler config with %d handlers", len(regularConfig)) + configJSON, err := json.Marshal(regularConfig) if err != nil { - consolidatedSafeOutputsLog.Printf("Failed to marshal handler config: %v", err) + consolidatedSafeOutputsLog.Printf("Failed to marshal regular handler config: %v", err) return } - // Escape the JSON for YAML (handle quotes and special chars) configStr := string(configJSON) *steps = append(*steps, fmt.Sprintf(" GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: %q\n", configStr)) - compilerSafeOutputsConfigLog.Printf("Added handler config env var: size=%d bytes", len(configStr)) + compilerSafeOutputsConfigLog.Printf("Added regular handler config env var: size=%d bytes", len(configStr)) + } else { + compilerSafeOutputsConfigLog.Print("No regular handlers configured, skipping regular config env var") + } + + // Add project handlers config env var + if len(projectConfig) > 0 { + compilerSafeOutputsConfigLog.Printf("Marshaling project handler config with %d handlers", len(projectConfig)) + configJSON, err := json.Marshal(projectConfig) + if err != nil { + consolidatedSafeOutputsLog.Printf("Failed to marshal project handler config: %v", err) + return + } + configStr := string(configJSON) + *steps = append(*steps, fmt.Sprintf(" GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG: %q\n", configStr)) + compilerSafeOutputsConfigLog.Printf("Added project handler config env var: size=%d bytes", len(configStr)) } else { - compilerSafeOutputsConfigLog.Print("No handlers configured, skipping config env var") + compilerSafeOutputsConfigLog.Print("No project handlers configured, skipping project config env var") } } diff --git a/pkg/workflow/create_project_status_update_handler_config_test.go b/pkg/workflow/create_project_status_update_handler_config_test.go index cd62faa5e9..5a59abb384 100644 --- a/pkg/workflow/create_project_status_update_handler_config_test.go +++ b/pkg/workflow/create_project_status_update_handler_config_test.go @@ -50,11 +50,11 @@ Test workflow compiledStr := string(compiledContent) - // Find the GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG line - require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG", - "Expected GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG in compiled workflow") + // Project handlers should be in GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG + require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG", + "Expected GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG in compiled workflow") - // Verify create_project_status_update is in the handler config + // Verify create_project_status_update is in the project handler config require.Contains(t, compiledStr, "create_project_status_update", "Expected create_project_status_update in handler config") @@ -101,11 +101,11 @@ Test workflow compiledStr := string(compiledContent) - // Find the GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG line - require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG", - "Expected GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG in compiled workflow") + // Project handlers should be in GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG + require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG", + "Expected GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG in compiled workflow") - // Verify create_project_status_update is in the handler config + // Verify create_project_status_update is in the project handler config require.Contains(t, compiledStr, "create_project_status_update", "Expected create_project_status_update in handler config") @@ -165,8 +165,23 @@ Test workflow compiledStr := string(compiledContent) - // Extract main handler config JSON + // Extract project handler config JSON lines := strings.Split(compiledStr, "\n") + var projectConfigJSON string + for _, line := range lines { + if strings.Contains(line, "GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG:") { + parts := strings.SplitN(line, "GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG:", 2) + if len(parts) == 2 { + projectConfigJSON = strings.TrimSpace(parts[1]) + projectConfigJSON = strings.Trim(projectConfigJSON, "\"") + projectConfigJSON = strings.ReplaceAll(projectConfigJSON, "\\\"", "\"") + } + } + } + + require.NotEmpty(t, projectConfigJSON, "Failed to extract GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG JSON") + + // Verify create-issue is in the regular handler config var mainConfigJSON string for _, line := range lines { if strings.Contains(line, "GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG:") { @@ -178,20 +193,16 @@ Test workflow } } } - require.NotEmpty(t, mainConfigJSON, "Failed to extract GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG JSON") - - // Verify create_issue is in the main handler config assert.Contains(t, mainConfigJSON, "create_issue", "Expected create_issue in main handler config") - // Verify create_project_status_update is also in the main handler config - // (as of recent changes, it's handled by the unified handler, not a separate project handler step) - assert.Contains(t, mainConfigJSON, "create_project_status_update", - "Expected create_project_status_update in main handler config") + // Verify create_project_status_update is in the project handler config + assert.Contains(t, projectConfigJSON, "create_project_status_update", + "Expected create_project_status_update in project handler config") // Verify max value is correct - assert.Contains(t, mainConfigJSON, `"max":2`, + assert.Contains(t, projectConfigJSON, `"max":2`, "Expected max:2 in create_project_status_update handler config") } @@ -229,7 +240,7 @@ Test workflow compiledStr := string(compiledContent) - // Verify project URL is in the handler config - require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG", "Expected main handler config") + // Verify project URL is in the project handler config + require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG", "Expected project handler config") require.Contains(t, compiledStr, "https://github.com/orgs/nonexistent-test-org-67890/projects/88888", "Expected project URL in handler config") } diff --git a/pkg/workflow/update_project_handler_config_test.go b/pkg/workflow/update_project_handler_config_test.go index 00b95ccc8b..202fd0d49e 100644 --- a/pkg/workflow/update_project_handler_config_test.go +++ b/pkg/workflow/update_project_handler_config_test.go @@ -44,8 +44,9 @@ Test workflow require.NoError(t, err, "Failed to read compiled output") compiledStr := string(compiledContent) - // Note: update-project is now in the main handler config, not the project handler config - require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG", "Expected main handler config env var") + // Project handlers (update_project, create_project, create_project_status_update) + // should be in GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG, not the regular handler config + require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG", "Expected project handler config env var") require.Contains(t, compiledStr, "update_project", "Expected update_project in handler config") // field_definitions uses underscore naming in the JSON config passed to JS @@ -86,10 +87,8 @@ Test workflow compiledStr := string(compiledContent) - // Note: Since update-project is no longer in the project handler manager, - // GH_AW_PROJECT_URL is not set when only update-project is configured. - // update-project is now handled by the unified handler, which doesn't set GH_AW_PROJECT_URL. - // The project URL is passed as part of the handler config instead. - require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG", "Expected main handler config") + // Project handlers (update_project, create_project, create_project_status_update) + // should be in GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG + require.Contains(t, compiledStr, "GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG", "Expected project handler config") require.Contains(t, compiledStr, "https://github.com/orgs/nonexistent-test-org-12345/projects/99999", "Expected project URL in handler config") }