Skip to content

feat: add assignees to create-pull-request for fallback issues#24966

Merged
pelikhan merged 4 commits intomainfrom
copilot/add-assignees-to-create-pull-request
Apr 7, 2026
Merged

feat: add assignees to create-pull-request for fallback issues#24966
pelikhan merged 4 commits intomainfrom
copilot/add-assignees-to-create-pull-request

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 6, 2026

safe-outputs.create-pull-request had no way to assign fallback issues — protected-files: fallback-to-issue creates issues outside the normal create-issue path, so create-issue.assignees was never consulted.

Changes

  • Go struct — Added Assignees []string to CreatePullRequestsConfig; single-string coercion to array matches reviewers behavior; field applies to any fallback issue created by create-pull-request
  • Compiler — Emits assignees into handler config via AddStringSlice
  • JSON schema — Added assignees (string or array) to create-pull-request schema with descriptions covering all fallback paths
  • Runtime (create_pull_request.cjs):
    • Added sanitizeFallbackAssignees(): filters invalid values, removes copilot (not a valid GitHub user for issue assignment), and enforces MAX_ASSIGNEES limit from shared constants
    • Added createFallbackIssue(): consolidates all five fallback issue-creation paths with retry-without-assignees logic on HTTP 422 errors, ensuring fallback reliability even if assignee usernames are invalid
  • TypeScript types — Added assignees?: string | string[] to CreatePullRequestConfig; also added the previously missing reviewers field
  • Docs — Updated reference examples and inline comments in safe-outputs-pull-requests.md and safe-outputs.md to reflect that assignees applies to all fallback paths (push failures, permission denied, and protected-files: fallback-to-issue)
  • Tests — Added TestHandlerConfigAssignees unit test (verifies JSON emission), and integration tests for array and single-string assignee coercion

Example

safe-outputs:
  create-pull-request:
    protected-files: fallback-to-issue
    title-prefix: "[ci-coach] "
    reviewers: [mcg]
    assignees: [mcg]   # ← now accepted; applied to all fallback issues

Copilot AI changed the title [WIP] Add support for assignees in create-pull-request for fallback issues feat: add assignees to create-pull-request for fallback issues Apr 6, 2026
Copilot AI requested a review from pelikhan April 6, 2026 22:28
@pelikhan pelikhan marked this pull request as ready for review April 6, 2026 22:34
Copilot AI review requested due to automatic review settings April 6, 2026 22:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for configuring assignees on safe-outputs.create-pull-request so that any fallback issues created by the PR workflow can be assigned automatically.

Changes:

  • Extend Go config + compiler emission to include assignees for the create_pull_request handler.
  • Update runtime (create_pull_request.cjs) to pass assignees into all fallback issue creation paths.
  • Update JSON schema, TypeScript types, and docs to document/validate the new field.
