Skip to content

Commit 1fd109a

Browse files
chore: sync actions from gh-aw@v0.74.5 (#107)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent d3abfe9 commit 1fd109a

39 files changed

Lines changed: 1503 additions & 364 deletions

setup/js/TESTING_LIVE_API.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The standalone script provides more detailed output about the API interaction.
4242

4343
The live API test:
4444
1. Fetches the `audit-workflows.md` workflow from the `github/gh-aw` repository
45-
2. Resolves and fetches all imported files (like `shared/mcp/gh-aw.md`)
45+
2. Resolves and fetches all imported files (like `shared/mcp/tavily.md`)
4646
3. Computes the frontmatter hash using the JavaScript implementation
4747
4. Verifies the hash is deterministic by computing it twice
4848
5. Confirms the hash format is a valid SHA-256 hex string
@@ -81,7 +81,7 @@ Workflow: .github/workflows/audit-workflows.md
8181

8282
📊 Summary:
8383
- Successfully fetched workflow from live GitHub API
84-
- Processed workflow with imports (shared/mcp/gh-aw.md, etc.)
84+
- Processed workflow with imports (shared/mcp/tavily.md, etc.)
8585
- Computed deterministic SHA-256 hash
8686
- Verified hash consistency across multiple API calls
8787

setup/js/add_comment.cjs

Lines changed: 25 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
const { generateFooterWithMessages, getDetectionCautionAlert, generateXMLMarker } = require("./messages_footer.cjs");
99
const { generateWorkflowCallIdMarker, matchesWorkflowId } = require("./generate_footer.cjs");
1010
const { getRepositoryUrl } = require("./get_repository_url.cjs");
11-
const { replaceTemporaryIdReferences, loadTemporaryIdMapFromResolved, resolveRepoIssueTarget } = require("./temporary_id.cjs");
11+
const { replaceTemporaryIdReferences, resolveSafeOutputIssueTarget } = require("./temporary_id.cjs");
1212
const { getTrackerID } = require("./get_tracker_id.cjs");
1313
const { getErrorMessage } = require("./error_helpers.cjs");
1414
const { parseBoolTemplatable } = require("./templatable.cjs");
@@ -31,6 +31,21 @@ const { resolveInvocationContext } = require("./invocation_context_helpers.cjs")
3131
/** @type {string} Safe output type handled by this module */
3232
const HANDLER_TYPE = "add_comment";
3333

34+
/**
35+
* Deduplicate an array of strings using case-insensitive comparison, preserving original casing and order.
36+
* @param {string[]} aliases
37+
* @returns {string[]}
38+
*/
39+
function deduplicateCaseInsensitive(aliases) {
40+
const seen = new Set();
41+
return aliases.filter(alias => {
42+
const key = alias.toLowerCase();
43+
if (seen.has(key)) return false;
44+
seen.add(key);
45+
return true;
46+
});
47+
}
48+
3449
/**
3550
* Resolve effective event name/payload for native and forwarded contexts.
3651
* Supports:
@@ -448,33 +463,11 @@ async function main(config = {}) {
448463
// Check if item_number or issue_number was explicitly provided in the message.
449464
// item_number takes precedence over issue_number when both are present.
450465
// pr-number is accepted as an alias for item_number for robustness.
451-
const explicitItemNumber = message.item_number ?? message.issue_number ?? message["pr-number"] ?? undefined;
452-
453-
if (explicitItemNumber !== undefined) {
454-
// Resolve temporary IDs if present
455-
const resolvedTarget = resolveRepoIssueTarget(explicitItemNumber, temporaryIdMap, repoParts.owner, repoParts.repo);
456-
457-
// Check if this is an unresolved temporary ID
458-
if (resolvedTarget.wasTemporaryId && !resolvedTarget.resolved) {
459-
core.info(`Deferring add_comment: unresolved temporary ID (${explicitItemNumber})`);
460-
return {
461-
success: false,
462-
deferred: true,
463-
error: resolvedTarget.errorMessage || `Unresolved temporary ID: ${explicitItemNumber}`,
464-
};
465-
}
466+
const itemTargetResult = resolveSafeOutputIssueTarget({ message, tempIdMap: temporaryIdMap, repoParts, handlerType: HANDLER_TYPE, aliases: ["item_number", "issue_number", "pr-number"] });
467+
if (!itemTargetResult.success) return itemTargetResult;
466468

467-
// Check for other resolution errors (including null resolved)
468-
if (resolvedTarget.errorMessage || !resolvedTarget.resolved) {
469-
core.warning(`Invalid explicit target number specified: ${explicitItemNumber}`);
470-
return {
471-
success: false,
472-
error: `Invalid explicit target number specified: ${explicitItemNumber}`,
473-
};
474-
}
475-
476-
// Use the resolved issue number (safe to access because we checked above)
477-
itemNumber = resolvedTarget.resolved.number;
469+
if (itemTargetResult.number !== null) {
470+
itemNumber = itemTargetResult.number;
478471
core.info(`Using explicitly provided target number (item_number/issue_number/pr-number): #${itemNumber}`);
479472
} else {
480473
// Check if this is a discussion context
@@ -536,7 +529,7 @@ async function main(config = {}) {
536529
const parentAuthors = [];
537530
if (!mentionsDisabled) {
538531
if (!isDiscussion) {
539-
if (explicitItemNumber !== undefined) {
532+
if (itemTargetResult.number !== null) {
540533
// Explicit item_number/issue_number: fetch the issue/PR to get its author
541534
try {
542535
const { data: issueData } = await githubClient.rest.issues.get({
@@ -566,24 +559,7 @@ async function main(config = {}) {
566559
}
567560
}
568561
}
569-
const allowedMentionAliases = [];
570-
const seenAllowedMentionAliases = new Set();
571-
for (const alias of parentAuthors) {
572-
const key = alias.toLowerCase();
573-
if (seenAllowedMentionAliases.has(key)) {
574-
continue;
575-
}
576-
seenAllowedMentionAliases.add(key);
577-
allowedMentionAliases.push(alias);
578-
}
579-
for (const alias of configuredMentionAliases) {
580-
const key = alias.toLowerCase();
581-
if (seenAllowedMentionAliases.has(key)) {
582-
continue;
583-
}
584-
seenAllowedMentionAliases.add(key);
585-
allowedMentionAliases.push(alias);
586-
}
562+
const allowedMentionAliases = deduplicateCaseInsensitive([...parentAuthors, ...configuredMentionAliases]);
587563

588564
if (allowedMentionAliases.length > 0) {
589565
core.info(`[MENTIONS] Allowing aliases in comment: ${allowedMentionAliases.join(", ")}`);
@@ -722,7 +698,7 @@ async function main(config = {}) {
722698
// reply as a threaded comment to the triggering comment instead of posting top-level.
723699
// GitHub Discussions only supports two nesting levels, so if the triggering comment is
724700
// itself a reply, we resolve the top-level parent's node ID to use as replyToId.
725-
const hasExplicitItemNumber = explicitItemNumber !== undefined;
701+
const hasExplicitItemNumber = itemTargetResult.number !== null;
726702
let replyToId;
727703
if (context.eventName === "discussion_comment" && !hasExplicitItemNumber) {
728704
// When triggered by a discussion_comment event, thread the reply under the triggering comment.
@@ -740,7 +716,7 @@ async function main(config = {}) {
740716
}
741717
comment = await commentOnDiscussion(githubClient, repoParts.owner, repoParts.repo, itemNumber, processedBody, replyToId);
742718
} else {
743-
const shouldReplyToTriggeringPRReviewComment = effectiveContext.eventName === "pull_request_review_comment" && explicitItemNumber === undefined;
719+
const shouldReplyToTriggeringPRReviewComment = effectiveContext.eventName === "pull_request_review_comment" && itemTargetResult.number === null;
744720
const triggeringReviewCommentId = Number(effectiveContext.payload?.comment?.id);
745721

746722
if (shouldReplyToTriggeringPRReviewComment && Number.isInteger(triggeringReviewCommentId) && triggeringReviewCommentId > 0) {
@@ -778,7 +754,7 @@ async function main(config = {}) {
778754

779755
// If 404 and item_number was explicitly provided and we tried as issue/PR,
780756
// retry as a discussion (the user may have provided a discussion number)
781-
if (is404 && !isDiscussion && explicitItemNumber !== undefined) {
757+
if (is404 && !isDiscussion && itemTargetResult.number !== null) {
782758
core.info(`Item #${itemNumber} not found as issue/PR, retrying as discussion...`);
783759

784760
try {

setup/js/add_labels.cjs

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@
88
*/
99

1010
/**
11-
* @typedef {{ item_number?: number|string, labels?: string[], repo?: string }} AddLabelsMessage
11+
* @typedef {{
12+
* item_number?: number|string,
13+
* issue_number?: number|string,
14+
* pr_number?: number|string,
15+
* pull_number?: number|string,
16+
* labels?: string[],
17+
* repo?: string
18+
* }} AddLabelsMessage
1219
*/
1320

1421
/** @type {string} Safe output type handled by this module */
@@ -20,7 +27,7 @@ const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_help
2027
const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs");
2128
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
2229
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
23-
const { resolveRepoIssueTarget, loadTemporaryIdMapFromResolved } = require("./temporary_id.cjs");
30+
const { resolveSafeOutputIssueTarget } = require("./temporary_id.cjs");
2431
const { MAX_LABELS } = require("./constants.cjs");
2532
const { createCountGatedHandler } = require("./handler_scaffold.cjs");
2633
const { withRetry, RATE_LIMIT_RETRY_CONFIG } = require("./error_recovery.cjs");
@@ -63,35 +70,12 @@ const main = createCountGatedHandler({
6370
core.info(`Target repository: ${itemRepo}`);
6471

6572
// Determine target issue/PR number
66-
let itemNumber;
67-
if (message.item_number !== undefined) {
68-
// Resolve temporary IDs if present
69-
const tempIdMap = loadTemporaryIdMapFromResolved(resolvedTemporaryIds);
70-
const resolvedTarget = resolveRepoIssueTarget(message.item_number, tempIdMap, repoParts.owner, repoParts.repo);
71-
72-
// Check if this is an unresolved temporary ID
73-
if (resolvedTarget.wasTemporaryId && !resolvedTarget.resolved) {
74-
core.info(`Deferring add_labels: unresolved temporary ID (${message.item_number})`);
75-
return {
76-
success: false,
77-
deferred: true,
78-
error: resolvedTarget.errorMessage || `Unresolved temporary ID: ${message.item_number}`,
79-
};
80-
}
81-
82-
// Check for other resolution errors
83-
if (resolvedTarget.errorMessage || !resolvedTarget.resolved) {
84-
const error = `Invalid item number: ${message.item_number}`;
85-
core.warning(error);
86-
return { success: false, error };
87-
}
73+
// Accept common aliases: issue_number, pr_number, and pull_number are normalised to item_number
74+
const targetResult = resolveSafeOutputIssueTarget({ message, resolvedTemporaryIds, repoParts, handlerType: HANDLER_TYPE });
75+
if (!targetResult.success) return targetResult;
76+
const itemNumber = targetResult.number ?? context.payload?.issue?.number ?? context.payload?.pull_request?.number;
8877

89-
itemNumber = resolvedTarget.resolved.number;
90-
} else {
91-
itemNumber = context.payload?.issue?.number ?? context.payload?.pull_request?.number;
92-
}
93-
94-
if (!itemNumber || isNaN(itemNumber)) {
78+
if (!itemNumber || Number.isNaN(Number(itemNumber))) {
9579
const error = "No issue/PR number available";
9680
core.warning(error);
9781
return { success: false, error };
@@ -118,6 +102,7 @@ const main = createCountGatedHandler({
118102

119103
// Use validation helper to sanitize and validate labels
120104
const labelsResult = validateLabels(requestedLabels, allowedLabels, maxCount, blockedPatterns);
105+
121106
if (!labelsResult.valid) {
122107
// If no valid labels, log info and return gracefully
123108
if (labelsResult.error?.includes("No valid labels")) {
@@ -129,6 +114,7 @@ const main = createCountGatedHandler({
129114
message: "No valid labels found",
130115
};
131116
}
117+
132118
// For other validation errors, return error
133119
core.warning(`Label validation failed: ${labelsResult.error}`);
134120
return {

setup/js/claude_harness.cjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const OVERLOADED_ERROR_PATTERN = /overloaded_error|"overloaded"/i;
6666
// - embedded stream-json result fields (e.g. "api_error_status":429)
6767
// - human-readable message text ("rate limit")
6868
const RATE_LIMIT_ERROR_PATTERN = /rate_limit_error|429 Too Many Requests|"api_error_status"\s*:\s*429|request rejected \(429\)|rate limit/i;
69+
const AUTHENTICATION_FAILED_PATTERN = /Authentication failed(?:\s*\(Request ID:[^)]+\))?/i;
6970

7071
// Pattern to detect a clean max-turns exit from Claude Code.
7172
// Claude Code emits a JSON result object with "subtype":"error_max_turns" when the
@@ -112,6 +113,15 @@ function isRateLimitError(output) {
112113
return RATE_LIMIT_ERROR_PATTERN.test(output);
113114
}
114115

116+
/**
117+
* Determines if the collected output contains an authentication failed error.
118+
* @param {string} output - Collected stdout+stderr from the process
119+
* @returns {boolean}
120+
*/
121+
function isAuthenticationFailedError(output) {
122+
return AUTHENTICATION_FAILED_PATTERN.test(output);
123+
}
124+
115125
/**
116126
* Determines if the collected output signals a clean max-turns exit.
117127
* When Claude Code hits its turn limit it emits a result object with
@@ -441,6 +451,7 @@ async function main() {
441451

442452
const isOverloaded = isOverloadedError(result.output);
443453
const isRateLimit = isRateLimitError(result.output);
454+
const isAuthenticationFailed = isAuthenticationFailedError(result.output);
444455
const isMaxTurns = isMaxTurnsExit(result.output);
445456
const isNoDeferredMarker = isNoDeferredMarkerError(result.output);
446457
const permissionDeniedCount = countPermissionDeniedIssues(result.output);
@@ -450,6 +461,7 @@ async function main() {
450461
` exitCode=${result.exitCode}` +
451462
` isOverloadedError=${isOverloaded}` +
452463
` isRateLimitError=${isRateLimit}` +
464+
` isAuthenticationFailedError=${isAuthenticationFailed}` +
453465
` isMaxTurnsExit=${isMaxTurns}` +
454466
` isNoDeferredMarkerError=${isNoDeferredMarker}` +
455467
` permissionDeniedCount=${permissionDeniedCount}` +
@@ -458,6 +470,11 @@ async function main() {
458470
` retriesRemaining=${MAX_RETRIES - attempt}`
459471
);
460472

473+
if (attempt === 0 && isAuthenticationFailed) {
474+
log(`attempt ${attempt + 1}: authentication failed — not retrying (first-attempt auth failure is non-retryable)`);
475+
break;
476+
}
477+
461478
if (hasNumerousPermissionDenied) {
462479
const deniedCommands = extractDeniedCommands(result.output);
463480
emitMissingToolPermissionIssue({ deniedCommands });
@@ -538,6 +555,7 @@ if (typeof module !== "undefined" && module.exports) {
538555
resolveClaudePromptFileArgs,
539556
stripPromptFileArgs,
540557
isRateLimitError,
558+
isAuthenticationFailedError,
541559
isMaxTurnsExit,
542560
isNoDeferredMarkerError,
543561
isSignalTerminationExitCode,

setup/js/codex_harness.cjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const MAX_DELAY_MS = 60000;
5858
// Matches "rate_limit_exceeded" from the OpenAI error type field and the "429" status code
5959
// that Codex emits when the API rate limit is hit.
6060
const RATE_LIMIT_ERROR_PATTERN = /rate_limit_exceeded|429 Too Many Requests|RateLimitError/i;
61+
const AUTHENTICATION_FAILED_PATTERN = /Authentication failed(?:\s*\(Request ID:[^)]+\))?/i;
6162

6263
// Pattern to detect OpenAI server-side errors (HTTP 500, 503).
6364
// These are transient infrastructure failures that may resolve on retry.
@@ -85,6 +86,15 @@ function isRateLimitError(output) {
8586
return RATE_LIMIT_ERROR_PATTERN.test(output);
8687
}
8788

89+
/**
90+
* Determines if the collected output contains an authentication failed error.
91+
* @param {string} output - Collected stdout+stderr from the process
92+
* @returns {boolean}
93+
*/
94+
function isAuthenticationFailedError(output) {
95+
return AUTHENTICATION_FAILED_PATTERN.test(output);
96+
}
97+
8898
/**
8999
* Determines if the collected output contains an OpenAI server error.
90100
* @param {string} output - Collected stdout+stderr from the process
@@ -297,20 +307,27 @@ async function main() {
297307
}
298308

299309
const isRateLimit = isRateLimitError(result.output);
310+
const isAuthenticationFailed = isAuthenticationFailedError(result.output);
300311
const isServer = isServerError(result.output);
301312
const permissionDeniedCount = countPermissionDeniedIssues(result.output);
302313
const hasNumerousPermissionDenied = hasNumerousPermissionDeniedIssues(result.output);
303314
log(
304315
`attempt ${attempt + 1} failed:` +
305316
` exitCode=${result.exitCode}` +
306317
` isRateLimitError=${isRateLimit}` +
318+
` isAuthenticationFailedError=${isAuthenticationFailed}` +
307319
` isServerError=${isServer}` +
308320
` permissionDeniedCount=${permissionDeniedCount}` +
309321
` hasNumerousPermissionDenied=${hasNumerousPermissionDenied}` +
310322
` hasOutput=${result.hasOutput}` +
311323
` retriesRemaining=${MAX_RETRIES - attempt}`
312324
);
313325

326+
if (attempt === 0 && isAuthenticationFailed) {
327+
log(`attempt ${attempt + 1}: authentication failed — not retrying (first-attempt auth failure is non-retryable)`);
328+
break;
329+
}
330+
314331
if (hasNumerousPermissionDenied) {
315332
const deniedCommands = extractDeniedCommands(result.output);
316333
emitMissingToolPermissionIssue({ deniedCommands });
@@ -347,6 +364,7 @@ if (typeof module !== "undefined" && module.exports) {
347364
module.exports = {
348365
resolveCodexPromptFileArgs,
349366
isRateLimitError,
367+
isAuthenticationFailedError,
350368
isServerError,
351369
countPermissionDeniedIssues,
352370
hasNumerousPermissionDeniedIssues,

0 commit comments

Comments
 (0)