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
1 change: 1 addition & 0 deletions actions/setup/js/create_missing_data_issue.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const HANDLER_TYPE = "create_missing_data_issue";
const main = buildMissingIssueHandler({
handlerType: HANDLER_TYPE,
defaultTitlePrefix: "[missing data]",
defaultLabels: ["agentic-workflows"],
itemsField: "missing_data",
templatePath: "/opt/gh-aw/prompts/missing_data_issue.md",
templateListKey: "missing_data_list",
Expand Down
1 change: 1 addition & 0 deletions actions/setup/js/create_missing_tool_issue.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const HANDLER_TYPE = "create_missing_tool_issue";
const main = buildMissingIssueHandler({
handlerType: HANDLER_TYPE,
defaultTitlePrefix: "[missing tool]",
defaultLabels: ["agentic-workflows"],
itemsField: "missing_tools",
templatePath: "/opt/gh-aw/prompts/missing_tool_issue.md",
templateListKey: "missing_tools_list",
Expand Down
6 changes: 4 additions & 2 deletions actions/setup/js/missing_issue_helpers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ const { sanitizeContent } = require("./sanitize_content.cjs");
* @param {function(string): string[]} options.buildCommentHeader - Returns header lines for the comment body given runUrl
* @param {function(Object, number): string[]} options.renderCommentItem - Renders a single item for an existing-issue comment
* @param {function(Object, number): string[]} options.renderIssueItem - Renders a single item for a new-issue body
* @param {string[]} [options.defaultLabels] - Labels always applied to created issues (merged with config.labels)
* @returns {HandlerFactoryFunction}
*/
function buildMissingIssueHandler(options) {
const { handlerType, defaultTitlePrefix, itemsField, templatePath, templateListKey, buildCommentHeader, renderCommentItem, renderIssueItem } = options;
const { handlerType, defaultTitlePrefix, itemsField, templatePath, templateListKey, buildCommentHeader, renderCommentItem, renderIssueItem, defaultLabels = [] } = options;

Comment on lines +32 to 33
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

defaultLabels is merged into the final label list without the same String/trim/filter normalization applied to config.labels. For consistency (and to avoid surprises if a future call site passes non-trimmed/non-string values), consider normalizing defaultLabels the same way before merging/deduping.

Copilot uses AI. Check for mistakes.
return async function main(config = {}) {
// Extract configuration
const titlePrefix = config.title_prefix || defaultTitlePrefix;
const envLabels = config.labels ? (Array.isArray(config.labels) ? config.labels : config.labels.split(",")).map(label => String(label).trim()).filter(label => label) : [];
const userLabels = config.labels ? (Array.isArray(config.labels) ? config.labels : config.labels.split(",")).map(label => String(label).trim()).filter(label => label) : [];
const envLabels = [...new Set([...defaultLabels, ...userLabels])];
const maxCount = config.max || 1; // Default to 1 to create only one issue per workflow run
Comment on lines +38 to 39
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

envLabels is no longer just labels sourced from the config/environment—it now includes defaultLabels plus user-provided labels. Renaming this variable to something like issueLabels/appliedLabels would make the intent clearer and reduce confusion for future maintenance.

This issue also appears on line 41 of the same file.

See below for a potential fix:

    const issueLabels = [...new Set([...defaultLabels, ...userLabels])];
    const maxCount = config.max || 1; // Default to 1 to create only one issue per workflow run

    core.info(`Title prefix: ${titlePrefix}`);
    if (issueLabels.length > 0) {
      core.info(`Applied labels: ${issueLabels.join(", ")}`);

Copilot uses AI. Check for mistakes.

core.info(`Title prefix: ${titlePrefix}`);
Expand Down
42 changes: 42 additions & 0 deletions actions/setup/js/missing_issue_helpers.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,48 @@ describe("missing_issue_helpers.cjs - buildMissingIssueHandler", () => {

expect(mockGithub.rest.issues.create).toHaveBeenCalledWith(expect.objectContaining({ labels: ["bug", "needs-triage"] }));
});

it("should always apply defaultLabels from options even without config.labels", async () => {
mockGithub.rest.search.issuesAndPullRequests.mockResolvedValue({
data: { total_count: 0, items: [] },
});
mockGithub.rest.issues.create.mockResolvedValue({
data: { number: 77, html_url: "https://github.com/owner/repo/issues/77" },
});

const handler = await buildMissingIssueHandler(makeOptions({ defaultLabels: ["agentic-workflows"] }))({});
await handler(defaultMessage);

expect(mockGithub.rest.issues.create).toHaveBeenCalledWith(expect.objectContaining({ labels: ["agentic-workflows"] }));
});

it("should merge defaultLabels with config.labels", async () => {
mockGithub.rest.search.issuesAndPullRequests.mockResolvedValue({
data: { total_count: 0, items: [] },
});
mockGithub.rest.issues.create.mockResolvedValue({
data: { number: 77, html_url: "https://github.com/owner/repo/issues/77" },
});

const handler = await buildMissingIssueHandler(makeOptions({ defaultLabels: ["agentic-workflows"] }))({ labels: ["bug"] });
await handler(defaultMessage);

expect(mockGithub.rest.issues.create).toHaveBeenCalledWith(expect.objectContaining({ labels: ["agentic-workflows", "bug"] }));
});

it("should deduplicate labels when defaultLabels and config.labels overlap", async () => {
mockGithub.rest.search.issuesAndPullRequests.mockResolvedValue({
data: { total_count: 0, items: [] },
});
mockGithub.rest.issues.create.mockResolvedValue({
data: { number: 77, html_url: "https://github.com/owner/repo/issues/77" },
});

const handler = await buildMissingIssueHandler(makeOptions({ defaultLabels: ["agentic-workflows"] }))({ labels: ["agentic-workflows", "bug"] });
await handler(defaultMessage);

expect(mockGithub.rest.issues.create).toHaveBeenCalledWith(expect.objectContaining({ labels: ["agentic-workflows", "bug"] }));
});
});

describe("error handling", () => {
Expand Down