Show a summary per file
File Description
pkg/workflow/create_pull_request.go Adds Assignees []string and coerces single-string assignees into an array before unmarshaling.
pkg/workflow/compiler_safe_outputs_config.go Emits assignees into the create_pull_request handler config.
pkg/parser/schemas/main_workflow_schema.json Adds assignees (string or array) to the create-pull-request schema.
actions/setup/js/create_pull_request.cjs Parses config.assignees and forwards it into fallback issue creation calls.
actions/setup/js/types/safe-outputs-config.d.ts Adds `assignees?: string
docs/src/content/docs/reference/safe-outputs.md Updates example to include assignees.
docs/src/content/docs/reference/safe-outputs-pull-requests.md Updates PR safe-outputs example to include assignees.
.github/workflows/api-consumption-report.lock.yml Bumps ghcr.io/github/gh-aw-mcpg image tag from v0.2.14 to v0.2.15.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (4)

actions/setup/js/create_pull_request.cjs:1058

  • Same as above: passing configAssignees directly into this fallback issues.create() can cause the fallback issue creation to fail (invalid usernames like copilot, too many assignees). Please enforce an assignee limit and handle invalid/special values, or degrade gracefully by retrying without assignees if needed.

            try {
              const { data: issue } = await githubClient.rest.issues.create({
                owner: repoParts.owner,
                repo: repoParts.repo,
                title: title,
                body: fallbackBody,
                labels: mergeFallbackIssueLabels(labels),
                ...(configAssignees.length > 0 && { assignees: configAssignees }),
              });

actions/setup/js/create_pull_request.cjs:1237

  • Same as above: passing configAssignees directly into this protected-files fallback issues.create() can make the review-issue creation fail if assignees is invalid/too large. Please enforce an assignee limit and handle invalid/special values (e.g. copilot) or retry without assignees so protected-files fallback remains reliable.
      try {
        const { data: issue } = await githubClient.rest.issues.create({
          owner: repoParts.owner,
          repo: repoParts.repo,
          title: title,
          body: fallbackBody,
          labels: mergeFallbackIssueLabels(labels),
          ...(configAssignees.length > 0 && { assignees: configAssignees }),
        });

actions/setup/js/create_pull_request.cjs:1438

  • Same as above: passing configAssignees directly into this permission-denied fallback issues.create() can cause fallback issue creation to fail entirely when assignees are invalid/too many. Please enforce an assignee limit and handle invalid/special values (e.g. copilot) or retry without assignees.
        try {
          const { data: issue } = await githubClient.rest.issues.create({
            owner: repoParts.owner,
            repo: repoParts.repo,
            title: title,
            body: fallbackBody,
            labels: mergeFallbackIssueLabels(labels),
            ...(configAssignees.length > 0 && { assignees: configAssignees }),
          });

actions/setup/js/create_pull_request.cjs:1510

  • Same as above: passing configAssignees directly into this final fallback issues.create() can cause fallback issue creation to fail when assignees contains invalid usernames (including copilot) or exceeds limits. Please enforce MAX_ASSIGNEES and/or retry without assignees on validation errors to keep the fallback path robust.
      try {
        const { data: issue } = await githubClient.rest.issues.create({
          owner: repoParts.owner,
          repo: repoParts.repo,
          title: title,
          body: fallbackBody,
          labels: mergeFallbackIssueLabels(labels),
          ...(configAssignees.length > 0 && { assignees: configAssignees }),
        });
  • Files reviewed: 7/8 changed files
  • Comments generated: 7

Labels []string `yaml:"labels,omitempty"`
AllowedLabels []string `yaml:"allowed-labels,omitempty"` // Optional list of allowed labels. If omitted, any labels are allowed (including creating new ones).
Reviewers []string `yaml:"reviewers,omitempty"` // List of users/bots to assign as reviewers to the pull request
Assignees []string `yaml:"assignees,omitempty"` // List of users to assign to the fallback issue when protected-files: fallback-to-issue triggers
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field comment says assignees apply only when protected-files: fallback-to-issue triggers, but the runtime passes assignees to all fallback-issue creation paths (e.g., push-failed, permission denied, general PR creation failure). Please update the comment to reflect that assignees apply to any fallback issue created by create-pull-request.

Suggested change
Assignees []string `yaml:"assignees,omitempty"` // List of users to assign to the fallback issue when protected-files: fallback-to-issue triggers
Assignees []string `yaml:"assignees,omitempty"` // List of users to assign to any fallback issue created by create-pull-request

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +74
// Pre-process the assignees field to convert single string to array BEFORE unmarshaling
if assignees, exists := configData["assignees"]; exists {
if assigneeStr, ok := assignees.(string); ok {
// Convert single string to array
configData["assignees"] = []string{assigneeStr}
createPRLog.Printf("Converted single assignee string to array before unmarshaling")
}
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are tests covering single-string coercion for reviewers, but I couldn't find an analogous test for the new assignees single-string coercion. Please add a regression test ensuring assignees: "user" unmarshals into []string{"user"} (similar to the existing reviewers single-string coverage).

Copilot uses AI. Check for mistakes.
Comment on lines 509 to 513
AddIfNotEmpty("title_prefix", c.TitlePrefix).
AddStringSlice("labels", c.Labels).
AddStringSlice("reviewers", c.Reviewers).
AddStringSlice("assignees", c.Assignees).
AddTemplatableBool("draft", c.Draft).
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assignees was added to the emitted handler config, but there is no unit test asserting it is present/serialized correctly (there is one for reviewers in pkg/workflow/compiler_safe_outputs_config_test.go). Please add a similar test for assignees to prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +5814 to +5824
"description": "Single username to assign to the fallback issue when protected-files: fallback-to-issue triggers."
},
{
"type": "array",
"description": "List of usernames to assign to the fallback issue when protected-files: fallback-to-issue triggers.",
"items": {
"type": "string"
}
}
],
"description": "Optional assignee(s) for the fallback issue created when protected-files: fallback-to-issue triggers. Accepts either a single string or an array of usernames."
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schema descriptions for assignees mention only the protected-files: fallback-to-issue path, but assignees are used for all fallback issue creation paths in create_pull_request.cjs. Please broaden the description so it matches runtime behavior (fallback issues when PR creation/push fails as well as protected-files fallback).

Suggested change
"description": "Single username to assign to the fallback issue when protected-files: fallback-to-issue triggers."
},
{
"type": "array",
"description": "List of usernames to assign to the fallback issue when protected-files: fallback-to-issue triggers.",
"items": {
"type": "string"
}
}
],
"description": "Optional assignee(s) for the fallback issue created when protected-files: fallback-to-issue triggers. Accepts either a single string or an array of usernames."
"description": "Single username to assign to a fallback issue created when pull request creation cannot proceed, including protected-files fallback-to-issue and pull request creation or push failures."
},
{
"type": "array",
"description": "List of usernames to assign to a fallback issue created when pull request creation cannot proceed, including protected-files fallback-to-issue and pull request creation or push failures.",
"items": {
"type": "string"
}
}
],
"description": "Optional assignee(s) for a fallback issue created when pull request creation cannot proceed, including protected-files fallback-to-issue and pull request creation or push failures. Accepts either a single string or an array of usernames."

Copilot uses AI. Check for mistakes.
title-prefix: "[ai] "
labels: [automation]
reviewers: [user1, copilot]
assignees: [user1] # assignees for fallback issues (when protected-files: fallback-to-issue triggers)
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc comment says assignees only apply when protected-files: fallback-to-issue triggers, but assignees are passed to all fallback issue creation paths (including generic PR creation failures). Please update the inline comment to reflect the broader behavior.

Suggested change
assignees: [user1] # assignees for fallback issues (when protected-files: fallback-to-issue triggers)
assignees: [user1] # assignees for fallback issues created when PR creation cannot proceed (including protected-files fallback)

Copilot uses AI. Check for mistakes.
title-prefix: "[ai] " # prefix for titles
labels: [automation] # labels to attach
reviewers: [user1, copilot] # reviewers (use 'copilot' for bot)
assignees: [user1] # assignees for fallback issues (when protected-files: fallback-to-issue triggers)
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc comment says assignees only apply when protected-files: fallback-to-issue triggers, but assignees are passed to all fallback issue creation paths (including generic PR creation failures). Please update the inline comment to reflect the broader behavior.

Suggested change
assignees: [user1] # assignees for fallback issues (when protected-files: fallback-to-issue triggers)
assignees: [user1] # assignees for fallback issues (including protected-files and PR creation failure fallbacks)

Copilot uses AI. Check for mistakes.
Comment on lines 802 to 812

try {
const { data: issue } = await githubClient.rest.issues.create({
owner: repoParts.owner,
repo: repoParts.repo,
title: title,
body: fallbackBody,
labels: mergeFallbackIssueLabels(labels),
...(configAssignees.length > 0 && { assignees: configAssignees }),
});

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configAssignees is forwarded into issues.create() for fallback issue creation, but it is not validated/limited the way create_issue.cjs does (SEC-003 array limits) and it doesn’t filter the special copilot assignee. If assignees contains an invalid username (including copilot) or too many entries, this call can fail and prevent the fallback issue from being created at all. Consider enforcing MAX_ASSIGNEES, filtering/handling copilot, and/or retrying issue creation without assignees if the API rejects them so fallback remains reliable.

This issue also appears in the following locations of the same file:

  • line 1049
  • line 1229
  • line 1430
  • line 1502

See below for a potential fix:

        const MAX_ASSIGNEES = 10;
        const sanitizedConfigAssignees = Array.isArray(configAssignees)
          ? configAssignees
              .filter((assignee) => typeof assignee === "string")
              .map((assignee) => assignee.trim())
              .filter((assignee) => assignee.length > 0 && assignee.toLowerCase() !== "copilot")
              .slice(0, MAX_ASSIGNEES)
          : [];

        /**
         * Retry without assignees only for validation-style failures so fallback issue creation remains reliable.
         *
         * @param {unknown} error
         * @returns {boolean}
         */
        function shouldRetryWithoutAssignees(error) {
          const status = typeof error === "object" && error !== null && "status" in error ? error.status : undefined;
          const message = getErrorMessage(error).toLowerCase();

          return status === 422 &&
            (
              message.includes("assignee") ||
              message.includes("assignees") ||
              message.includes("copilot") ||
              message.includes("unprocessable")
            );
        }

        try {
          const issuePayload = {
            owner: repoParts.owner,
            repo: repoParts.repo,
            title: title,
            body: fallbackBody,
            labels: mergeFallbackIssueLabels(labels),
            ...(sanitizedConfigAssignees.length > 0 && { assignees: sanitizedConfigAssignees }),
          };

          let issue;

          try {
            ({ data: issue } = await githubClient.rest.issues.create(issuePayload));
          } catch (issueError) {
            if (sanitizedConfigAssignees.length === 0 || !shouldRetryWithoutAssignees(issueError)) {
              throw issueError;
            }

            core.warning(`Fallback issue creation failed with assignees; retrying without assignees. ${getErrorMessage(issueError)}`);
            const { assignees, ...issuePayloadWithoutAssignees } = issuePayload;
            ({ data: issue } = await githubClient.rest.issues.create(issuePayloadWithoutAssignees));
          }

Copilot uses AI. Check for mistakes.
@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented Apr 7, 2026

@copilot review comments

…tions, and tests

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/dfa271c3-446f-4855-b2f8-d603c44ac81a

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 7, 2026

@copilot review comments

Addressed in 76261ab:

  • Field comment — updated to say "any fallback issue created by create-pull-request" (not just the protected-files path)
  • Schema descriptions — broadened to cover all fallback paths (protected-files, push failures, PR creation failures)
  • Doc comments — updated in both safe-outputs.md and safe-outputs-pull-requests.md
  • JS validation — added sanitizeFallbackAssignees() (filters invalid values, strips copilot, enforces MAX_ASSIGNEES) and createFallbackIssue() (wraps all 5 fallback paths with retry-without-assignees on 422 errors); imported MAX_ASSIGNEES from constants.cjs and tryEnforceArrayLimit from limit_enforcement_helpers.cjs
  • Tests — added TestHandlerConfigAssignees (unit, verifies JSON emission) and TestCreatePullRequestWorkflowCompilationWithAssignees + TestCreatePullRequestWorkflowCompilationWithSingleStringAssignee (integration, including single-string coercion regression)

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw --local x_amd64/vet git rev-�� --show-toplevel d2I1XET/HAi2X5anzAmU3j7jY8JX /usr/bin/git .js&#39; --ignore-pagit --local a84036af35182792--show-toplevel git (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha --noprofile (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha prettier --write .cfg **/*.ts **/*.json --ignore-path ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel 64/pkg/tool/linux_amd64/vet /usr/bin/git rite &#39;**/*.cjs&#39; git .cfg 64/pkg/tool/linu--show-toplevel git rev-�� ref/tags/v0.1.2 64/pkg/tool/linux_amd64/vet /usr/bin/git te &#39;scripts/**/*git .cfg 64/pkg/tool/linu--show-toplevel git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha -unreachable=false /tmp/go-build3610941154/b208/vet.cfg /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet h ../../../.pretgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha -unreachable=false /tmp/go-build3610941154/b220/vet.cfg ache/node/24.14.1/x64/bin/node h ../../../.pretgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel x_amd64/vet /usr/bin/git --local committer.name 64/pkg/tool/linu--show-toplevel git rev-�� --show-toplevel 64/pkg/tool/linux_amd64/vet /usr/bin/git md .cfg 64/pkg/tool/linu--show-toplevel git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -unreachable=false /tmp/go-build3610941154/b195/vet.cfg ache/node/24.14.1/x64/bin/node h ../../../.pretgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -unreachable=false /tmp/go-build3610941154/b217/vet.cfg /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet h ../../../.pretgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha ithub/workflows/agentic-observability-kit.md /tmp/go-build3610941154/b221/vet.cfg /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet h ../../../.pretgit (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha -unreachable=false /tmp/go-build3610941154/b215/vet.cfg /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet h ../../../.pretgit (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha -unreachable=false /tmp/go-build3610941154/b212/vet.cfg /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet h ../../../.pret/opt/hostedtoolcache/node/24.14.1/x64/bin/node (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0.1.2
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq .object.sha -unreachable=false /tmp/go-build3610941154/b203/vet.cfg 1/x64/bin/node h ../../../.pretgit (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha te &#39;../../../**/*.json&#39; &#39;!../../../pkg/workflow/-p (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha te &#39;../../../**/*.json&#39; &#39;!../../../pkg/workflow/--log-target (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha --show-toplevel (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan pelikhan merged commit f4f9e92 into main Apr 7, 2026
78 of 85 checks passed
@pelikhan pelikhan deleted the copilot/add-assignees-to-create-pull-request branch April 7, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: support assignees on create-pull-request for protected-files: fallback-to-issue

3 participants