diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml
index 3fc2d5d190..79051d8813 100644
--- a/.github/workflows/issue-triage.lock.yml
+++ b/.github/workflows/issue-triage.lock.yml
@@ -5,7 +5,7 @@
#
# Source: githubnext/agentics/workflows/issue-triage.md@0837fb7b24c3b84ee77fb7c8cfa8735c48be347a
#
-# Effective stop-time: 2025-12-03 20:01:19
+# Effective stop-time: 2025-12-06 19:47:58
#
# Job Dependency Graph:
# ```mermaid
@@ -43,8 +43,8 @@
# https://github.com/actions/github-script/commit/ed597411d8f924073f98dfc5c65a23a2325f34cd
# - actions/setup-node@v6 (2028fbc5c25fe9cf00d9f06a71cc4710d4507903)
# https://github.com/actions/setup-node/commit/2028fbc5c25fe9cf00d9f06a71cc4710d4507903
-# - actions/upload-artifact@v4 (ea165f8d65b6e75b540449e92b4886f43607fa02)
-# https://github.com/actions/upload-artifact/commit/ea165f8d65b6e75b540449e92b4886f43607fa02
+# - actions/upload-artifact@v5 (330a01c490aca151604b8cf639adc76d48f6c5d4)
+# https://github.com/actions/upload-artifact/commit/330a01c490aca151604b8cf639adc76d48f6c5d4
name: "Agentic Triage"
"on":
@@ -59,12 +59,22 @@ concurrency:
run-name: "Agentic Triage"
+env:
+ GH_AW_ERROR_PATTERNS: |-
+ [
+ {"id":"gh-action-error","pattern":"^::(error)(?:\\\\s+[^:]*)?::(.+)","level_group":1,"message_group":2,"description":"GitHub Actions workflow command - error"},
+ {"id":"gh-action-warning","pattern":"^::(warning)(?:\\\\s+[^:]*)?::(.+)","level_group":1,"message_group":2,"description":"GitHub Actions workflow command - warning"},
+ {"id":"bracketed-level","pattern":"^\\[(ERROR|CRITICAL|WARNING|WARN)\\]\\s+(.+)","level_group":1,"message_group":2,"description":"Bracketed log level at start of line"},
+ {"id":"timestamped-copilot","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z\\s+\\[(ERROR|WARN|WARNING|CRITICAL)\\]\\s+(.+)","level_group":1,"message_group":3,"description":"Timestamped Copilot CLI messages"}
+ ]
+
jobs:
activation:
needs: pre_activation
if: needs.pre_activation.outputs.activated == 'true'
runs-on: ubuntu-slim
permissions:
+ contents: read
discussions: write
issues: write
pull-requests: write
@@ -132,14 +142,22 @@ jobs:
core.info(` Source modified: ${workflowStat.mtime.toISOString()}`);
core.info(` Lock modified: ${lockStat.mtime.toISOString()}`);
if (workflowMtime > lockMtime) {
- const warningMessage = `🔴🔴🔴 WARNING: Lock file '${lockFile}' is outdated! The workflow file '${workflowMdFile}' has been modified more recently. Run 'gh aw compile' to regenerate the lock file.`;
+ const warningMessage = `WARNING: Lock file '${lockFile}' is outdated! The workflow file '${workflowMdFile}' has been modified more recently. Run 'gh aw compile' to regenerate the lock file.`;
core.error(warningMessage);
- await core.summary
- .addRaw("## ⚠️ Workflow Lock File Warning\n\n")
- .addRaw(`🔴🔴🔴 **WARNING**: Lock file \`${lockFile}\` is outdated!\n\n`)
- .addRaw(`The workflow file \`${workflowMdFile}\` has been modified more recently.\n\n`)
- .addRaw("Run `gh aw compile` to regenerate the lock file.\n\n")
- .write();
+ const workflowTimestamp = workflowStat.mtime.toISOString();
+ const lockTimestamp = lockStat.mtime.toISOString();
+ const gitSha = process.env.GITHUB_SHA;
+ let summary = core.summary
+ .addRaw("### ⚠️ Workflow Lock File Warning\n\n")
+ .addRaw("**WARNING**: Lock file is outdated and needs to be regenerated.\n\n")
+ .addRaw("**Files:**\n")
+ .addRaw(`- Source: \`${workflowMdFile}\` (modified: ${workflowTimestamp})\n`)
+ .addRaw(`- Lock: \`${lockFile}\` (modified: ${lockTimestamp})\n\n`);
+ if (gitSha) {
+ summary = summary.addRaw(`**Git Commit:** \`${gitSha}\`\n\n`);
+ }
+ summary = summary.addRaw("**Action Required:** Run `gh aw compile` to regenerate the lock file.\n\n");
+ await summary.write();
} else {
core.info("✅ Lock file is up to date");
}
@@ -482,9 +500,7 @@ jobs:
needs:
- agent
- detection
- if: >
- (((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'add_comment'))) &&
- (((github.event.issue.number) || (github.event.pull_request.number)) || (github.event.discussion.number))
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'add_comment'))
runs-on: ubuntu-slim
permissions:
contents: read
@@ -512,8 +528,8 @@ jobs:
- name: Setup agent output environment variable
run: |
mkdir -p /tmp/gh-aw/safeoutputs/
- find /tmp/gh-aw/safeoutputs/ -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> $GITHUB_ENV
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Add Issue Comment
id: add_comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
@@ -522,9 +538,44 @@ jobs:
GH_AW_WORKFLOW_NAME: "Agentic Triage"
GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/issue-triage.md@0837fb7b24c3b84ee77fb7c8cfa8735c48be347a"
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/tree/0837fb7b24c3b84ee77fb7c8cfa8735c48be347a/workflows/issue-triage.md"
+ GH_AW_COMMENT_TARGET: "*"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
+ const fs = require("fs");
+ function loadAgentOutput() {
+ const agentOutputFile = process.env.GH_AW_AGENT_OUTPUT;
+ if (!agentOutputFile) {
+ core.info("No GH_AW_AGENT_OUTPUT environment variable found");
+ return { success: false };
+ }
+ let outputContent;
+ try {
+ outputContent = fs.readFileSync(agentOutputFile, "utf8");
+ } catch (error) {
+ const errorMessage = `Error reading agent output file: ${error instanceof Error ? error.message : String(error)}`;
+ core.setFailed(errorMessage);
+ return { success: false, error: errorMessage };
+ }
+ if (outputContent.trim() === "") {
+ core.info("Agent output content is empty");
+ return { success: false };
+ }
+ core.info(`Agent output content length: ${outputContent.length}`);
+ let validatedOutput;
+ try {
+ validatedOutput = JSON.parse(outputContent);
+ } catch (error) {
+ const errorMessage = `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}`;
+ core.setFailed(errorMessage);
+ return { success: false, error: errorMessage };
+ }
+ if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) {
+ core.info("No valid items found in agent output");
+ return { success: false };
+ }
+ return { success: true, items: validatedOutput.items };
+ }
function generateFooter(
workflowName,
runUrl,
@@ -608,35 +659,11 @@ jobs:
async function main() {
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
const isDiscussionExplicit = process.env.GITHUB_AW_COMMENT_DISCUSSION === "true";
- const agentOutputFile = process.env.GH_AW_AGENT_OUTPUT;
- if (!agentOutputFile) {
- core.info("No GH_AW_AGENT_OUTPUT environment variable found");
+ const result = loadAgentOutput();
+ if (!result.success) {
return;
}
- let outputContent;
- try {
- outputContent = require("fs").readFileSync(agentOutputFile, "utf8");
- } catch (error) {
- core.setFailed(`Error reading agent output file: ${error instanceof Error ? error.message : String(error)}`);
- return;
- }
- if (outputContent.trim() === "") {
- core.info("Agent output content is empty");
- return;
- }
- core.info(`Agent output content length: ${outputContent.length}`);
- let validatedOutput;
- try {
- validatedOutput = JSON.parse(outputContent);
- } catch (error) {
- core.setFailed(`Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}`);
- return;
- }
- if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) {
- core.info("No valid items found in agent output");
- return;
- }
- const commentItems = validatedOutput.items.filter( item => item.type === "add_comment");
+ const commentItems = result.items.filter( item => item.type === "add_comment");
if (commentItems.length === 0) {
core.info("No add-comment items found in agent output");
return;
@@ -873,9 +900,7 @@ jobs:
needs:
- agent
- detection
- if: >
- (((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'add_labels'))) &&
- ((github.event.issue.number) || (github.event.pull_request.number))
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'add_labels'))
runs-on: ubuntu-slim
permissions:
contents: read
@@ -894,8 +919,8 @@ jobs:
- name: Setup agent output environment variable
run: |
mkdir -p /tmp/gh-aw/safeoutputs/
- find /tmp/gh-aw/safeoutputs/ -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> $GITHUB_ENV
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Add Labels
id: add_labels
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
@@ -903,6 +928,7 @@ jobs:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_LABELS_ALLOWED: ""
GH_AW_LABELS_MAX_COUNT: 5
+ GH_AW_LABELS_TARGET: "*"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -911,8 +937,8 @@ jobs:
return "";
}
let sanitized = content.trim();
- sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
+ sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
sanitized = sanitized.replace(
/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g,
(_m, p1, p2) => `${p1}\`@${p2}\``
@@ -920,54 +946,86 @@ jobs:
sanitized = sanitized.replace(/[<>&'"]/g, "");
return sanitized.trim();
}
- async function main() {
+ const fs = require("fs");
+ function loadAgentOutput() {
const agentOutputFile = process.env.GH_AW_AGENT_OUTPUT;
if (!agentOutputFile) {
core.info("No GH_AW_AGENT_OUTPUT environment variable found");
- return;
+ return { success: false };
}
let outputContent;
try {
- outputContent = require("fs").readFileSync(agentOutputFile, "utf8");
+ outputContent = fs.readFileSync(agentOutputFile, "utf8");
} catch (error) {
- core.setFailed(`Error reading agent output file: ${error instanceof Error ? error.message : String(error)}`);
- return;
+ const errorMessage = `Error reading agent output file: ${error instanceof Error ? error.message : String(error)}`;
+ core.setFailed(errorMessage);
+ return { success: false, error: errorMessage };
}
if (outputContent.trim() === "") {
core.info("Agent output content is empty");
- return;
+ return { success: false };
}
core.info(`Agent output content length: ${outputContent.length}`);
let validatedOutput;
try {
validatedOutput = JSON.parse(outputContent);
} catch (error) {
- core.setFailed(`Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}`);
- return;
+ const errorMessage = `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}`;
+ core.setFailed(errorMessage);
+ return { success: false, error: errorMessage };
}
if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) {
- core.warning("No valid items found in agent output");
+ core.info("No valid items found in agent output");
+ return { success: false };
+ }
+ return { success: true, items: validatedOutput.items };
+ }
+ async function generateStagedPreview(options) {
+ const { title, description, items, renderItem } = options;
+ let summaryContent = `## 🎭 Staged Mode: ${title} Preview\n\n`;
+ summaryContent += `${description}\n\n`;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ summaryContent += renderItem(item, i);
+ summaryContent += "---\n\n";
+ }
+ try {
+ await core.summary.addRaw(summaryContent).write();
+ core.info(summaryContent);
+ core.info(`📝 ${title} preview written to step summary`);
+ } catch (error) {
+ core.setFailed(error instanceof Error ? error : String(error));
+ }
+ }
+ async function main() {
+ const result = loadAgentOutput();
+ if (!result.success) {
return;
}
- const labelsItem = validatedOutput.items.find(item => item.type === "add_labels");
+ const labelsItem = result.items.find(item => item.type === "add_labels");
if (!labelsItem) {
core.warning("No add-labels item found in agent output");
return;
}
core.info(`Found add-labels item with ${labelsItem.labels.length} labels`);
if (process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true") {
- let summaryContent = "## 🎭 Staged Mode: Add Labels Preview\n\n";
- summaryContent += "The following labels would be added if staged mode was disabled:\n\n";
- if (labelsItem.item_number) {
- summaryContent += `**Target Issue:** #${labelsItem.item_number}\n\n`;
- } else {
- summaryContent += `**Target:** Current issue/PR\n\n`;
- }
- if (labelsItem.labels && labelsItem.labels.length > 0) {
- summaryContent += `**Labels to add:** ${labelsItem.labels.join(", ")}\n\n`;
- }
- await core.summary.addRaw(summaryContent).write();
- core.info("📝 Label addition preview written to step summary");
+ await generateStagedPreview({
+ title: "Add Labels",
+ description: "The following labels would be added if staged mode was disabled:",
+ items: [labelsItem],
+ renderItem: item => {
+ let content = "";
+ if (item.item_number) {
+ content += `**Target Issue:** #${item.item_number}\n\n`;
+ } else {
+ content += `**Target:** Current issue/PR\n\n`;
+ }
+ if (item.labels && item.labels.length > 0) {
+ content += `**Labels to add:** ${item.labels.join(", ")}\n\n`;
+ }
+ return content;
+ },
+ });
return;
}
const allowedLabelsEnv = process.env.GH_AW_LABELS_ALLOWED?.trim();
@@ -1119,7 +1177,6 @@ jobs:
group: "gh-aw-copilot-${{ github.workflow }}"
env:
GH_AW_SAFE_OUTPUTS: /tmp/gh-aw/safeoutputs/outputs.jsonl
- GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"max\":5},\"missing_tool\":{}}"
outputs:
output: ${{ steps.collect_output.outputs.output }}
output_types: ${{ steps.collect_output.outputs.output_types }}
@@ -1180,24 +1237,29 @@ jobs:
main().catch(error => {
core.setFailed(error instanceof Error ? error.message : String(error));
});
- - name: Validate COPILOT_CLI_TOKEN secret
+ - name: Validate COPILOT_GITHUB_TOKEN or COPILOT_CLI_TOKEN secret
run: |
- if [ -z "$COPILOT_CLI_TOKEN" ]; then
- echo "Error: COPILOT_CLI_TOKEN secret is not set"
- echo "The GitHub Copilot CLI engine requires the COPILOT_CLI_TOKEN secret to be configured."
- echo "Please configure this secret in your repository settings."
+ if [ -z "$COPILOT_GITHUB_TOKEN" ] && [ -z "$COPILOT_CLI_TOKEN" ]; then
+ echo "Error: Neither COPILOT_GITHUB_TOKEN nor COPILOT_CLI_TOKEN secret is set"
+ echo "The GitHub Copilot CLI engine requires either COPILOT_GITHUB_TOKEN or COPILOT_CLI_TOKEN secret to be configured."
+ echo "Please configure one of these secrets in your repository settings."
echo "Documentation: https://githubnext.github.io/gh-aw/reference/engines/#github-copilot-default"
exit 1
fi
- echo "COPILOT_CLI_TOKEN secret is configured"
+ if [ -n "$COPILOT_GITHUB_TOKEN" ]; then
+ echo "COPILOT_GITHUB_TOKEN secret is configured"
+ else
+ echo "COPILOT_CLI_TOKEN secret is configured (using as fallback for COPILOT_GITHUB_TOKEN)"
+ fi
env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
COPILOT_CLI_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903
with:
node-version: '24'
- name: Install GitHub Copilot CLI
- run: npm install -g @github/copilot@0.0.353
+ run: npm install -g @github/copilot@0.0.354
- name: Downloading container images
run: |
set -e
@@ -1207,7 +1269,7 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/safeoutputs
cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF'
- {"add_comment":{"max":1},"add_labels":{"max":5},"missing_tool":{}}
+ {"add_comment":{"max":1,"target":"*"},"add_labels":{"max":5},"missing_tool":{}}
EOF
cat > /tmp/gh-aw/safeoutputs/mcp-server.cjs << 'EOF'
const fs = require("fs");
@@ -1231,39 +1293,26 @@ jobs:
normalized = normalized.toLowerCase();
return normalized;
}
- const configEnv = process.env.GH_AW_SAFE_OUTPUTS_CONFIG;
+ const configPath = process.env.GH_AW_SAFE_OUTPUTS_CONFIG_PATH || "/tmp/gh-aw/safeoutputs/config.json";
let safeOutputsConfigRaw;
- if (!configEnv) {
- const defaultConfigPath = "/tmp/gh-aw/safeoutputs/config.json";
- debug(`GH_AW_SAFE_OUTPUTS_CONFIG not set, attempting to read from default path: ${defaultConfigPath}`);
- try {
- if (fs.existsSync(defaultConfigPath)) {
- debug(`Reading config from file: ${defaultConfigPath}`);
- const configFileContent = fs.readFileSync(defaultConfigPath, "utf8");
- debug(`Config file content length: ${configFileContent.length} characters`);
- debug(`Config file read successfully, attempting to parse JSON`);
- safeOutputsConfigRaw = JSON.parse(configFileContent);
- debug(`Successfully parsed config from file with ${Object.keys(safeOutputsConfigRaw).length} configuration keys`);
- } else {
- debug(`Config file does not exist at: ${defaultConfigPath}`);
- debug(`Using minimal default configuration`);
- safeOutputsConfigRaw = {};
- }
- } catch (error) {
- debug(`Error reading config file: ${error instanceof Error ? error.message : String(error)}`);
- debug(`Falling back to empty configuration`);
+ debug(`Reading config from file: ${configPath}`);
+ try {
+ if (fs.existsSync(configPath)) {
+ debug(`Config file exists at: ${configPath}`);
+ const configFileContent = fs.readFileSync(configPath, "utf8");
+ debug(`Config file content length: ${configFileContent.length} characters`);
+ debug(`Config file read successfully, attempting to parse JSON`);
+ safeOutputsConfigRaw = JSON.parse(configFileContent);
+ debug(`Successfully parsed config from file with ${Object.keys(safeOutputsConfigRaw).length} configuration keys`);
+ } else {
+ debug(`Config file does not exist at: ${configPath}`);
+ debug(`Using minimal default configuration`);
safeOutputsConfigRaw = {};
}
- } else {
- debug(`Using GH_AW_SAFE_OUTPUTS_CONFIG from environment variable`);
- debug(`Config environment variable length: ${configEnv.length} characters`);
- try {
- safeOutputsConfigRaw = JSON.parse(configEnv);
- debug(`Successfully parsed config from environment: ${JSON.stringify(safeOutputsConfigRaw)}`);
- } catch (error) {
- debug(`Error parsing config from environment: ${error instanceof Error ? error.message : String(error)}`);
- throw new Error(`Failed to parse GH_AW_SAFE_OUTPUTS_CONFIG: ${error instanceof Error ? error.message : String(error)}`);
- }
+ } catch (error) {
+ debug(`Error reading config file: ${error instanceof Error ? error.message : String(error)}`);
+ debug(`Falling back to empty configuration`);
+ safeOutputsConfigRaw = {};
}
const safeOutputsConfig = Object.fromEntries(Object.entries(safeOutputsConfigRaw).map(([k, v]) => [k.replace(/-/g, "_"), v]));
debug(`Final processed config: ${JSON.stringify(safeOutputsConfig)}`);
@@ -1509,6 +1558,17 @@ jobs:
};
};
function getCurrentBranch() {
+ const cwd = process.env.GITHUB_WORKSPACE || process.cwd();
+ try {
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
+ encoding: "utf8",
+ cwd: cwd,
+ }).trim();
+ debug(`Resolved current branch from git in ${cwd}: ${branch}`);
+ return branch;
+ } catch (error) {
+ debug(`Failed to get branch from git: ${error instanceof Error ? error.message : String(error)}`);
+ }
const ghHeadRef = process.env.GITHUB_HEAD_REF;
const ghRefName = process.env.GITHUB_REF_NAME;
if (ghHeadRef) {
@@ -1519,23 +1579,22 @@ jobs:
debug(`Resolved current branch from GITHUB_REF_NAME: ${ghRefName}`);
return ghRefName;
}
- const cwd = process.env.GITHUB_WORKSPACE || process.cwd();
- try {
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
- encoding: "utf8",
- cwd: cwd,
- }).trim();
- debug(`Resolved current branch from git in ${cwd}: ${branch}`);
- return branch;
- } catch (error) {
- throw new Error(`Failed to get current branch: ${error instanceof Error ? error.message : String(error)}`);
- }
+ throw new Error("Failed to determine current branch: git command failed and no GitHub environment variables available");
+ }
+ function getBaseBranch() {
+ return process.env.GH_AW_BASE_BRANCH || "main";
}
const createPullRequestHandler = args => {
const entry = { ...args, type: "create_pull_request" };
- if (!entry.branch || entry.branch.trim() === "") {
- entry.branch = getCurrentBranch();
- debug(`Using current branch for create_pull_request: ${entry.branch}`);
+ const baseBranch = getBaseBranch();
+ if (!entry.branch || entry.branch.trim() === "" || entry.branch === baseBranch) {
+ const detectedBranch = getCurrentBranch();
+ if (entry.branch === baseBranch) {
+ debug(`Branch equals base branch (${baseBranch}), detecting actual working branch: ${detectedBranch}`);
+ } else {
+ debug(`Using current branch for create_pull_request: ${detectedBranch}`);
+ }
+ entry.branch = detectedBranch;
}
appendSafeOutput(entry);
return {
@@ -1549,9 +1608,15 @@ jobs:
};
const pushToPullRequestBranchHandler = args => {
const entry = { ...args, type: "push_to_pull_request_branch" };
- if (!entry.branch || entry.branch.trim() === "") {
- entry.branch = getCurrentBranch();
- debug(`Using current branch for push_to_pull_request_branch: ${entry.branch}`);
+ const baseBranch = getBaseBranch();
+ if (!entry.branch || entry.branch.trim() === "" || entry.branch === baseBranch) {
+ const detectedBranch = getCurrentBranch();
+ if (entry.branch === baseBranch) {
+ debug(`Branch equals base branch (${baseBranch}), detecting actual working branch: ${detectedBranch}`);
+ } else {
+ debug(`Using current branch for push_to_pull_request_branch: ${detectedBranch}`);
+ }
+ entry.branch = detectedBranch;
}
appendSafeOutput(entry);
return {
@@ -1995,7 +2060,6 @@ jobs:
env:
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GH_AW_SAFE_OUTPUTS_CONFIG: ${{ toJSON(env.GH_AW_SAFE_OUTPUTS_CONFIG) }}
GH_AW_ASSETS_BRANCH: ${{ env.GH_AW_ASSETS_BRANCH }}
GH_AW_ASSETS_MAX_SIZE_KB: ${{ env.GH_AW_ASSETS_MAX_SIZE_KB }}
GH_AW_ASSETS_ALLOWED_EXTS: ${{ env.GH_AW_ASSETS_ALLOWED_EXTS }}
@@ -2032,7 +2096,6 @@ jobs:
"tools": ["*"],
"env": {
"GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}",
- "GH_AW_SAFE_OUTPUTS_CONFIG": "\${GH_AW_SAFE_OUTPUTS_CONFIG}",
"GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}",
"GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}",
"GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}",
@@ -2065,8 +2128,9 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
run: |
- mkdir -p $(dirname "$GH_AW_PROMPT")
- cat > "$GH_AW_PROMPT" << 'PROMPT_EOF'
+ PROMPT_DIR="$(dirname "$GH_AW_PROMPT")"
+ mkdir -p "$PROMPT_DIR"
+ cat > "$GH_AW_PROMPT" << PROMPT_EOF
# Agentic Triage
@@ -2134,7 +2198,7 @@ jobs:
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
run: |
- cat >> "$GH_AW_PROMPT" << 'PROMPT_EOF'
+ cat >> "$GH_AW_PROMPT" << PROMPT_EOF
---
@@ -2166,7 +2230,7 @@ jobs:
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
run: |
- cat >> "$GH_AW_PROMPT" << 'PROMPT_EOF'
+ cat >> "$GH_AW_PROMPT" << PROMPT_EOF
---
@@ -2179,7 +2243,7 @@ jobs:
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
run: |
- cat >> "$GH_AW_PROMPT" << 'PROMPT_EOF'
+ cat >> "$GH_AW_PROMPT" << PROMPT_EOF
---
@@ -2204,7 +2268,7 @@ jobs:
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
run: |
- cat >> "$GH_AW_PROMPT" << 'PROMPT_EOF'
+ cat >> "$GH_AW_PROMPT" << PROMPT_EOF
---
@@ -2269,21 +2333,27 @@ jobs:
}
}
main();
- - name: Print prompt to step summary
+ - name: Print prompt
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
run: |
- echo "" >> "$GITHUB_STEP_SUMMARY"
- echo "Generated Prompt
" >> "$GITHUB_STEP_SUMMARY"
- echo "" >> "$GITHUB_STEP_SUMMARY"
- echo '```markdown' >> "$GITHUB_STEP_SUMMARY"
- cat "$GH_AW_PROMPT" >> "$GITHUB_STEP_SUMMARY"
- echo '```' >> "$GITHUB_STEP_SUMMARY"
- echo "" >> "$GITHUB_STEP_SUMMARY"
- echo " " >> "$GITHUB_STEP_SUMMARY"
+ # Print prompt to workflow logs (equivalent to core.info)
+ echo "Generated Prompt:"
+ cat "$GH_AW_PROMPT"
+ # Print prompt to step summary
+ {
+ echo ""
+ echo "Generated Prompt
"
+ echo ""
+ echo '```markdown'
+ cat "$GH_AW_PROMPT"
+ echo '```'
+ echo ""
+ echo " "
+ } >> "$GITHUB_STEP_SUMMARY"
- name: Upload prompt
if: always()
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: prompt.txt
path: /tmp/gh-aw/aw-prompts/prompt.txt
@@ -2299,7 +2369,7 @@ jobs:
engine_name: "GitHub Copilot CLI",
model: "",
version: "",
- agent_version: "0.0.353",
+ agent_version: "0.0.354",
workflow_name: "Agentic Triage",
experimental: false,
supports_tools_allowlist: true,
@@ -2326,7 +2396,7 @@ jobs:
console.log(JSON.stringify(awInfo, null, 2));
- name: Upload agentic run info
if: always()
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: aw_info.json
path: /tmp/gh-aw/aw_info.json
@@ -2340,7 +2410,7 @@ jobs:
timeout-minutes: 10
run: |
set -o pipefail
- COPILOT_CLI_INSTRUCTION=$(cat /tmp/gh-aw/aw-prompts/prompt.txt)
+ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"
mkdir -p /tmp/
mkdir -p /tmp/gh-aw/
mkdir -p /tmp/gh-aw/agent/
@@ -2348,15 +2418,14 @@ jobs:
copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/.copilot/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool web-fetch --prompt "$COPILOT_CLI_INSTRUCTION" 2>&1 | tee /tmp/gh-aw/agent-stdio.log
env:
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN || secrets.COPILOT_CLI_TOKEN }}
GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"max\":5},\"missing_tool\":{}}"
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
- GITHUB_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
GITHUB_WORKSPACE: ${{ github.workspace }}
XDG_CONFIG_HOME: /home/runner
- name: Redact secrets in logs
@@ -2470,13 +2539,14 @@ jobs:
}
await main();
env:
- GH_AW_SECRET_NAMES: 'COPILOT_CLI_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
+ GH_AW_SECRET_NAMES: 'COPILOT_CLI_TOKEN,COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
SECRET_COPILOT_CLI_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
+ SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Safe Outputs
if: always()
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: safe_output.jsonl
path: ${{ env.GH_AW_SAFE_OUTPUTS }}
@@ -2486,24 +2556,58 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"max\":5},\"missing_tool\":{}}"
GH_AW_ALLOWED_DOMAINS: "api.enterprise.githubcopilot.com,api.github.com,github.com,raw.githubusercontent.com,registry.npmjs.org"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
with:
script: |
async function main() {
const fs = require("fs");
+ function extractDomainsFromUrl(url) {
+ if (!url || typeof url !== "string") {
+ return [];
+ }
+ try {
+ const urlObj = new URL(url);
+ const hostname = urlObj.hostname.toLowerCase();
+ const domains = [hostname];
+ if (hostname === "github.com") {
+ domains.push("api.github.com");
+ domains.push("raw.githubusercontent.com");
+ domains.push("*.githubusercontent.com");
+ }
+ else if (!hostname.startsWith("api.")) {
+ domains.push("api." + hostname);
+ domains.push("raw." + hostname);
+ }
+ return domains;
+ } catch (e) {
+ return [];
+ }
+ }
function sanitizeContent(content, maxLength) {
if (!content || typeof content !== "string") {
return "";
}
const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS;
const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"];
- const allowedDomains = allowedDomainsEnv
+ let allowedDomains = allowedDomainsEnv
? allowedDomainsEnv
.split(",")
.map(d => d.trim())
.filter(d => d)
: defaultAllowedDomains;
+ const githubServerUrl = process.env.GITHUB_SERVER_URL;
+ const githubApiUrl = process.env.GITHUB_API_URL;
+ if (githubServerUrl) {
+ const serverDomains = extractDomainsFromUrl(githubServerUrl);
+ allowedDomains = allowedDomains.concat(serverDomains);
+ }
+ if (githubApiUrl) {
+ const apiDomains = extractDomainsFromUrl(githubApiUrl);
+ allowedDomains = allowedDomains.concat(apiDomains);
+ }
+ allowedDomains = [...new Set(allowedDomains)];
let sanitized = content;
sanitized = neutralizeCommands(sanitized);
sanitized = neutralizeMentions(sanitized);
@@ -2918,7 +3022,16 @@ jobs:
}
}
const outputFile = process.env.GH_AW_SAFE_OUTPUTS;
- const safeOutputsConfig = process.env.GH_AW_SAFE_OUTPUTS_CONFIG;
+ const configPath = process.env.GH_AW_SAFE_OUTPUTS_CONFIG_PATH || "/tmp/gh-aw/safeoutputs/config.json";
+ let safeOutputsConfig;
+ try {
+ if (fs.existsSync(configPath)) {
+ const configFileContent = fs.readFileSync(configPath, "utf8");
+ safeOutputsConfig = JSON.parse(configFileContent);
+ }
+ } catch (error) {
+ core.warning(`Failed to read config file from ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (!outputFile) {
core.info("GH_AW_SAFE_OUTPUTS not set, no output to collect");
core.setOutput("output", "");
@@ -2937,8 +3050,7 @@ jobs:
let expectedOutputTypes = {};
if (safeOutputsConfig) {
try {
- const rawConfig = JSON.parse(safeOutputsConfig);
- expectedOutputTypes = Object.fromEntries(Object.entries(rawConfig).map(([key, value]) => [key.replace(/-/g, "_"), value]));
+ expectedOutputTypes = Object.fromEntries(Object.entries(safeOutputsConfig).map(([key, value]) => [key.replace(/-/g, "_"), value]));
core.info(`Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}`);
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
@@ -3309,13 +3421,13 @@ jobs:
await main();
- name: Upload sanitized agent output
if: always() && env.GH_AW_AGENT_OUTPUT
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: agent_output.json
path: ${{ env.GH_AW_AGENT_OUTPUT }}
if-no-files-found: warn
- name: Upload engine output files
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: agent_outputs
path: |
@@ -3323,7 +3435,7 @@ jobs:
if-no-files-found: ignore
- name: Upload MCP logs
if: always()
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: mcp-logs
path: /tmp/gh-aw/mcp-logs/
@@ -4208,7 +4320,7 @@ jobs:
main();
- name: Upload Agent Stdio
if: always()
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: agent-stdio.log
path: /tmp/gh-aw/agent-stdio.log
@@ -4590,24 +4702,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Validate COPILOT_CLI_TOKEN secret
+ - name: Validate COPILOT_GITHUB_TOKEN or COPILOT_CLI_TOKEN secret
run: |
- if [ -z "$COPILOT_CLI_TOKEN" ]; then
- echo "Error: COPILOT_CLI_TOKEN secret is not set"
- echo "The GitHub Copilot CLI engine requires the COPILOT_CLI_TOKEN secret to be configured."
- echo "Please configure this secret in your repository settings."
+ if [ -z "$COPILOT_GITHUB_TOKEN" ] && [ -z "$COPILOT_CLI_TOKEN" ]; then
+ echo "Error: Neither COPILOT_GITHUB_TOKEN nor COPILOT_CLI_TOKEN secret is set"
+ echo "The GitHub Copilot CLI engine requires either COPILOT_GITHUB_TOKEN or COPILOT_CLI_TOKEN secret to be configured."
+ echo "Please configure one of these secrets in your repository settings."
echo "Documentation: https://githubnext.github.io/gh-aw/reference/engines/#github-copilot-default"
exit 1
fi
- echo "COPILOT_CLI_TOKEN secret is configured"
+ if [ -n "$COPILOT_GITHUB_TOKEN" ]; then
+ echo "COPILOT_GITHUB_TOKEN secret is configured"
+ else
+ echo "COPILOT_CLI_TOKEN secret is configured (using as fallback for COPILOT_GITHUB_TOKEN)"
+ fi
env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
COPILOT_CLI_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903
with:
node-version: '24'
- name: Install GitHub Copilot CLI
- run: npm install -g @github/copilot@0.0.353
+ run: npm install -g @github/copilot@0.0.354
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
@@ -4621,7 +4738,7 @@ jobs:
timeout-minutes: 20
run: |
set -o pipefail
- COPILOT_CLI_INSTRUCTION=$(cat /tmp/gh-aw/aw-prompts/prompt.txt)
+ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"
mkdir -p /tmp/
mkdir -p /tmp/gh-aw/
mkdir -p /tmp/gh-aw/agent/
@@ -4629,11 +4746,11 @@ jobs:
copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/.copilot/logs/ --disable-builtin-mcps --allow-tool 'shell(cat)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(jq)' --allow-tool 'shell(ls)' --allow-tool 'shell(tail)' --allow-tool 'shell(wc)' --prompt "$COPILOT_CLI_INSTRUCTION" 2>&1 | tee /tmp/gh-aw/threat-detection/detection.log
env:
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN || secrets.COPILOT_CLI_TOKEN }}
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
- GITHUB_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
GITHUB_WORKSPACE: ${{ github.workspace }}
XDG_CONFIG_HOME: /home/runner
- name: Parse threat detection results
@@ -4674,7 +4791,7 @@ jobs:
}
- name: Upload threat detection log
if: always()
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: threat-detection.log
path: /tmp/gh-aw/threat-detection/detection.log
@@ -4702,8 +4819,8 @@ jobs:
- name: Setup agent output environment variable
run: |
mkdir -p /tmp/gh-aw/safeoutputs/
- find /tmp/gh-aw/safeoutputs/ -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> $GITHUB_ENV
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Record Missing Tool
id: missing_tool
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
@@ -4821,7 +4938,7 @@ jobs:
id: check_stop_time
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
env:
- GH_AW_STOP_TIME: 2025-12-03 20:01:19
+ GH_AW_STOP_TIME: 2025-12-06 19:47:58
GH_AW_WORKFLOW_NAME: "Agentic Triage"
with:
script: |
@@ -4891,8 +5008,8 @@ jobs:
- name: Setup agent output environment variable
run: |
mkdir -p /tmp/gh-aw/safeoutputs/
- find /tmp/gh-aw/safeoutputs/ -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> $GITHUB_ENV
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Update reaction comment with completion status
id: update_reaction
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md
index 087f009106..2ad2978de7 100644
--- a/.github/workflows/issue-triage.md
+++ b/.github/workflows/issue-triage.md
@@ -8,12 +8,25 @@ on:
permissions: read-all
+# Add stricter error-detection patterns so issue text doesn't trigger agent error detection.
+# After merging this file run `gh aw compile` to regenerate the lock file.
+env:
+ GH_AW_ERROR_PATTERNS: >-
+ [
+ {"id":"gh-action-error","pattern":"^::(error)(?:\\\\s+[^:]*)?::(.+)","level_group":1,"message_group":2,"description":"GitHub Actions workflow command - error"},
+ {"id":"gh-action-warning","pattern":"^::(warning)(?:\\\\s+[^:]*)?::(.+)","level_group":1,"message_group":2,"description":"GitHub Actions workflow command - warning"},
+ {"id":"bracketed-level","pattern":"^\\[(ERROR|CRITICAL|WARNING|WARN)\\]\\s+(.+)","level_group":1,"message_group":2,"description":"Bracketed log level at start of line"},
+ {"id":"timestamped-copilot","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z\\s+\\[(ERROR|WARN|WARNING|CRITICAL)\\]\\s+(.+)","level_group":1,"message_group":3,"description":"Timestamped Copilot CLI messages"}
+ ]
+
network: defaults
safe-outputs:
add-labels:
max: 5
+ target: "*"
add-comment:
+ target: "*"
tools:
web-fetch: