Skip to content

Make create-pull-request auto-close issue behavior configurable #23736

@abillingsley

Description

@abillingsley

Summary

When a workflow is triggered from an issue, the create-pull-request safe output handler automatically appends - Fixes #<issue_number> to the PR description if no closing keyword (fixes, closes, resolves) is already present. This causes GitHub to auto-close the triggering issue when the PR is merged.

This behavior is intentional (added in #12823) but is not configurable. Workflows that create PRs related to an issue, but don't intend to fully resolve it, have no way to opt out.

Proposed Changes

Add a new auto-close-issue field to the create-pull-request safe-outputs frontmatter configuration, defaulting to true to preserve backward compatibility.

Frontmatter usage

safe-outputs:
  create-pull-request:
    auto-close-issue: false   # Don't auto-add "Fixes #N" to PR description

What needs to change

1. Go config structpkg/workflow/create_pull_request.go

Add a new field to CreatePullRequestsConfig:

AutoCloseIssue *bool `yaml:"auto-close-issue,omitempty"` // Auto-add "Fixes #N" closing keyword when triggered from an issue (default: true)

2. Compiler handler config serialization — wherever the handler config is built for the create_pull_request JS handler, pass the auto_close_issue value through to the runtime config object.

3. JavaScript handleractions/setup/js/create_pull_request.cjs

Read the config value and gate the auto-append logic:

// ~line 116 area, with the other config reads
const autoCloseIssue = config.auto_close_issue !== false; // Default to true

// ~line 492-501, gate existing behavior
if (triggeringIssueNumber && autoCloseIssue) {
  const hasClosingKeyword = /(?:fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved)\s+#\d+/i.test(processedBody);
  if (!hasClosingKeyword) {
    processedBody = processedBody.trimEnd() + `\n\n- Fixes #${triggeringIssueNumber}`;
    core.info(`Auto-added "Fixes #${triggeringIssueNumber}" closing keyword to PR body as bullet point`);
  }
} else if (triggeringIssueNumber && !autoCloseIssue) {
  core.info(`Skipping auto-close keyword for #${triggeringIssueNumber} (auto-close-issue: false)`);
}

4. JSON schema — Update the create-pull-request schema in pkg/parser/schemas/ to document the new field.

5. Documentation — Update docs/src/content/docs/reference/safe-outputs.md to document auto-close-issue.

Why

The current behavior assumes that every PR created from an issue is intended to fully resolve that issue. In practice, several workflows don't fit this pattern:

  • Partial work PRs — A workflow that creates a PR addressing part of an issue but not all of it. Merging the PR would prematurely close the issue.
  • Investigation/analysis PRs — A workflow that creates a PR with findings or config changes related to an issue but the issue itself requires further action.
  • Multi-PR workflows — A workflow that may spawn multiple PRs from a single issue. Only the final PR should close the issue.
  • Draft/tracking PRs — PRs created for visibility or review that reference an issue but shouldn't close it on merge.

Today, the only workaround is to instruct the agent to include a closing keyword in the body (which skips the auto-append), but this is fragile — agents don't reliably follow instructions (which is the reason the auto-append was added in the first place), and there's no clean way to say "reference but don't close."

Test Plan

Unit tests (actions/setup/js/create_pull_request.test.cjs)

  1. Default behavior preserved — When auto_close_issue is not set in config and the workflow is triggered from an issue, verify - Fixes #N is appended to the PR body.
  2. Explicit true — When auto_close_issue: true, same behavior as default.
  3. Explicit false — When auto_close_issue: false and the workflow is triggered from an issue, verify no Fixes #N is appended.
  4. false with existing closing keyword — When auto_close_issue: false and the body already contains Fixes #N, verify neither the existing keyword is removed nor a new one is added.
  5. No triggering issue — When not triggered from an issue, verify the flag has no effect regardless of value.

Go tests (pkg/workflow/create_pull_request_test.go)

  1. Config parsing — Verify auto-close-issue: false is correctly parsed into CreatePullRequestsConfig.AutoCloseIssue.
  2. Config serialization — Verify the config value is passed through to the handler config JSON as auto_close_issue.
  3. Default omission — Verify the field is omitted from compiled output when not set (backward compatibility).

Integration/compilation tests

  1. Compile a workflow with auto-close-issue: false set and verify the compiled lock file includes the config value.
  2. Compile a workflow without the field set and verify the compiled output is unchanged (no regression).

Schema validation

  1. Valid valuestrue, false accepted.
  2. Invalid values — String, number, etc. rejected by schema validation.

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions