Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/aw/github-agentic-workflows.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions actions/setup/js/pr_review_buffer.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ function createReviewBuffer() {
* Also accepts boolean values for backward compatibility:
* - true → "always"
* - false → "none"
* Note: create-pull-request-review-comment.footer is converted to a string in Go,
* but submit-pull-request-review.footer and global footer are still emitted as booleans.
* Note: submit-pull-request-review.footer is emitted as a string by the Go compiler.
* The global footer setting is emitted as a boolean and converted by getEffectiveFooterString.
* @param {string|boolean} value - Footer mode string or boolean
*/
function setFooterMode(value) {
Expand Down
10 changes: 3 additions & 7 deletions actions/setup/js/safe_output_handler_manager.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -740,14 +740,10 @@ async function main() {
const prReviewBuffer = createReviewBuffer();

// Apply footer config with priority:
// 1. create_pull_request_review_comment.footer (highest priority)
// 2. submit_pull_request_review.footer (fallback)
// 3. Default: "always"
// 1. submit_pull_request_review.footer (highest priority — footer controls review body)
// 2. Default: "always"
let footerConfig = undefined;
if (config.create_pull_request_review_comment?.footer !== undefined) {
footerConfig = config.create_pull_request_review_comment.footer;
core.info(`Using footer config from create_pull_request_review_comment: ${footerConfig}`);
} else if (config.submit_pull_request_review?.footer !== undefined) {
if (config.submit_pull_request_review?.footer !== undefined) {
footerConfig = config.submit_pull_request_review.footer;
core.info(`Using footer config from submit_pull_request_review: ${footerConfig}`);
}
Expand Down
10 changes: 3 additions & 7 deletions actions/setup/js/safe_output_unified_handler_manager.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -985,14 +985,10 @@ async function main() {
const prReviewBuffer = createReviewBuffer();

// Apply footer config with priority:
// 1. create_pull_request_review_comment.footer (highest priority)
// 2. submit_pull_request_review.footer (fallback)
// 3. Default: "always"
// 1. submit_pull_request_review.footer (highest priority — footer controls review body)
// 2. Default: "always"
let footerConfig = undefined;
if (configs.regular?.create_pull_request_review_comment?.footer !== undefined) {
footerConfig = configs.regular.create_pull_request_review_comment.footer;
core.info(`Using footer config from create_pull_request_review_comment: ${footerConfig}`);
} else if (configs.regular?.submit_pull_request_review?.footer !== undefined) {
if (configs.regular?.submit_pull_request_review?.footer !== undefined) {
footerConfig = configs.regular.submit_pull_request_review.footer;
core.info(`Using footer config from submit_pull_request_review: ${footerConfig}`);
}
Expand Down
14 changes: 14 additions & 0 deletions actions/setup/js/types/safe-outputs-config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ interface CreatePullRequestReviewCommentConfig extends SafeOutputConfig {
target?: string;
}

/**
* Configuration for submitting a consolidated PR review.
* The footer field controls when AI-generated footer is added to the review body:
* - "always" (default): Always include footer
* - "none": Never include footer
* - "if-body": Only include footer when review has body text
* Boolean values are also supported: true maps to "always", false maps to "none".
*/
interface SubmitPullRequestReviewConfig extends SafeOutputConfig {
footer?: boolean | "always" | "none" | "if-body";
}

/**
* Configuration for replying to pull request review comments.
* Inherits common fields (e.g. "github-token") from SafeOutputConfig.
Expand Down Expand Up @@ -290,6 +302,7 @@ type SpecificSafeOutputConfig =
| AddCommentConfig
| CreatePullRequestConfig
| CreatePullRequestReviewCommentConfig
| SubmitPullRequestReviewConfig
| CreateCodeScanningAlertConfig
| AutofixCodeScanningAlertConfig
| AddLabelsConfig
Expand Down Expand Up @@ -324,6 +337,7 @@ export {
AddCommentConfig,
CreatePullRequestConfig,
CreatePullRequestReviewCommentConfig,
SubmitPullRequestReviewConfig,
CreateCodeScanningAlertConfig,
AutofixCodeScanningAlertConfig,
AddLabelsConfig,
Expand Down
13 changes: 6 additions & 7 deletions docs/src/content/docs/reference/footers.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@ safe-outputs:

Individual handler settings always take precedence over the global setting.

## PR Review Comment Footer Control
## PR Review Footer Control

For PR review comments (`create-pull-request-review-comment`), the `footer` field supports additional conditional control:
For PR reviews (`submit-pull-request-review`), the `footer` field supports conditional control over when the footer is added to the review body:

```yaml wrap
safe-outputs:
create-pull-request-review-comment:
footer: "if-body" # conditional footer based on review body
submit-pull-request-review:
footer: "if-body" # conditional footer based on review body
```

The `footer` field accepts three string values:

- `"always"` (default) - Always include footer on review comments
- `"none"` - Never include footer on review comments
- `"always"` (default) - Always include footer on the review body
- `"none"` - Never include footer on the review body
- `"if-body"` - Only include footer when the review has body text

Boolean values are also supported and automatically converted:
Expand All @@ -70,9 +70,8 @@ This is particularly useful for clean approval reviews without body text. With `
```yaml wrap
safe-outputs:
create-pull-request-review-comment:
footer: "if-body" # Show footer only when review has body
submit-pull-request-review:
max: 1
footer: "if-body" # Show footer only when review has body
```

When the agent submits an approval without a body (just "APPROVE" event), no footer appears. When the agent includes explanatory comments in the review body, the footer is included.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,7 @@ This section provides complete definitions for all remaining safe output types.
**Notes**:
- Submits all buffered review comments from `create_pull_request_review_comment`
- Review status affects PR merge requirements
- Footer control: `footer` accepts `"always"` (default), `"none"`, or `"if-body"` (only when review body has text); boolean `true`/`false` maps to `"always"`/`"none"`

---

Expand Down
14 changes: 12 additions & 2 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4987,8 +4987,18 @@
"maximum": 10
},
"footer": {
"type": "boolean",
"description": "Controls whether AI-generated footer is added to the review body. When false, the footer is omitted. Defaults to true."
"oneOf": [
{
"type": "boolean",
"description": "Controls whether AI-generated footer is added to the review body. true maps to 'always', false maps to 'none'."
},
{
"type": "string",
"enum": ["always", "none", "if-body"],
"description": "Controls when AI-generated footer is added to the review body: 'always' (default), 'none' (never), or 'if-body' (only when review has body text)."
}
],
"description": "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'."
},
"github-token": {
"$ref": "#/$defs/github_token",
Expand Down
22 changes: 20 additions & 2 deletions pkg/workflow/compiler_safe_outputs_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@ func getEffectiveFooter(localFooter *bool, globalFooter *bool) *bool {
return globalFooter
}

// getEffectiveFooterString returns the effective footer string value for a config.
// If the local string footer is set, use it; otherwise convert the global bool footer.
// Returns nil if neither is set (default to "always" in JavaScript).
func getEffectiveFooterString(localFooter *string, globalFooter *bool) *string {
if localFooter != nil {
return localFooter
}
if globalFooter != nil {
var s string
if *globalFooter {
s = "always"
} else {
s = "none"
}
return &s
}
return nil
}

// handlerConfigBuilder provides a fluent API for building handler configurations
type handlerConfigBuilder struct {
config map[string]any
Expand Down Expand Up @@ -303,7 +322,6 @@ var handlerRegistry = map[string]handlerBuilder{
AddIfNotEmpty("target", c.Target).
AddIfNotEmpty("target-repo", c.TargetRepoSlug).
AddStringSlice("allowed_repos", c.AllowedRepos).
AddStringPtr("footer", c.Footer).
Build()
},
"submit_pull_request_review": func(cfg *SafeOutputsConfig) map[string]any {
Expand All @@ -313,7 +331,7 @@ var handlerRegistry = map[string]handlerBuilder{
c := cfg.SubmitPullRequestReview
return newHandlerConfigBuilder().
AddIfPositive("max", c.Max).
AddBoolPtr("footer", getEffectiveFooter(c.Footer, cfg.Footer)).
AddStringPtr("footer", getEffectiveFooterString(c.Footer, cfg.Footer)).
Build()
},
"reply_to_pull_request_review_comment": func(cfg *SafeOutputsConfig) map[string]any {
Expand Down
25 changes: 0 additions & 25 deletions pkg/workflow/create_pr_review_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ type CreatePullRequestReviewCommentsConfig struct {
Target string `yaml:"target,omitempty"` // Target for comments: "triggering" (default), "*" (any PR), or explicit PR number
TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository PR review comments
AllowedRepos []string `yaml:"allowed-repos,omitempty"` // List of additional repositories that PR review comments can be added to (additionally to the target-repo)
Footer *string `yaml:"footer,omitempty"` // Controls when to show footer in PR review: "always" (default), "none", or "if-body" (only when review has body text)
}

// buildCreateOutputPullRequestReviewCommentJob creates the create_pr_review_comment job
Expand Down Expand Up @@ -120,30 +119,6 @@ func (c *Compiler) parsePullRequestReviewCommentsConfig(outputMap map[string]any
}
prReviewCommentsConfig.TargetRepoSlug = targetRepoSlug

// Parse footer configuration
if footer, exists := configMap["footer"]; exists {
switch f := footer.(type) {
case string:
// Validate string values: "always", "none", "if-body"
if f == "always" || f == "none" || f == "if-body" {
prReviewCommentsConfig.Footer = &f
createPRReviewCommentLog.Printf("Footer control: %s", f)
} else {
createPRReviewCommentLog.Printf("Invalid footer value: %s (must be 'always', 'none', or 'if-body')", f)
}
case bool:
// Map boolean to string: true -> "always", false -> "none"
var footerStr string
if f {
footerStr = "always"
} else {
footerStr = "none"
}
prReviewCommentsConfig.Footer = &footerStr
createPRReviewCommentLog.Printf("Footer control (mapped from bool): %s", footerStr)
}
}

// Parse common base fields with default max of 10
c.parseBaseSafeOutputConfig(configMap, &prReviewCommentsConfig.BaseSafeOutputConfig, 10)
} else {
Expand Down
26 changes: 21 additions & 5 deletions pkg/workflow/submit_pr_review.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var submitPRReviewLog = logger.New("workflow:submit_pr_review")
// If this safe output type is not configured, review comments default to event: "COMMENT".
type SubmitPullRequestReviewConfig struct {
BaseSafeOutputConfig `yaml:",inline"`
Footer *bool `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added to the review body. When false, footer is omitted.
Footer *string `yaml:"footer,omitempty"` // Controls when to show footer in PR review body: "always" (default), "none", or "if-body" (only when review has body text)
}

// parseSubmitPullRequestReviewConfig handles submit-pull-request-review configuration
Expand All @@ -31,11 +31,27 @@ func (c *Compiler) parseSubmitPullRequestReviewConfig(outputMap map[string]any)
// Parse common base fields with default max of 1
c.parseBaseSafeOutputConfig(configMap, &config.BaseSafeOutputConfig, 1)

// Parse footer flag
// Parse footer configuration (string: "always"/"none"/"if-body", or bool for backward compat)
if footer, exists := configMap["footer"]; exists {
if footerBool, ok := footer.(bool); ok {
config.Footer = &footerBool
submitPRReviewLog.Printf("Footer control: %t", footerBool)
switch f := footer.(type) {
case string:
// Validate string values: "always", "none", "if-body"
if f == "always" || f == "none" || f == "if-body" {
config.Footer = &f
submitPRReviewLog.Printf("Footer control: %s", f)
} else {
submitPRReviewLog.Printf("Invalid footer value: %s (must be 'always', 'none', or 'if-body')", f)
}
case bool:
// Map boolean to string: true -> "always", false -> "none"
var footerStr string
if f {
footerStr = "always"
} else {
footerStr = "none"
}
config.Footer = &footerStr
submitPRReviewLog.Printf("Footer control (mapped from bool): %s", footerStr)
}
}
} else {
Expand Down
Loading
Loading