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 .`