Skip to content

Commit 318d7f4

Browse files
chore: sync actions from gh-aw@v0.74.9 (#111)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent efa5584 commit 318d7f4

31 files changed

Lines changed: 568 additions & 110 deletions

setup/js/add_comment.cjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ async function main(config = {}) {
377377
const maxCount = config.max || 20;
378378
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
379379
const includeFooter = parseBoolTemplatable(config.footer, true);
380+
const requiredLabels = Array.isArray(config.required_labels) ? config.required_labels : [];
381+
const requiredTitlePrefix = config.required_title_prefix || "";
380382
const mentionsDisabled = config.mentions === false || config.mentions?.enabled === false;
381383
const configuredMentionAliases =
382384
!mentionsDisabled && Array.isArray(config.mentions?.allowed) ? config.mentions.allowed.map(alias => (typeof alias === "string" ? alias.trim().replace(/^@+/, "") : "")).filter(alias => alias.length > 0) : [];
@@ -397,6 +399,8 @@ async function main(config = {}) {
397399
if (allowedRepos.size > 0) {
398400
core.info(`Allowed repos: ${Array.from(allowedRepos).join(", ")}`);
399401
}
402+
if (requiredLabels.length > 0) core.info(`Required labels (all): ${requiredLabels.join(", ")}`);
403+
if (requiredTitlePrefix) core.info(`Required title prefix: ${requiredTitlePrefix}`);
400404
if (hideOlderCommentsEnabled) {
401405
core.info("Hide-older-comments is enabled");
402406
}
@@ -521,6 +525,31 @@ async function main(config = {}) {
521525
}
522526
}
523527

528+
// Apply required-labels and required-title-prefix filters (issues/PRs only, not discussions)
529+
if (!isDiscussion && (requiredLabels.length > 0 || requiredTitlePrefix)) {
530+
try {
531+
const { data: filterItem } = await githubClient.rest.issues.get({
532+
owner: repoParts.owner,
533+
repo: repoParts.repo,
534+
issue_number: itemNumber,
535+
});
536+
if (requiredLabels.length > 0) {
537+
const itemLabels = (filterItem.labels || []).map(/** @param {any} l */ l => (typeof l === "string" ? l : l.name || ""));
538+
if (!requiredLabels.every(r => itemLabels.includes(r))) {
539+
core.info(`Skipping add_comment for #${itemNumber}: does not match required-labels filter (${requiredLabels.join(", ")})`);
540+
return { success: false, skipped: true, error: `Item does not match required-labels filter` };
541+
}
542+
}
543+
if (requiredTitlePrefix && !filterItem.title?.startsWith(requiredTitlePrefix)) {
544+
core.info(`Skipping add_comment for #${itemNumber}: title does not start with required prefix "${requiredTitlePrefix}"`);
545+
return { success: false, skipped: true, error: `Item title does not start with required prefix` };
546+
}
547+
} catch (err) {
548+
core.warning(`Could not fetch item #${itemNumber} to check filters: ${getErrorMessage(err)}`);
549+
return { success: false, error: `Failed to check required-labels/required-title-prefix filter: ${getErrorMessage(err)}` };
550+
}
551+
}
552+
524553
// Collect parent issue/PR/discussion authors to allow in @mentions.
525554
// The body was already sanitized in collect_ndjson_output with allowed mentions from the
526555
// event payload (which includes the issue author). Re-sanitizing here without the same

setup/js/add_labels.cjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,16 @@ const main = createCountGatedHandler({
4141
handlerType: HANDLER_TYPE,
4242
setup: async (config, maxCount, isStaged) => {
4343
const { allowed: allowedLabels = [], blocked: blockedPatterns = [] } = config;
44+
const requiredLabels = Array.isArray(config.required_labels) ? config.required_labels : [];
45+
const requiredTitlePrefix = config.required_title_prefix || "";
4446
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
4547
const githubClient = await createAuthenticatedGitHubClient(config);
4648

4749
core.info(`Add labels configuration: max=${maxCount}`);
4850
if (allowedLabels.length > 0) core.info(`Allowed labels: ${allowedLabels.join(", ")}`);
4951
if (blockedPatterns.length > 0) core.info(`Blocked patterns: ${blockedPatterns.join(", ")}`);
52+
if (requiredLabels.length > 0) core.info(`Required labels (all): ${requiredLabels.join(", ")}`);
53+
if (requiredTitlePrefix) core.info(`Required title prefix: ${requiredTitlePrefix}`);
5054
core.info(`Default target repo: ${defaultTargetRepo}`);
5155
if (allowedRepos.size > 0) core.info(`Allowed repos: ${[...allowedRepos].join(", ")}`);
5256

@@ -85,6 +89,26 @@ const main = createCountGatedHandler({
8589
const requestedLabels = message.labels ?? [];
8690
core.info(`Requested labels: ${JSON.stringify(requestedLabels)}`);
8791

92+
// Apply required-labels and required-title-prefix filters
93+
if (requiredLabels.length > 0 || requiredTitlePrefix) {
94+
const { data: item } = await githubClient.rest.issues.get({
95+
owner: repoParts.owner,
96+
repo: repoParts.repo,
97+
issue_number: itemNumber,
98+
});
99+
if (requiredLabels.length > 0) {
100+
const itemLabels = (item.labels || []).map(/** @param {any} l */ l => (typeof l === "string" ? l : l.name || ""));
101+
if (!requiredLabels.every(r => itemLabels.includes(r))) {
102+
core.info(`Skipping add_labels for ${contextType} #${itemNumber}: does not match required-labels filter (${requiredLabels.join(", ")})`);
103+
return { success: false, skipped: true, error: `Item does not match required-labels filter` };
104+
}
105+
}
106+
if (requiredTitlePrefix && !item.title?.startsWith(requiredTitlePrefix)) {
107+
core.info(`Skipping add_labels for ${contextType} #${itemNumber}: title does not start with required prefix "${requiredTitlePrefix}"`);
108+
return { success: false, skipped: true, error: `Item title does not start with required prefix` };
109+
}
110+
}
111+
88112
// If no labels provided, return a helpful message with allowed labels if configured
89113
if (requestedLabels.length === 0) {
90114
const labelSource = allowedLabels.length > 0 ? `the allowed list: ${JSON.stringify(allowedLabels)}` : "the repository's available labels";

setup/js/add_reaction_and_edit_comment.cjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ async function resolveEventEndpoints(eventName, owner, repo, payload) {
139139

140140
async function main() {
141141
const reaction = process.env.GH_AW_REACTION || "eyes";
142-
const command = process.env.GH_AW_COMMAND; // Only present for command workflows
142+
const commandsJSON = process.env.GH_AW_COMMANDS;
143+
const command = commandsJSON ? (JSON.parse(commandsJSON)[0] ?? null) : null; // Only present for command workflows
143144
const invocationContext = resolveInvocationContext(context);
144145
const runUrl = buildWorkflowRunUrl(context, invocationContext.workflowRepo);
145146

setup/js/add_reviewer.cjs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const { processItems } = require("./safe_output_processor.cjs");
1919
const { getErrorMessage } = require("./error_helpers.cjs");
2020
const { getPullRequestNumber } = require("./pr_helpers.cjs");
2121
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
22-
const { isStagedMode } = require("./safe_output_helpers.cjs");
22+
const { isStagedMode, checkRequiredFilter } = require("./safe_output_helpers.cjs");
2323
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
2424
const { COPILOT_REVIEWER_BOT } = require("./constants.cjs");
2525

@@ -35,6 +35,11 @@ async function main(config = {}) {
3535
const githubClient = await createAuthenticatedGitHubClient(config);
3636
const isStaged = isStagedMode(config);
3737

38+
const requiredLabels = Array.isArray(config.required_labels) ? config.required_labels : [];
39+
const requiredTitlePrefix = config.required_title_prefix || "";
40+
if (requiredLabels.length > 0) core.info(`Required labels (all): ${requiredLabels.join(", ")}`);
41+
if (requiredTitlePrefix) core.info(`Required title prefix: ${requiredTitlePrefix}`);
42+
3843
core.info(`Add reviewer configuration: max=${maxCount}`);
3944
if (allowedReviewers.length > 0) {
4045
core.info(`Allowed reviewers: ${allowedReviewers.join(", ")}`);
@@ -77,6 +82,10 @@ async function main(config = {}) {
7782
};
7883
}
7984

85+
const repoParts = { owner: context.repo.owner, repo: context.repo.repo };
86+
const filterResult = await checkRequiredFilter(githubClient, repoParts, prNumber, requiredLabels, requiredTitlePrefix, HANDLER_TYPE);
87+
if (filterResult) return filterResult;
88+
8089
const requestedReviewers = message.reviewers ?? [];
8190
const requestedTeamReviewers = message.team_reviewers ?? [];
8291
core.info(`Requested reviewers: ${JSON.stringify(requestedReviewers)}`);

setup/js/assign_milestone.cjs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
const { getErrorMessage } = require("./error_helpers.cjs");
99
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
10-
const { isStagedMode } = require("./safe_output_helpers.cjs");
10+
const { isStagedMode, checkRequiredFilter } = require("./safe_output_helpers.cjs");
1111
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
1212
const { loadTemporaryIdMapFromResolved, resolveRepoIssueTarget } = require("./temporary_id.cjs");
1313

@@ -49,6 +49,11 @@ async function main(config = {}) {
4949
// Check if we're in staged mode
5050
const isStaged = isStagedMode(config);
5151

52+
const requiredLabels = Array.isArray(config.required_labels) ? config.required_labels : [];
53+
const requiredTitlePrefix = config.required_title_prefix || "";
54+
if (requiredLabels.length > 0) core.info(`Required labels (all): ${requiredLabels.join(", ")}`);
55+
if (requiredTitlePrefix) core.info(`Required title prefix: ${requiredTitlePrefix}`);
56+
5257
core.info(`Assign milestone configuration: max=${maxCount}, auto_create=${autoCreate}`);
5358
if (allowedMilestones.length > 0) {
5459
core.info(`Allowed milestones: ${allowedMilestones.join(", ")}`);
@@ -143,6 +148,11 @@ async function main(config = {}) {
143148
}
144149

145150
const issueNumber = resolvedIssueTarget.resolved.number;
151+
152+
const repoParts = { owner: context.repo.owner, repo: context.repo.repo };
153+
const filterResult = await checkRequiredFilter(githubClient, repoParts, issueNumber, requiredLabels, requiredTitlePrefix, "assign_milestone");
154+
if (filterResult) return filterResult;
155+
146156
if (resolvedIssueTarget.wasTemporaryId) {
147157
core.info(`Resolved temporary ID '${item.issue_number}' to issue #${issueNumber}`);
148158
}

setup/js/assign_to_user.cjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
const { processItems } = require("./safe_output_processor.cjs");
99
const { getErrorMessage } = require("./error_helpers.cjs");
1010
const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs");
11-
const { resolveIssueNumber, extractAssignees } = require("./safe_output_helpers.cjs");
11+
const { resolveIssueNumber, extractAssignees, checkRequiredFilter } = require("./safe_output_helpers.cjs");
1212
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
1313
const { parseBoolTemplatable } = require("./templatable.cjs");
1414
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
@@ -31,6 +31,10 @@ const main = createCountGatedHandler({
3131
const unassignFirst = parseBoolTemplatable(config.unassign_first, false);
3232
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
3333
const githubClient = await createAuthenticatedGitHubClient(config);
34+
const requiredLabels = Array.isArray(config.required_labels) ? config.required_labels : [];
35+
const requiredTitlePrefix = config.required_title_prefix || "";
36+
if (requiredLabels.length > 0) core.info(`Required labels (all): ${requiredLabels.join(", ")}`);
37+
if (requiredTitlePrefix) core.info(`Required title prefix: ${requiredTitlePrefix}`);
3438

3539
core.info(`Assign to user configuration: max=${maxCount}, unassign_first=${unassignFirst}`);
3640
if (allowedAssignees.length > 0) {
@@ -76,6 +80,9 @@ const main = createCountGatedHandler({
7680
}
7781
const issueNumber = issueResult.issueNumber;
7882

83+
const filterResult = await checkRequiredFilter(githubClient, repoParts, issueNumber, requiredLabels, requiredTitlePrefix, HANDLER_TYPE);
84+
if (filterResult) return filterResult;
85+
7986
// Extract assignees using shared helper
8087
const requestedAssignees = extractAssignees(assignItem);
8188

setup/js/close_discussion.cjs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ async function main(config = {}) {
164164
const requiredTitlePrefix = config.required_title_prefix || "";
165165
const maxCount = config.max || 10;
166166
const githubClient = await createAuthenticatedGitHubClient(config);
167+
const allowBody = config.allow_body !== false; // default true; false only when explicitly set to false
167168
let allowedMentionAliases = [];
168169
if (Array.isArray(config.allowedMentionAliases)) {
169170
allowedMentionAliases = config.allowedMentionAliases;
@@ -174,7 +175,7 @@ async function main(config = {}) {
174175
// Check if we're in staged mode
175176
const isStaged = isStagedMode(config);
176177

177-
core.info(`Close discussion configuration: max=${maxCount}`);
178+
core.info(`Close discussion configuration: max=${maxCount}, allow_body=${allowBody}`);
178179
if (requiredLabels.length > 0) {
179180
core.info(`Required labels: ${requiredLabels.join(", ")}`);
180181
}
@@ -278,9 +279,18 @@ async function main(config = {}) {
278279
};
279280
}
280281

281-
// Add comment if body is provided
282+
// Add comment if body is provided and allow-body is not false
282283
let commentUrl;
283-
if (item.body) {
284+
if (!allowBody) {
285+
// allow-body: false — drop any body the agent provided and close without a comment
286+
if (item.body) {
287+
core.warning(
288+
`close_discussion: allow-body is false — dropping non-empty body (length=${item.body.length}) and closing without a comment`
289+
);
290+
} else {
291+
core.info("close_discussion: allow-body is false — closing without a comment");
292+
}
293+
} else if (item.body) {
284294
const sanitizedBody = sanitizeContent(item.body, { allowedAliases: allowedMentionAliases });
285295
const comment = await addDiscussionComment(githubClient, discussion.id, sanitizedBody);
286296
core.info(`Added comment to discussion #${discussionNumber}: ${comment.url}`);

setup/js/close_entity_helpers.cjs

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ function createCloseEntityHandler(config, entityConfig, callbacks, githubClient)
269269
const comment = config.comment || "";
270270
const isStaged = isStagedMode(config);
271271
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
272+
const allowBody = config.allow_body !== false; // default true; false only when explicitly set to false
272273

273274
let processedCount = 0;
274275

@@ -293,12 +294,22 @@ function createCloseEntityHandler(config, entityConfig, callbacks, githubClient)
293294
core.info(`Processing ${entityConfig.itemType} message: ${JSON.stringify(logFields)}`);
294295

295296
// 2. Comment body resolution
296-
/** @type {string} */
297+
/** @type {string|undefined} */
297298
let commentToPost;
298299
/** @type {string} */
299300
let commentSource = "unknown";
300301

301-
if (typeof item.body === "string" && item.body.trim() !== "") {
302+
if (!allowBody) {
303+
// allow-body: false — drop any body the agent provided and skip the comment
304+
if (typeof item.body === "string" && item.body.trim() !== "") {
305+
core.warning(
306+
`${entityConfig.itemType}: allow-body is false — dropping non-empty body (length=${item.body.length}) and closing without a comment`
307+
);
308+
} else {
309+
core.info(`${entityConfig.itemType}: allow-body is false — closing without a comment`);
310+
}
311+
commentToPost = undefined;
312+
} else if (typeof item.body === "string" && item.body.trim() !== "") {
302313
commentToPost = item.body;
303314
commentSource = "item.body";
304315
} else if (typeof comment === "string" && comment.trim() !== "") {
@@ -309,10 +320,12 @@ function createCloseEntityHandler(config, entityConfig, callbacks, githubClient)
309320
return { success: false, error: "No comment body provided" };
310321
}
311322

312-
core.info(`Comment body determined: length=${commentToPost.length}, source=${commentSource}`);
323+
if (commentToPost !== undefined) {
324+
core.info(`Comment body determined: length=${commentToPost.length}, source=${commentSource}`);
313325

314-
// 3. Content sanitization
315-
commentToPost = sanitizeContent(commentToPost);
326+
// 3. Content sanitization
327+
commentToPost = sanitizeContent(commentToPost);
328+
}
316329

317330
// 4. Target repository / entity number resolution
318331
const targetResult = callbacks.resolveTarget(item, config, resolvedTemporaryIds);
@@ -379,34 +392,38 @@ function createCloseEntityHandler(config, entityConfig, callbacks, githubClient)
379392
};
380393
}
381394

382-
// 9. Comment posting
383-
const commentBody = callbacks.buildCommentBody(commentToPost, item);
384-
core.info(`Adding comment to ${entityConfig.displayName} #${entityNumber}: length=${commentBody.length}`);
385-
395+
// 9. Comment posting (skipped when allow-body: false or no body available)
386396
/** @type {{id: number, html_url: string}|null} */
387397
let commentResult = null;
388398
let commentPosted = false;
389-
try {
390-
commentResult = await callbacks.addComment(githubClient, owner, repoName, entityNumber, commentBody);
391-
commentPosted = true;
392-
core.info(`✓ Comment posted to ${entityConfig.displayName} #${entityNumber}: ${commentResult.html_url}`);
393-
core.info(`Comment details: id=${commentResult.id}, body_length=${commentBody.length}`);
394-
} catch (commentError) {
395-
const errorMsg = getErrorMessage(commentError);
396-
if (callbacks.continueOnCommentError) {
397-
core.error(`Failed to add comment to ${entityConfig.displayName} #${entityNumber}: ${errorMsg}`);
398-
core.error(
399-
`Error details: ${JSON.stringify({
400-
entityNumber,
401-
hasBody: !!item.body,
402-
bodyLength: item.body ? item.body.length : 0,
403-
errorMessage: errorMsg,
404-
})}`
405-
);
406-
// commentPosted stays false; close operation continues
407-
} else {
408-
throw new Error(`${ERR_API}: Failed to add comment to ${entityConfig.displayName} #${entityNumber}: ${errorMsg}`, { cause: commentError });
399+
if (commentToPost !== undefined) {
400+
const commentBody = callbacks.buildCommentBody(commentToPost, item);
401+
core.info(`Adding comment to ${entityConfig.displayName} #${entityNumber}: length=${commentBody.length}`);
402+
403+
try {
404+
commentResult = await callbacks.addComment(githubClient, owner, repoName, entityNumber, commentBody);
405+
commentPosted = true;
406+
core.info(`✓ Comment posted to ${entityConfig.displayName} #${entityNumber}: ${commentResult.html_url}`);
407+
core.info(`Comment details: id=${commentResult.id}, body_length=${commentBody.length}`);
408+
} catch (commentError) {
409+
const errorMsg = getErrorMessage(commentError);
410+
if (callbacks.continueOnCommentError) {
411+
core.error(`Failed to add comment to ${entityConfig.displayName} #${entityNumber}: ${errorMsg}`);
412+
core.error(
413+
`Error details: ${JSON.stringify({
414+
entityNumber,
415+
hasBody: !!item.body,
416+
bodyLength: item.body ? item.body.length : 0,
417+
errorMessage: errorMsg,
418+
})}`
419+
);
420+
// commentPosted stays false; close operation continues
421+
} else {
422+
throw new Error(`${ERR_API}: Failed to add comment to ${entityConfig.displayName} #${entityNumber}: ${errorMsg}`, { cause: commentError });
423+
}
409424
}
425+
} else {
426+
core.info(`Skipping comment for ${entityConfig.displayName} #${entityNumber}: no comment body`);
410427
}
411428

412429
// 10. Entity close (skipped when already closed)

0 commit comments

Comments
 (0)