diff --git a/docs/adr/26587-pre-agent-cache-memory-working-tree-sanitization.md b/docs/adr/26587-pre-agent-cache-memory-working-tree-sanitization.md index 985a764b2cc..e8c55522c02 100644 --- a/docs/adr/26587-pre-agent-cache-memory-working-tree-sanitization.md +++ b/docs/adr/26587-pre-agent-cache-memory-working-tree-sanitization.md @@ -1,7 +1,7 @@ # ADR-26587: Pre-Agent Cache-Memory Working-Tree Sanitization **Date**: 2026-04-16 -**Status**: Draft +**Status**: Accepted **Deciders**: pelikhan, Copilot --- @@ -77,6 +77,7 @@ The existing integrity and threat-detection pipeline could be trusted to catch m 2. Extensions in `GH_AW_ALLOWED_EXTENSIONS` **MUST** include the leading dot (e.g. `.json`, `.md`); files with no extension are treated as if their extension is an empty string. 3. When `GH_AW_ALLOWED_EXTENSIONS` is absent or empty, implementations **MUST NOT** remove any files based on extension (all extensions are allowed). 4. Implementations **SHOULD** log each removed file path and its extension, and **SHOULD** log a final count of removed files. +5. If `GH_AW_ALLOWED_EXTENSIONS` contains any non-empty entry without a leading dot, implementations **SHOULD** emit a warning identifying the malformed entry before continuing with any fallback or filtering behavior. ### Go-to-Shell Configuration Contract @@ -90,4 +91,4 @@ An implementation is considered conformant with this ADR if it satisfies all **M --- -*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/24514620900) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* +*Accepted on 2026-07-19 after validating the Go-side env-var contract and the shell-script sanitization tests.* diff --git a/docs/adr/26666-add-pre-agent-steps-support.md b/docs/adr/26666-add-pre-agent-steps-support.md index f235237eb9e..a8222f9a27e 100644 --- a/docs/adr/26666-add-pre-agent-steps-support.md +++ b/docs/adr/26666-add-pre-agent-steps-support.md @@ -1,7 +1,7 @@ # ADR-26666: Introduce `pre-agent-steps` as a Distinct Workflow Extension Point **Date**: 2026-04-16 -**Status**: Draft +**Status**: Accepted **Deciders**: pelikhan, Copilot --- @@ -10,11 +10,11 @@ ### Context -The gh-aw workflow compiler translates markdown workflow files into GitHub Actions YAML. It already supports two custom step extension points: `pre-steps` (injected before the checkout step, very early in the agent job) and `post-steps` (injected after AI engine execution). A gap exists for steps that must run after all framework initialization — including checkout, credential setup, and environment preparation — but *immediately before* the AI engine starts. Use cases include final context preparation scripts, last-moment environment variable injection, and validations that depend on checked-out code being available. +The gh-aw workflow compiler translates markdown workflow files into GitHub Actions YAML. It already supports two custom step extension points: `pre-steps` (injected before the checkout step, very early in the agent job) and `post-steps` (injected after AI engine execution). A gap exists for steps that must run after checkout, checkout cleanup, and PR/base restoration, but before MCP startup and later engine-owned runtime setup. Use cases include final context preparation scripts, last-moment environment variable injection, and validations that depend on checked-out code being available. ### Decision -We will add a new `pre-agent-steps` frontmatter field that is injected into the compiled GitHub Actions YAML immediately before the engine execution step, after all setup steps (including the CLI proxy start step). Like `pre-steps`, `pre-agent-steps` participates in the import merge system: imported `pre-agent-steps` are prepended before the main workflow's `pre-agent-steps`, preserving a deterministic, layered execution order. The existing `validateStepsSecrets` infrastructure is extended to cover the new field, applying identical secret expression restrictions. +We will add a new `pre-agent-steps` frontmatter field that is injected into the compiled GitHub Actions YAML after checkout cleanup and trusted base-restore steps, but before MCP startup and the later engine execution sequence. Like `pre-steps`, `pre-agent-steps` participates in the import merge system: imported `pre-agent-steps` are prepended before the main workflow's `pre-agent-steps`, preserving a deterministic, layered execution order. The existing `validateStepsSecrets` infrastructure is extended to cover the new field, applying identical secret expression restrictions. ### Alternatives Considered @@ -61,7 +61,7 @@ Users could approximate "run before engine" behaviour by placing steps in `post- ### Compilation and Placement 1. Implementations **MUST** inject all resolved `pre-agent-steps` into the compiled GitHub Actions YAML immediately before the engine execution step. -2. `pre-agent-steps` **MUST** be placed after all framework setup steps (including the CLI proxy start step) and **MUST NOT** be placed before any framework-owned initialization steps. +2. `pre-agent-steps` **MUST** be placed after checkout cleanup and any trusted PR/base restoration steps, and **MUST** be placed before MCP startup and the later engine execution sequence. 3. If no `pre-agent-steps` are defined (neither in the main workflow nor in any imports), the compiler **MUST NOT** emit any placeholder or empty steps block. ### Import Merge Ordering @@ -81,8 +81,8 @@ Users could approximate "run before engine" behaviour by placing steps in `post- ### Conformance -An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. Specifically: `pre-agent-steps` are injected immediately before the engine execution step (not before setup steps), imports are merged in prepend order, secret validation is applied, and action pinning is applied. Failure to meet any **MUST** or **MUST NOT** requirement constitutes non-conformance. +An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. Specifically: `pre-agent-steps` are injected after checkout cleanup / trusted restore steps and before MCP startup and engine execution, imports are merged in prepend order, secret validation is applied, and action pinning is applied. Failure to meet any **MUST** or **MUST NOT** requirement constitutes non-conformance. --- -*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/24519930482) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* +*Accepted on 2026-07-19 after validating compiler ordering and adding a conformance test for workflows that define only `pre-agent-steps`.* diff --git a/docs/adr/26688-configurable-expiration-for-action-failure-issues.md b/docs/adr/26688-configurable-expiration-for-action-failure-issues.md index b72c9aafe75..e472c687bc9 100644 --- a/docs/adr/26688-configurable-expiration-for-action-failure-issues.md +++ b/docs/adr/26688-configurable-expiration-for-action-failure-issues.md @@ -1,7 +1,7 @@ # ADR-26688: Configurable Expiration for Action Failure Issues via aw.json **Date**: 2026-04-16 -**Status**: Draft +**Status**: Accepted **Deciders**: pelikhan, Copilot --- @@ -43,7 +43,7 @@ Setting `GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS` as a GitHub Actions repositor #### Neutral - The `DefaultActionFailureIssueExpiresHours` constant (`24 * 7 = 168`) is now an exported symbol, making it accessible to other packages that may need the canonical default. -- Documentation in `docs/src/content/docs/guides/ephemerals.md` is updated to describe the new field alongside the existing `runs_on` maintenance option. +- Documentation in `docs/src/content/docs/reference/ephemerals.md` is updated to describe the new field alongside the existing `runs_on` maintenance option. --- @@ -60,7 +60,7 @@ Setting `GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS` as a GitHub Actions repositor ### Compilation and Environment Variable Propagation 1. The workflow compiler **MUST** read `action_failure_issue_expires` from `aw.json` (via `LoadRepoConfig`) during `buildConclusionJob` and inject the resolved value as the `GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS` environment variable into the generated conclusion-job YAML. -2. If `aw.json` cannot be loaded or parsed, the compiler **MUST** log a warning and fall back to the default value of `168`; it **MUST NOT** fail the build. +2. If `.github/workflows/aw.json` exists but cannot be loaded, parsed, or validated, the compiler **MUST** emit a warning that identifies the file path and the fallback value (`168`) before continuing; it **MUST NOT** fail the build. 3. The injected environment variable value **MUST** be a positive integer string representing hours. ### Runtime Handling @@ -75,4 +75,4 @@ An implementation is considered conformant with this ADR if it satisfies all **M --- -*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/24522565038) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* +*Accepted on 2026-07-19 after validating the `aw.json`-driven expiration path and documenting the fallback warning behavior.* diff --git a/docs/adr/26693-reaction-target-scoping-and-union-activation-permission-derivation.md b/docs/adr/26693-reaction-target-scoping-and-union-activation-permission-derivation.md index 351c172c793..7d0963a2668 100644 --- a/docs/adr/26693-reaction-target-scoping-and-union-activation-permission-derivation.md +++ b/docs/adr/26693-reaction-target-scoping-and-union-activation-permission-derivation.md @@ -1,7 +1,7 @@ # ADR-26693: Reaction Target Scoping and Union Activation Permission Derivation **Date**: 2026-04-16 -**Status**: Draft +**Status**: Accepted **Deciders**: pelikhan, Copilot --- @@ -88,4 +88,4 @@ An implementation is considered conformant with this ADR if it satisfies all **M --- -*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/24526383923) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* +*Accepted on 2026-07-19 after validating target-scoped permission derivation and parser defaults for scalar and object reaction forms.* diff --git a/docs/adr/26827-cap-gh-aw-native-action-updates-at-cli-version.md b/docs/adr/26827-cap-gh-aw-native-action-updates-at-cli-version.md index 401e8c7ed06..638d0ce7fc0 100644 --- a/docs/adr/26827-cap-gh-aw-native-action-updates-at-cli-version.md +++ b/docs/adr/26827-cap-gh-aw-native-action-updates-at-cli-version.md @@ -1,7 +1,7 @@ # ADR-26827: Cap gh-aw Native Action Updates at Running CLI Version **Date**: 2026-04-17 -**Status**: Draft +**Status**: Accepted **Deciders**: dsyme, Copilot --- @@ -55,6 +55,7 @@ Introduce a checked-in file (e.g., `compatibility.json`) that explicitly maps CL 1. Implementations **MUST** classify an action as a "gh-aw native action" if and only if its base repository (resolved via `gitutil.ExtractBaseRepo`) is `github/gh-aw` or `github/gh-aw-actions`. 2. Implementations **MUST NOT** classify any other action as a gh-aw native action, including actions in forked or similarly named repositories. +3. Future native action repositories **MAY** be added only by an explicit allowlist expansion in a later release; until then, unknown repositories **MUST** remain non-native. ### Version Cap Enforcement @@ -75,4 +76,4 @@ An implementation is considered conformant with this ADR if it satisfies all **M --- -*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/24560253328) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* +*Accepted on 2026-07-19 after validating the CLI-version cap behavior and documenting the explicit-allowlist extension model for future native action repositories.* diff --git a/docs/src/content/docs/reference/cache-memory.md b/docs/src/content/docs/reference/cache-memory.md index f005cdb7d77..d1bd77dec5a 100644 --- a/docs/src/content/docs/reference/cache-memory.md +++ b/docs/src/content/docs/reference/cache-memory.md @@ -47,6 +47,8 @@ tools: If files with disallowed extensions are found, the workflow will report validation failures. +When a cache is restored for agent execution, gh-aw also strips execute bits from restored working-tree files and removes disallowed file types before the agent can read them. See [ADR-26587](https://github.com/github/gh-aw/blob/main/docs/adr/26587-pre-agent-cache-memory-working-tree-sanitization.md) for the pre-agent sanitization contract behind `allowed-extensions`. + ## Multiple Configurations ```aw wrap diff --git a/docs/src/content/docs/reference/ephemerals.md b/docs/src/content/docs/reference/ephemerals.md index 85af094b112..0dc49f058c1 100644 --- a/docs/src/content/docs/reference/ephemerals.md +++ b/docs/src/content/docs/reference/ephemerals.md @@ -140,6 +140,8 @@ Customize the runner: `action_failure_issue_expires` sets expiration, in hours, for failure issues opened by the conclusion job, including grouped parent issues when `group-reports: true`. The default is `168` (7 days). +If `.github/workflows/aw.json` is present but cannot be loaded, parsed, or validated, compilation keeps the default `168`-hour expiration and emits a warning that identifies the config path and fallback value. + `disabled_jobs` lets you omit specific maintenance jobs from the generated workflow. Job IDs are case-insensitive, and `_` / `-` are treated equivalently. Supported job IDs: diff --git a/docs/src/content/docs/reference/frontmatter.md b/docs/src/content/docs/reference/frontmatter.md index 1e132d77c28..718ed370e0f 100644 --- a/docs/src/content/docs/reference/frontmatter.md +++ b/docs/src/content/docs/reference/frontmatter.md @@ -107,7 +107,7 @@ imports: See [Imports](/gh-aw/reference/imports/) for complete documentation on syntax, shared components, APM package dependencies, and composition patterns. -### Custom Steps and Jobs (`steps:`, `pre-agent-steps:`, `post-steps:`, `jobs:`) +### Custom Steps and Jobs (`pre-steps:`, `steps:`, `pre-agent-steps:`, `post-steps:`, `jobs:`) Add deterministic steps before or after agentic execution, or define full custom GitHub Actions jobs that run before the agent. See [Custom Steps and Jobs](/gh-aw/reference/steps-jobs/) for complete documentation. diff --git a/docs/src/content/docs/reference/steps-jobs.md b/docs/src/content/docs/reference/steps-jobs.md index cda48b71921..f299efb0f6e 100644 --- a/docs/src/content/docs/reference/steps-jobs.md +++ b/docs/src/content/docs/reference/steps-jobs.md @@ -1,6 +1,6 @@ --- title: Custom Steps and Jobs -description: "Add deterministic pre-processing steps and custom GitHub Actions jobs to agentic workflows using steps:, pre-agent-steps:, post-steps:, and jobs:" +description: "Add deterministic pre-processing steps and custom GitHub Actions jobs to agentic workflows using pre-steps:, steps:, pre-agent-steps:, post-steps:, and jobs:" sidebar: order: 820 --- @@ -9,6 +9,28 @@ Custom steps and jobs let you mix deterministic computation with agentic executi See [DeterministicOps](/gh-aw/patterns/deterministic-ops/) for patterns combining computation with AI reasoning. +## Choosing the Right Step Hook + +Use these top-level step hooks in this order: + +1. `pre-steps:` — runs before checkout and all later built-in agent-job setup; only compiler-injected OTLP masking steps may run earlier. Use this for short-lived token minting or anything that must happen before repository checkout. +2. `steps:` — runs after checkout and the normal runtime/bootstrap steps, but before the final pre-agent phase. Use this for deterministic preprocessing that needs the checked-out repository. +3. `pre-agent-steps:` — runs after framework-owned initialization such as checkout cleanup and base-branch restoration, and before MCP startup and engine execution. Use this for last-moment environment preparation immediately before the agent starts. +4. `post-steps:` — runs after the engine finishes. Use this for cleanup, summaries, uploads, or follow-up automation. + +## Custom Pre-Checkout Steps (`pre-steps:`) + +Add custom steps before checkout and the later pre-checkout agent-job setup. Compiler-injected OTLP masking steps may still run earlier. + +```yaml wrap +pre-steps: + - name: Mint checkout token + id: checkout_app + uses: actions/create-github-app-token@v2 +``` + +Use pre-steps when later checkout or setup must consume outputs from a step in the same job. + ## Custom Steps (`steps:`) Add custom steps before agentic execution. If unspecified, a default checkout step is added automatically. diff --git a/docs/src/content/docs/setup/creating-workflows.mdx b/docs/src/content/docs/setup/creating-workflows.mdx index e9a3c6aed28..9014a56029c 100644 --- a/docs/src/content/docs/setup/creating-workflows.mdx +++ b/docs/src/content/docs/setup/creating-workflows.mdx @@ -99,6 +99,10 @@ After merging the pull request, you can run the workflow to see it in action. Ei - trigger runs manually from the Actions tab in GitHub.com, or - use [the `gh aw run` command](/gh-aw/setup/cli/#run) to trigger runs from your terminal. +## Choose the Right Step Lifecycle Hook + +When you add deterministic setup or cleanup to a workflow, choose the smallest hook that matches the lifecycle point you need. See [Custom Steps and Jobs](/gh-aw/reference/steps-jobs/#choosing-the-right-step-hook) for the canonical ordering guide, including the distinction between pre-checkout `pre-steps:` and the later `pre-agent-steps:` phase. + ## Initialize the Repository Running `gh aw init` is **required** to enable the authoring experience in the GitHub code agent. This step configures your repository so that you can create and modify agentic workflows directly from [github.com](https://github.com) or the GitHub mobile app, using the Copilot coding agent. diff --git a/pkg/workflow/compiler_pre_agent_steps_test.go b/pkg/workflow/compiler_pre_agent_steps_test.go index a23bb45c6ba..bc5f9f2c0d6 100644 --- a/pkg/workflow/compiler_pre_agent_steps_test.go +++ b/pkg/workflow/compiler_pre_agent_steps_test.go @@ -9,8 +9,13 @@ import ( "testing" "github.com/github/gh-aw/pkg/testutil" + "gopkg.in/yaml.v3" ) +type compiledWorkflowJobs struct { + Jobs map[string]map[string]any `yaml:"jobs"` +} + func TestPreAgentStepsGeneration(t *testing.T) { tmpDir := testutil.TempDir(t, "pre-agent-steps-test") @@ -18,6 +23,8 @@ func TestPreAgentStepsGeneration(t *testing.T) { on: push permissions: contents: read +checkout: + force-clean-git-credentials: true pre-agent-steps: - name: Finalize prompt context run: echo "finalize" @@ -39,28 +46,49 @@ Test pre-agent-steps. } lockFile := filepath.Join(tmpDir, "test-pre-agent-steps.lock.yml") - content, err := os.ReadFile(lockFile) + lockBytes, err := os.ReadFile(lockFile) if err != nil { t.Fatalf("Failed to read generated lock file: %v", err) } - lockContent := string(content) + lockContent := string(lockBytes) if !strings.Contains(lockContent, "- name: Finalize prompt context") { t.Error("Expected pre-agent-step to be in generated workflow") } startMCPGatewayIndex := indexInNonCommentLines(lockContent, "- name: Start MCP Gateway") + cleanCheckoutCredentialsIndex := indexInNonCommentLines(lockContent, "- name: Clean git credentials after checkout") preAgentStepIndex := indexInNonCommentLines(lockContent, "- name: Finalize prompt context") aiStepIndex := indexInNonCommentLines(lockContent, "- name: Execute Claude Code CLI") - if startMCPGatewayIndex == -1 || preAgentStepIndex == -1 || aiStepIndex == -1 { + if startMCPGatewayIndex == -1 || cleanCheckoutCredentialsIndex == -1 || preAgentStepIndex == -1 || aiStepIndex == -1 { t.Fatal("Could not find expected steps in generated workflow") } + if cleanCheckoutCredentialsIndex >= preAgentStepIndex { + t.Errorf("Clean git credentials after checkout (%d) should appear before pre-agent-step (%d)", cleanCheckoutCredentialsIndex, preAgentStepIndex) + } if preAgentStepIndex >= startMCPGatewayIndex { t.Errorf("Pre-agent-step (%d) should appear before Start MCP Gateway (%d)", preAgentStepIndex, startMCPGatewayIndex) } if preAgentStepIndex >= aiStepIndex { t.Errorf("Pre-agent-step (%d) should appear before AI execution step (%d)", preAgentStepIndex, aiStepIndex) } + var compiled compiledWorkflowJobs + if err := yaml.Unmarshal(lockBytes, &compiled); err != nil { + t.Fatalf("Failed to parse generated lock file as YAML: %v", err) + } + for jobName, job := range compiled.Jobs { + rawSteps, hasSteps := job["steps"] + if !hasSteps { + continue + } + steps, ok := rawSteps.([]any) + if !ok { + t.Fatalf("Expected %s job steps to decode as a YAML sequence, got %T", jobName, rawSteps) + } + if len(steps) == 0 { + t.Fatalf("Expected %s job to omit empty steps blocks instead of emitting steps: []", jobName) + } + } } func TestPreAgentStepsImportsMergeOrder(t *testing.T) { diff --git a/pkg/workflow/compiler_repo_config.go b/pkg/workflow/compiler_repo_config.go index f8375e8b6a7..1a2d53dc581 100644 --- a/pkg/workflow/compiler_repo_config.go +++ b/pkg/workflow/compiler_repo_config.go @@ -1,7 +1,9 @@ package workflow import ( + "fmt" "maps" + "os" "strings" ) @@ -17,6 +19,19 @@ func (c *Compiler) loadRepoConfig() (*RepoConfig, error) { c.repoConfigLoaded = true if c.repoConfigErr != nil { repoConfigLog.Printf("loadRepoConfig: failed to load repo config: %v", c.repoConfigErr) + fmt.Fprintln( + os.Stderr, + formatCompilerMessage( + RepoConfigFileName, + "warning", + fmt.Sprintf( + "failed to load aw.json; compilation will continue with defaults, and action_failure_issue_expires will fall back to %d hours where applicable: %v", + DefaultActionFailureIssueExpiresHours, + c.repoConfigErr, + ), + ), + ) + c.IncrementWarningCount() } else { repoConfigLog.Print("loadRepoConfig: repo config loaded successfully") } diff --git a/pkg/workflow/compiler_repo_config_test.go b/pkg/workflow/compiler_repo_config_test.go index 674f7b7eb0b..6198064befa 100644 --- a/pkg/workflow/compiler_repo_config_test.go +++ b/pkg/workflow/compiler_repo_config_test.go @@ -5,9 +5,51 @@ package workflow import ( "os" "path/filepath" + "strconv" + "strings" "testing" ) +func assertInvalidAWJSONWarning(t *testing.T, content string) { + t.Helper() + + gitRoot := t.TempDir() + workflowsDir := filepath.Join(gitRoot, ".github", "workflows") + if err := os.MkdirAll(workflowsDir, 0o755); err != nil { + t.Fatalf("Failed to create workflows directory: %v", err) + } + if err := os.WriteFile(filepath.Join(workflowsDir, "aw.json"), []byte(content), 0o600); err != nil { + t.Fatalf("Failed to write invalid aw.json: %v", err) + } + + compiler := NewCompiler() + compiler.gitRoot = gitRoot + + var ( + cfg *RepoConfig + err error + ) + stderr := captureStderr(func() { + cfg, err = compiler.loadRepoConfig() + }) + + if err == nil { + t.Fatal("Expected loadRepoConfig to fail for invalid config") + } + if cfg != nil { + t.Fatal("Expected nil config on loadRepoConfig error") + } + if !strings.Contains(stderr, RepoConfigFileName) { + t.Fatalf("Expected warning to mention %s, got: %s", RepoConfigFileName, stderr) + } + if !strings.Contains(stderr, strconv.Itoa(DefaultActionFailureIssueExpiresHours)) { + t.Fatalf("Expected warning to mention fallback %d, got: %s", DefaultActionFailureIssueExpiresHours, stderr) + } + if compiler.GetWarningCount() != 1 { + t.Fatalf("Expected warning count 1, got %d", compiler.GetWarningCount()) + } +} + func TestCompilerLoadRepoConfig_CachesResult(t *testing.T) { gitRoot := t.TempDir() workflowsDir := filepath.Join(gitRoot, ".github", "workflows") @@ -91,6 +133,14 @@ func TestCompilerLoadRepoConfig_CachesError(t *testing.T) { } } +func TestCompilerLoadRepoConfig_InvalidAWJSONWarnsWithPathAndFallback(t *testing.T) { + assertInvalidAWJSONWarning(t, `{"maintenance":{"action_failure_issue_expires":0}}`) +} + +func TestCompilerLoadRepoConfig_MalformedAWJSONWarnsWithPathAndFallback(t *testing.T) { + assertInvalidAWJSONWarning(t, `{"maintenance":`) +} + func TestCompilerLoadRepoConfig_EmptyGitRoot(t *testing.T) { cwd, err := os.Getwd() if err != nil { diff --git a/pkg/workflow/reactions_test.go b/pkg/workflow/reactions_test.go index f08b8984b1f..6adbe9122d5 100644 --- a/pkg/workflow/reactions_test.go +++ b/pkg/workflow/reactions_test.go @@ -210,3 +210,35 @@ func TestParseReactionConfigMapAllTargetsDisabled(t *testing.T) { t.Fatal("Expected parseReactionConfig to fail when all reaction targets are disabled") } } + +func TestParseReactionConfigScalarLeavesTargetPointersNil(t *testing.T) { + reaction, issues, pullRequests, discussions, err := parseReactionConfig("rocket") + if err != nil { + t.Fatalf("parseReactionConfig(scalar) returned unexpected error: %v", err) + } + if reaction != "rocket" { + t.Fatalf("Expected reaction type 'rocket', got %q", reaction) + } + if issues != nil || pullRequests != nil || discussions != nil { + t.Fatalf("Expected scalar reaction config to leave target pointers nil, got issues=%v pullRequests=%v discussions=%v", issues, pullRequests, discussions) + } +} + +func TestParseReactionConfigMapDefaultsTypeAndTargets(t *testing.T) { + reaction, issues, pullRequests, discussions, err := parseReactionConfig(map[string]any{}) + if err != nil { + t.Fatalf("parseReactionConfig(empty map) returned unexpected error: %v", err) + } + if reaction != "eyes" { + t.Fatalf("Expected default reaction type 'eyes', got %q", reaction) + } + if issues == nil || !*issues { + t.Fatalf("Expected issues target to default to true, got %v", issues) + } + if pullRequests == nil || !*pullRequests { + t.Fatalf("Expected pull-requests target to default to true, got %v", pullRequests) + } + if discussions == nil || !*discussions { + t.Fatalf("Expected discussions target to default to true, got %v", discussions) + } +}