From 8d85204a8eae4b5f1f7b2f3c378d08c4c133c90b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 20:53:01 +0000 Subject: [PATCH] chore: sync actions from gh-aw@v0.84.0 --- setup/js/check_command_position.cjs | 2 +- setup/js/generate_footer.cjs | 15 ++++++++--- setup/js/install_frontmatter_skills.cjs | 15 ++++++----- setup/js/messages_footer.cjs | 34 ++++++++++++++++++++++++- setup/js/messages_run_status.cjs | 15 +++++++++-- setup/js/safe_outputs_handlers.cjs | 9 ++++++- setup/js/threat_detection_warning.cjs | 15 +++++++++++ setup/js/update_pull_request.cjs | 11 ++++++-- setup/md/mcp_cli_tools_prompt.md | 9 +++++++ setup/md/safe_outputs_prompt.md | 2 +- 10 files changed, 108 insertions(+), 19 deletions(-) diff --git a/setup/js/check_command_position.cjs b/setup/js/check_command_position.cjs index e945c7bb..2910b328 100644 --- a/setup/js/check_command_position.cjs +++ b/setup/js/check_command_position.cjs @@ -136,7 +136,7 @@ async function main() { core.setOutput("matched_command", ""); await writeDenialSummary( `The trigger comment did not start with a required command. Expected one of: ${expectedCommands}. Found: \`${firstWord}\`.`, - "Make sure the trigger comment starts with the required command defined in `on.command:` in the workflow frontmatter." + "Make sure the trigger comment starts with the required command defined in `on.slash_command:` in the workflow frontmatter." ); } } catch (error) { diff --git a/setup/js/generate_footer.cjs b/setup/js/generate_footer.cjs index f5d76431..77e5af63 100644 --- a/setup/js/generate_footer.cjs +++ b/setup/js/generate_footer.cjs @@ -1,7 +1,7 @@ // @ts-check /// -const { getDetectionReasonText, getThreatDetectedMarker } = require("./threat_detection_warning.cjs"); +const { getDetectionReasonText, getThreatDetectedMarker, isToolingFailureReason } = require("./threat_detection_warning.cjs"); /** * Generates a standalone workflow-id XML comment marker for searchability. @@ -105,9 +105,13 @@ function generateXMLMarker(workflowName, runUrl) { } /** - * Get the detection caution alert for expired entity closing comments. + * Get the detection alert for expired entity closing comments. * Reads GH_AW_DETECTION_CONCLUSION and GH_AW_DETECTION_REASON from environment variables. - * Returns the caution alert markdown when conclusion is "warning", or empty string otherwise. + * Returns alert markdown when conclusion is "warning", or empty string otherwise. + * + * When the reason indicates a tooling failure (agent_failure or parse_error) a [!WARNING] + * admonition is used so reviewers can distinguish "detection engine crashed" from "detection + * engine found something". Actual threat findings (threat_detected) keep [!CAUTION]. * * Note: This function is intentionally kept inline (not imported from messages_footer.cjs) * because importing messages_footer.cjs here would cause the bundler to inline @@ -119,7 +123,7 @@ function generateXMLMarker(workflowName, runUrl) { * * @param {string} workflowName - Name of the workflow * @param {string} runUrl - URL of the workflow run - * @returns {string} Caution alert markdown or empty string + * @returns {string} Alert markdown or empty string */ function getExpiredEntityCautionAlert(workflowName, runUrl) { const detectionConclusion = process.env.GH_AW_DETECTION_CONCLUSION; @@ -128,6 +132,9 @@ function getExpiredEntityCautionAlert(workflowName, runUrl) { } const detectionReason = process.env.GH_AW_DETECTION_REASON || ""; const reasonText = getDetectionReasonText(detectionReason); + if (isToolingFailureReason(detectionReason)) { + return `> [!WARNING]\n> threat detection engine error\n> The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.\n> ${getThreatDetectedMarker(detectionReason)}\n>\n>
\n> Details\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n>
`; + } return `> [!CAUTION]\n> agentic threat detected\n> Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.\n> ${getThreatDetectedMarker(detectionReason)}\n>\n>
\n> Details\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n>
`; } diff --git a/setup/js/install_frontmatter_skills.cjs b/setup/js/install_frontmatter_skills.cjs index dfc0c5a0..4d666452 100644 --- a/setup/js/install_frontmatter_skills.cjs +++ b/setup/js/install_frontmatter_skills.cjs @@ -132,17 +132,18 @@ function appendSkillInstallFailure(skillSpec, errorMessage) { * @returns {Promise} */ async function writeSkillSummary(skillDir, skills, installedSkillCount, failures) { - core.summary - .addRaw("### Frontmatter skills installed\n\n") - .addRaw(`- Engine skill directory: \`${skillDir}\`\n`) - .addRaw(`- Requested references: \`${JSON.stringify(skills)}\`\n`) - .addRaw(`- Installed SKILL.md files: ${installedSkillCount}\n`); + let body = ""; + body += `- Engine skill directory: \`${skillDir}\`\n`; + body += `- Requested references: \`${JSON.stringify(skills)}\`\n`; + body += `- Installed SKILL.md files: ${installedSkillCount}\n`; if (failures.length > 0) { - core.summary.addRaw("\n#### ⚠️ Skill install failures\n\n"); + body += "\n#### Skill install failures\n\n"; for (const f of failures) { - core.summary.addRaw(`- \`${f.skill}\`: ${f.error}\n`); + body += `- \`${f.skill}\`: ${f.error}\n`; } } + const openAttr = failures.length > 0 ? " open" : ""; + core.summary.addRaw(`### Frontmatter skills installed\n\n\nSkill install details\n\n${body}\n\n\n`); await core.summary.write(); } diff --git a/setup/js/messages_footer.cjs b/setup/js/messages_footer.cjs index bee5985d..aaa0ed91 100644 --- a/setup/js/messages_footer.cjs +++ b/setup/js/messages_footer.cjs @@ -97,6 +97,8 @@ function buildAICEntry(label, value, modelAlias) { * aiCredits: number|undefined, * aiCreditsFormatted: string|undefined, * aiCreditsSuffix: string, + * aiModel: string|undefined, + * aiModelShort: string|undefined, * compressedModelName: string|undefined, * agentAiCredits: number|undefined, * agentAiCreditsFormatted: string|undefined, @@ -110,7 +112,8 @@ function buildAICEntry(label, value, modelAlias) { * }} */ function getAICFromEnv() { - const compressedModelName = reduceModelNameToIdentifier(process.env.GH_AW_PRIMARY_MODEL || process.env.GH_AW_ENGINE_MODEL); + const aiModel = process.env.GH_AW_PRIMARY_MODEL || process.env.GH_AW_ENGINE_MODEL || undefined; + const compressedModelName = reduceModelNameToIdentifier(aiModel); const totalAIC = parsePositiveAIC(process.env.GH_AW_AIC); const explicitAgentAIC = parsePositiveAIC(process.env.GH_AW_AGENT_AIC); const evalsAIC = parsePositiveAIC(process.env.GH_AW_EVALS_AIC); @@ -128,6 +131,8 @@ function getAICFromEnv() { aiCredits, aiCreditsFormatted, aiCreditsSuffix, + aiModel, + aiModelShort: compressedModelName, compressedModelName, agentAiCredits: agentEntry.value, agentAiCreditsFormatted: agentEntry.formatted, @@ -171,6 +176,8 @@ function getFooterMessage(ctx) { aiCredits: envAIC, aiCreditsFormatted: envAICFormatted, aiCreditsSuffix: envAICSuffix, + aiModel, + aiModelShort, compressedModelName, agentAiCredits, agentAiCreditsFormatted, @@ -185,6 +192,8 @@ function getFooterMessage(ctx) { const { ambientContext: envAmbientContext, ambientContextFormatted: envAmbientContextFormatted, ambientContextSuffix: envAmbientContextSuffix } = getAmbientContextFromEnv(); const aiCredits = ctx.aiCredits ?? envAIC; const ambientContext = envAmbientContext; + const detectionConclusion = process.env.GH_AW_DETECTION_CONCLUSION || undefined; + const detectionReason = process.env.GH_AW_DETECTION_REASON || undefined; // Pre-compute history_link as a ready-to-use markdown suffix (empty string when unavailable) const historyLink = ctx.historyUrl ? ` · [◷](${ctx.historyUrl})` : ""; @@ -210,6 +219,11 @@ function getFooterMessage(ctx) { agenticWorkflowUrl, aiCreditsFormatted, aiCreditsSuffix: aiCreditsSuffixForTemplate, + aiModel, + aiModelShort, + aiCreditsUnit: "AIC", + detectionConclusion, + detectionReason, ambientContext, ambientContextFormatted: envAmbientContextFormatted, ambientContextSuffix: envAmbientContextSuffix, @@ -397,6 +411,8 @@ function getFooterAgentFailureIssueMessage(ctx) { aiCredits: envAIC, aiCreditsFormatted: envAICFormatted, aiCreditsSuffix: envAICSuffix, + aiModel, + aiModelShort, compressedModelName, agentAiCredits, agentAiCreditsFormatted, @@ -415,6 +431,8 @@ function getFooterAgentFailureIssueMessage(ctx) { const aiCreditsFormatted = hasExplicitContextAIC ? (explicitContextAIC ? formatAIC(explicitContextAIC) : undefined) : envAICFormatted; const aiCreditsSuffix = hasExplicitContextAIC ? buildAICEntry("", explicitContextAIC, compressedModelName).suffix : envAICSuffix; const aiCreditsSuffixForTemplate = `${aiCreditsSuffix}${ambientContextSuffix}`; + const detectionConclusion = process.env.GH_AW_DETECTION_CONCLUSION || undefined; + const detectionReason = process.env.GH_AW_DETECTION_REASON || undefined; // Create context with both camelCase and snake_case keys, including computed history_link and agentic_workflow_url const templateContext = toSnakeCase({ @@ -424,6 +442,11 @@ function getFooterAgentFailureIssueMessage(ctx) { aiCredits, aiCreditsFormatted, aiCreditsSuffix: aiCreditsSuffixForTemplate, + aiModel, + aiModelShort, + aiCreditsUnit: "AIC", + detectionConclusion, + detectionReason, agentAiCredits, agentAiCreditsFormatted, agentAiCreditsSuffix, @@ -479,6 +502,8 @@ function getFooterAgentFailureCommentMessage(ctx) { aiCredits: envAIC, aiCreditsFormatted: envAICFormatted, aiCreditsSuffix: envAICSuffix, + aiModel, + aiModelShort, compressedModelName, agentAiCredits, agentAiCreditsFormatted, @@ -497,6 +522,8 @@ function getFooterAgentFailureCommentMessage(ctx) { const aiCreditsFormatted = hasExplicitContextAIC ? (explicitContextAIC ? formatAIC(explicitContextAIC) : undefined) : envAICFormatted; const aiCreditsSuffix = hasExplicitContextAIC ? buildAICEntry("", explicitContextAIC, compressedModelName).suffix : envAICSuffix; const aiCreditsSuffixForTemplate = `${aiCreditsSuffix}${ambientContextSuffix}`; + const detectionConclusion = process.env.GH_AW_DETECTION_CONCLUSION || undefined; + const detectionReason = process.env.GH_AW_DETECTION_REASON || undefined; // Create context with both camelCase and snake_case keys, including computed history_link and agentic_workflow_url const templateContext = toSnakeCase({ @@ -506,6 +533,11 @@ function getFooterAgentFailureCommentMessage(ctx) { aiCredits, aiCreditsFormatted, aiCreditsSuffix: aiCreditsSuffixForTemplate, + aiModel, + aiModelShort, + aiCreditsUnit: "AIC", + detectionConclusion, + detectionReason, agentAiCredits, agentAiCreditsFormatted, agentAiCreditsSuffix, diff --git a/setup/js/messages_run_status.cjs b/setup/js/messages_run_status.cjs index 229debb9..445e7cfc 100644 --- a/setup/js/messages_run_status.cjs +++ b/setup/js/messages_run_status.cjs @@ -8,7 +8,7 @@ */ const { getMessages, renderTemplate, toSnakeCase } = require("./messages_core.cjs"); -const { getDetectionReasonText, getThreatDetectedMarkerTemplate, normalizeThreatKinds } = require("./threat_detection_warning.cjs"); +const { getDetectionReasonText, getThreatDetectedMarkerTemplate, normalizeThreatKinds, isToolingFailureReason } = require("./threat_detection_warning.cjs"); /** * Renders a message using a custom template from config or a default template. @@ -141,11 +141,22 @@ function getCommitPushedMessage(ctx) { /** * Get the detection-warning message with progressive disclosure via details/summary. * Used when continue-on-error is true (default) instead of false. + * + * When the reason indicates a tooling failure (agent_failure or parse_error) the + * message uses a [!WARNING] admonition so reviewers can distinguish "detection + * engine crashed" from "detection engine found something". Actual threat findings + * (threat_detected) keep the [!CAUTION] admonition. + * * @param {DetectionWarningContext} ctx - Context for detection-warning message generation - * @returns {string} Detection-warning message with caution admonition + * @returns {string} Detection-warning message with admonition */ function getDetectionWarningMessage(ctx) { const reasonText = getDetectionReasonText(ctx.reason); + const isEngineError = isToolingFailureReason(ctx.reason); + if (isEngineError) { + const defaultTemplate = `> [!WARNING]\n> threat detection engine error\n> The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.\n> ${getThreatDetectedMarkerTemplate()}\n>\n>
\n> Details\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
`; + return renderConfiguredMessage("detectionEngineError", defaultTemplate, { ...ctx, reasonText, threatKinds: normalizeThreatKinds(ctx.reason) }); + } const defaultTemplate = `> [!CAUTION]\n> agentic threat detected\n> Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.\n> ${getThreatDetectedMarkerTemplate()}\n>\n>
\n> Details\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
`; return renderConfiguredMessage("detectionWarning", defaultTemplate, { ...ctx, reasonText, threatKinds: normalizeThreatKinds(ctx.reason) }); } diff --git a/setup/js/safe_outputs_handlers.cjs b/setup/js/safe_outputs_handlers.cjs index b8206285..da0c183b 100644 --- a/setup/js/safe_outputs_handlers.cjs +++ b/setup/js/safe_outputs_handlers.cjs @@ -1084,7 +1084,14 @@ function createHandlers(server, appendSafeOutput, config = {}) { server.debug(`Using configured patch_workspace_path for push_to_pull_request_branch: ${pushPatchWorkspacePath} -> ${repoCwd}`); } - if (((entry.repo && entry.repo.trim()) || pushConfig["target-repo"]) && !repoCwd) { + const envTargetSlug = (process.env.GH_AW_TARGET_REPO_SLUG || "").trim(); + const currentRepo = (process.env.GITHUB_REPOSITORY || "").toLowerCase(); + const envSlugIsSideRepo = envTargetSlug && envTargetSlug.toLowerCase() !== currentRepo; + if (envTargetSlug && !envSlugIsSideRepo) { + server.debug(`GH_AW_TARGET_REPO_SLUG (${envTargetSlug}) matches current repo; not using as side-repo checkout hint for push_to_pull_request_branch`); + } + const hasExplicitTargetRepoHint = (entry.repo && entry.repo.trim()) || pushConfig["target-repo"] || envSlugIsSideRepo; + if (hasExplicitTargetRepoHint && !repoCwd) { server.debug(`Looking for checkout of target repo: ${itemRepo}`); const checkoutResult = findRepoCheckout(itemRepo); if (!checkoutResult.success) { diff --git a/setup/js/threat_detection_warning.cjs b/setup/js/threat_detection_warning.cjs index f5774f11..4202bffa 100644 --- a/setup/js/threat_detection_warning.cjs +++ b/setup/js/threat_detection_warning.cjs @@ -55,9 +55,24 @@ function getDetectionReasonText(reason) { return reasonDescriptions[normalizedReason] || "The threat detection analysis could not be completed."; } +/** + * Returns true when the reason indicates a tooling failure rather than an actual + * security finding. Tooling failures (agent_failure, parse_error) mean the + * detection engine itself crashed or could not produce a verdict — they should be + * surfaced as a distinct infrastructure error, not as a security threat. + * + * @param {string | undefined | null} reason + * @returns {boolean} + */ +function isToolingFailureReason(reason) { + const normalized = String(reason || "").trim(); + return normalized === "agent_failure" || normalized === "parse_error"; +} + module.exports = { normalizeThreatKinds, getThreatDetectedMarker, getThreatDetectedMarkerTemplate, getDetectionReasonText, + isToolingFailureReason, }; diff --git a/setup/js/update_pull_request.cjs b/setup/js/update_pull_request.cjs index a80344b5..bc515403 100644 --- a/setup/js/update_pull_request.cjs +++ b/setup/js/update_pull_request.cjs @@ -38,9 +38,12 @@ function isNonFatalUpdateBranchError(error) { // Require both permission wording and update-branch context to avoid treating unrelated // "workflows permission" errors as non-fatal for pull request branch updates. const hasWorkflowsPermissionError = hasWorkflowsPermissionPhrase && (hasWorkflowMutationRefusal || message.includes("update pull request")); + // GitHub update-branch API also returns 403 with this message when a PR contains workflow + // file changes and the check times out, rather than the usual "refusing to allow" phrase. + const hasWorkflowsScopeRequired = message.includes("`workflows` scope may be required") || message.includes("unable to determine if workflow can be created or updated"); if (status !== undefined) { - if (status === 403 && hasWorkflowsPermissionError) { + if (status === 403 && (hasWorkflowsPermissionError || hasWorkflowsScopeRequired)) { return true; } if (status !== 422) { @@ -52,7 +55,11 @@ function isNonFatalUpdateBranchError(error) { // - already up to date ("There are no new commits on the base branch") // - cannot auto-update due to conflict ("merge conflict between base and head") // These should not fail safe output processing. - return message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head") || hasWorkflowsPermissionError; + // hasWorkflowsPermissionError / hasWorkflowsScopeRequired are only checked here for errors + // with no numeric status (status === undefined). The explicit 403 case is already handled + // by the if-block above, and other numeric statuses (e.g. 422 with these phrases) should + // not be silently swallowed. + return message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head") || ((hasWorkflowsPermissionError || hasWorkflowsScopeRequired) && status === undefined); } /** diff --git a/setup/md/mcp_cli_tools_prompt.md b/setup/md/mcp_cli_tools_prompt.md index cc48bb8e..10403da1 100644 --- a/setup/md/mcp_cli_tools_prompt.md +++ b/setup/md/mcp_cli_tools_prompt.md @@ -13,6 +13,15 @@ printf '{"item_number":42,"body":"### Title\n\nBody."}' | safeoutputs add_commen # or write to a file: safeoutputs create_pull_request . < /tmp/payload.json ``` +To inject an entire local file as the `body` field without re-embedding its content in the model context, use `jq -Rs`: +```bash +jq -Rs --arg discussion_number "$DISCUSSION_NUMBER" \ + '{discussion_number: ($discussion_number|tonumber), body: .}' \ + discussion-body.md \ + | safeoutputs update_discussion . +``` +`jq -Rs` reads the file as a raw string (`-R`) and slurps it into a single JSON string value (`-s`), so `body` is always a valid JSON field. Piping `cat file | safeoutputs ...` does not populate `body` and will be rejected. + The generated command syntax above is schema-derived from each enabled tool's final `inputSchema` and is the source of truth for required/optional parameters. Use ` --help` and ` --help` for the same schema-derived signatures and examples before calling any command. diff --git a/setup/md/safe_outputs_prompt.md b/setup/md/safe_outputs_prompt.md index c70bb2fd..69d1afcb 100644 --- a/setup/md/safe_outputs_prompt.md +++ b/setup/md/safe_outputs_prompt.md @@ -15,5 +15,5 @@ Safe-output calls are write-once declarations for real downstream side effects. temporary_id: optional cross-reference field for future resources created by safe outputs. Canonical form: '#aw_' followed by 3–12 alphanumeric or underscore characters — e.g., '#aw_abc1', '#aw_pr_fix'. Pattern: /^#?aw_[A-Za-z0-9_]{3,12}$/i (the '#' prefix is optional; bare 'aw_abc1' is accepted and normalised to '#aw_abc1' automatically). Use this form for all field values (temporary_id, item_number, issue_number, parent, etc.). In body/markdown text, '#aw_abc1' references are replaced with the real issue/PR number after creation. Omit entirely when not needed. -**Note**: safeoutputs tools do NOT support `@filename` file name expansion. Always provide content inline — do not use `@filename` references in tool arguments. +**Note**: safeoutputs tools do NOT support `@filename` file name expansion. Always provide content inline — do not use `@filename` references in tool arguments. To inject an entire file as the `body` field, use `jq -Rs` to read it as a JSON string and pipe the resulting payload: `jq -Rs '{body: .}' file.md | safeoutputs update_discussion .`