From 9d80a262c7b3bad095a6631e02d6aa58be084aac Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Fri, 7 Aug 2026 15:57:35 +0000 Subject: [PATCH 01/23] Harden safe-output field validation Allow only schema-declared fields through safe-output validation and move trusted transport metadata out of agent-controlled NDJSON. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5c72be66-bc49-4422-8e20-5567e99015f1 --- actions/setup/js/collect_ndjson_output.cjs | 17 ++-- .../setup/js/collect_ndjson_output.test.cjs | 23 ++--- actions/setup/js/commit_sha_helpers.cjs | 16 ++++ actions/setup/js/commit_sha_helpers.test.cjs | 17 +++- actions/setup/js/create_pull_request.cjs | 12 ++- actions/setup/js/create_pull_request.test.cjs | 13 +-- actions/setup/js/generate_git_patch.cjs | 17 +++- .../setup/js/push_to_pull_request_branch.cjs | 45 ++-------- .../js/push_to_pull_request_branch.test.cjs | 65 ++++++-------- .../setup/js/safe_output_type_validator.cjs | 7 +- .../js/safe_output_type_validator.test.cjs | 53 +++++++---- actions/setup/js/safe_outputs_handlers.cjs | 6 +- .../setup/js/safe_outputs_handlers.test.cjs | 4 +- actions/setup/js/safe_outputs_tools.json | 5 -- actions/setup/js/upload_assets.cjs | 40 ++++++--- actions/setup/js/upload_assets.test.cjs | 32 +++++++ pkg/workflow/js/safe_outputs_tools.json | 5 -- .../safe_output_validation_config_test.go | 29 ++++++ .../safe_outputs_validation_config.go | 90 ++++++++++++++----- 19 files changed, 321 insertions(+), 175 deletions(-) diff --git a/actions/setup/js/collect_ndjson_output.cjs b/actions/setup/js/collect_ndjson_output.cjs index 0f01abf5d24..c00e3744a12 100644 --- a/actions/setup/js/collect_ndjson_output.cjs +++ b/actions/setup/js/collect_ndjson_output.cjs @@ -112,12 +112,12 @@ async function main() { } function validateItemWithSafeJobConfig(item, jobConfig, lineNum) { const errors = []; - const normalizedItem = { ...item }; + const normalizedItem = { type: item.type }; if (!jobConfig.inputs) { return { isValid: true, errors: [], - normalizedItem: item, + normalizedItem, }; } for (const [fieldName, inputSchema] of Object.entries(jobConfig.inputs)) { @@ -380,16 +380,13 @@ async function main() { continue; } const safeJobConfig = jobOutputType; - if (safeJobConfig && safeJobConfig.inputs) { - const validation = validateItemWithSafeJobConfig(item, safeJobConfig, i + 1); - if (!validation.isValid) { - errors.push(...validation.errors); - continue; - } - Object.assign(item, validation.normalizedItem); + const validation = validateItemWithSafeJobConfig(item, safeJobConfig, i + 1); + if (!validation.isValid) { + errors.push(...validation.errors); + continue; } core.info(`Line ${i + 1}: Valid ${itemType} item`); - parsedItems.push(item); + parsedItems.push(validation.normalizedItem); } } catch (error) { const errorMsg = getErrorMessage(error); diff --git a/actions/setup/js/collect_ndjson_output.test.cjs b/actions/setup/js/collect_ndjson_output.test.cjs index ab3762d4b7c..fe5190c1d2e 100644 --- a/actions/setup/js/collect_ndjson_output.test.cjs +++ b/actions/setup/js/collect_ndjson_output.test.cjs @@ -61,6 +61,12 @@ describe("collect_ndjson_output.cjs", () => { }, }, add_comment: { defaultMax: 1, fields: { body: { required: !0, type: "string", sanitize: !0, maxLength: 65e3 }, item_number: { issueOrPRNumber: !0 } } }, + add_labels: { defaultMax: 5, fields: { labels: { required: !0, type: "array" }, item_number: { issueNumberOrTemporaryId: !0 } } }, + assign_milestone: { + defaultMax: 1, + customValidation: "requiresOneOf:milestone_number,milestone_title", + fields: { issue_number: { issueNumberOrTemporaryId: !0 }, milestone_number: { optionalPositiveInteger: !0 }, milestone_title: { type: "string", sanitize: !0, maxLength: 128 } }, + }, create_pull_request: { defaultMax: 1, fields: { @@ -258,7 +264,7 @@ describe("collect_ndjson_output.cjs", () => { it("should preserve Slack mrkdwn links in custom safe-job string inputs", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; const slackText = "Tracking issue: "; - const ndjsonContent = JSON.stringify({ type: "post_to_slack", text: slackText }); + const ndjsonContent = JSON.stringify({ type: "post_to_slack", text: slackText, channel: "security-alerts" }); fs.writeFileSync(testFile, ndjsonContent); process.env.GH_AW_SAFE_OUTPUTS = testFile; fs.writeFileSync("/tmp/gh-aw/safeoutputs/config.json", JSON.stringify({ post_to_slack: { inputs: { text: { type: "string", required: true } } } })); @@ -881,9 +887,9 @@ describe("collect_ndjson_output.cjs", () => { const parsedOutput = JSON.parse(outputCall[1]); (expect(parsedOutput.items).toHaveLength(1), expect(parsedOutput.items[0].type).toBe("create_issue"), - expect(parsedOutput.items[0].priority).toBe(5), - expect(parsedOutput.items[0].urgent).toBe(!0), - expect(parsedOutput.items[0].assignee).toBe(null), + expect(parsedOutput.items[0]).not.toHaveProperty("priority"), + expect(parsedOutput.items[0]).not.toHaveProperty("urgent"), + expect(parsedOutput.items[0]).not.toHaveProperty("assignee"), expect(parsedOutput.errors).toHaveLength(0)); }), it("should attempt repair but fail gracefully with excessive malformed JSON", async () => { @@ -928,12 +934,7 @@ describe("collect_ndjson_output.cjs", () => { outputCall = setOutputCalls.find(call => "output" === call[0]); expect(outputCall).toBeDefined(); const parsedOutput = JSON.parse(outputCall[1]); - (expect(parsedOutput.items).toHaveLength(1), - expect(parsedOutput.items[0].type).toBe("create_issue"), - expect(parsedOutput.items[0].metadata).toBeDefined(), - expect(parsedOutput.items[0].metadata.project).toBe("test"), - expect(parsedOutput.items[0].metadata.tags).toEqual(["important", "urgent"]), - expect(parsedOutput.errors).toHaveLength(0)); + (expect(parsedOutput.items).toHaveLength(1), expect(parsedOutput.items[0].type).toBe("create_issue"), expect(parsedOutput.items[0]).not.toHaveProperty("metadata"), expect(parsedOutput.errors).toHaveLength(0)); }), it("should handle complex backslash scenarios with graceful failure", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt", @@ -1052,7 +1053,7 @@ describe("collect_ndjson_output.cjs", () => { (expect(parsedOutput.items).toHaveLength(1), expect(parsedOutput.items[0].type).toBe("create_issue"), expect(parsedOutput.items[0].title).toBe("Combined issues"), - expect(parsedOutput.items[0].priority).toBe(1), + expect(parsedOutput.items[0]).not.toHaveProperty("priority"), expect(parsedOutput.errors).toHaveLength(0)); })); }), diff --git a/actions/setup/js/commit_sha_helpers.cjs b/actions/setup/js/commit_sha_helpers.cjs index f3f34c3c1b4..d477529001c 100644 --- a/actions/setup/js/commit_sha_helpers.cjs +++ b/actions/setup/js/commit_sha_helpers.cjs @@ -15,6 +15,22 @@ function normalizeCommitSHA(value) { return GIT_COMMIT_SHA_PATTERN.test(normalized) ? normalized : ""; } +/** + * Extract the trusted base commit embedded in the generated patch artifact. + * + * @param {unknown} patchContent + * @returns {string} + */ +function extractPatchBaseCommit(patchContent) { + if (typeof patchContent !== "string") { + return ""; + } + const headerBlock = patchContent.split(/\r?\n\r?\n/, 1)[0]; + const match = headerBlock.match(/^X-GH-AW-Base-Commit:\s*([0-9a-fA-F]{7,40})\s*$/m); + return normalizeCommitSHA(match?.[1]); +} + module.exports = { + extractPatchBaseCommit, normalizeCommitSHA, }; diff --git a/actions/setup/js/commit_sha_helpers.test.cjs b/actions/setup/js/commit_sha_helpers.test.cjs index 9f0d9c53089..0f8c1a4f5ba 100644 --- a/actions/setup/js/commit_sha_helpers.test.cjs +++ b/actions/setup/js/commit_sha_helpers.test.cjs @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { createRequire } from "module"; const require = createRequire(import.meta.url); -const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); +const { extractPatchBaseCommit, normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); describe("normalizeCommitSHA", () => { it("accepts valid commit SHAs and trims whitespace", () => { @@ -11,6 +11,21 @@ describe("normalizeCommitSHA", () => { expect(normalizeCommitSHA("a".repeat(40))).toBe("a".repeat(40)); }); + describe("extractPatchBaseCommit", () => { + it("extracts a validated base commit from patch metadata", () => { + expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\nX-GH-AW-Base-Commit: deadbeef\nFrom: Test\n")).toBe("deadbeef"); + }); + + it("ignores missing or malformed patch metadata", () => { + expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\nFrom: Test\n")).toBe(""); + expect(extractPatchBaseCommit("X-GH-AW-Base-Commit: main\n")).toBe(""); + }); + + it("ignores metadata-like lines outside the patch header block", () => { + expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\nFrom: Test\n\nX-GH-AW-Base-Commit: deadbeef\n")).toBe(""); + }); + }); + it("rejects invalid commit references", () => { expect(normalizeCommitSHA("main")).toBe(""); expect(normalizeCommitSHA("--upload-pack=/bin/echo")).toBe(""); diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index 74c3336d67c..199f62dcafe 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -33,7 +33,7 @@ const { renderTemplateFromFile, renderFilesList, buildProtectedFileList, getProm const { withGitHubHostToken } = require("./git_auth_helpers.cjs"); const { COPILOT_REVIEWER_BOT, FAQ_CREATE_PR_PERMISSIONS_URL } = require("./constants.cjs"); const { isStagedMode } = require("./safe_output_helpers.cjs"); -const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); +const { extractPatchBaseCommit } = require("./commit_sha_helpers.cjs"); const { withRetry, RATE_LIMIT_RETRY_CONFIG } = require("./error_recovery.cjs"); const { findAgent, getIssueDetails, assignAgentToIssue } = require("./assign_agent_helpers.cjs"); const { ensureFullHistoryForBundle, extractBundlePrerequisiteCommits, getBundlePrerequisites, isShallowOrSparseCheckout, linearizeRangeAsCommit } = require("./git_helpers.cjs"); @@ -1882,10 +1882,10 @@ gh pr create --title '${title}' --base ${baseBranch} --head ${getPullRequestHead // Handle branch creation/checkout let branchBaseRef = baseBranch; - const recordedBaseCommit = normalizeCommitSHA(pullRequestItem.base_commit); + const recordedBaseCommit = extractPatchBaseCommit(patchContent); if (recordedBaseCommit) { core.info(`Patch route base_commit resolved: ${recordedBaseCommit}`); - core.info(`Using base_commit from safe output entry for patch apply: ${recordedBaseCommit}`); + core.info(`Using base_commit embedded in the patch for patch apply: ${recordedBaseCommit}`); try { try { await exec.exec("git", ["fetch", "origin", recordedBaseCommit, "--depth=1"]); @@ -1901,8 +1901,6 @@ gh pr create --title '${title}' --base ${baseBranch} --head ${getPullRequestHead } catch (baseCommitError) { core.warning(`Recorded base_commit ${recordedBaseCommit} is not available in this checkout (${getErrorMessage(baseCommitError)}); falling back to ${baseBranch}`); } - } else if (String(pullRequestItem.base_commit ?? "").trim()) { - core.warning(`Ignoring invalid base_commit value for patch apply: ${String(pullRequestItem.base_commit).trim()}`); } core.info(`Branch should not exist locally, creating new branch from base: ${branchName} (${branchBaseRef})`); await exec.exec("git", ["checkout", "-b", branchName, branchBaseRef]); @@ -1997,9 +1995,9 @@ gh pr create --title '${title}' --base ${baseBranch} --head ${getPullRequestHead // Use the base commit recorded at patch generation time. // The From header in format-patch output contains the agent's new commit SHA // which does not exist in this checkout, so we cannot derive the base from it. - const originalBaseCommit = normalizeCommitSHA(pullRequestItem.base_commit); + const originalBaseCommit = extractPatchBaseCommit(patchContent); if (!originalBaseCommit) { - core.warning("No base_commit recorded in safe output entry - fallback not possible"); + core.warning("No base_commit embedded in patch - fallback not possible"); } else { core.info(`Original base commit from patch generation: ${originalBaseCommit}`); diff --git a/actions/setup/js/create_pull_request.test.cjs b/actions/setup/js/create_pull_request.test.cjs index e17d52ec36d..7c968fc7342 100644 --- a/actions/setup/js/create_pull_request.test.cjs +++ b/actions/setup/js/create_pull_request.test.cjs @@ -55,6 +55,7 @@ function ensureDefaultDisclosureHeaderPrompt() { beforeEach(() => { cleanupCanonicalTransports(); ensureDefaultDisclosureHeaderPrompt(); + process.env.GH_AW_PROMPTS_DIR = promptsSourceDir; }); afterEach(() => { cleanupCanonicalTransports(); @@ -2848,6 +2849,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( // Minimal valid format-patch output const PATCH_CONTENT = `From a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 Mon Sep 17 00:00:00 2001\n` + + `X-GH-AW-Base-Commit: ${MOCK_BASE_COMMIT_SHA}\n` + `From: Test Author \n` + `Date: Wed, 26 Mar 2026 12:00:00 +0000\n` + `Subject: [PATCH] Test change\n\n` + @@ -2868,7 +2870,6 @@ describe("create_pull_request - patch apply fallback to original base commit", ( process.env.GH_AW_WORKFLOW_ID = "test-workflow"; process.env.GITHUB_REPOSITORY = "test-owner/test-repo"; process.env.GITHUB_BASE_REF = "main"; - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "create-pr-fallback-test-")); patchFilePath = path.join(tempDir, "test.patch"); fs.writeFileSync(patchFilePath, PATCH_CONTENT, "utf8"); @@ -2963,7 +2964,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( return false; } - it("should create the PR branch from normalized base_commit before applying the patch when available", async () => { + it("should create the PR branch from the patch-embedded base commit when available", async () => { global.exec = { exec: vi.fn().mockResolvedValue(0), getExecOutput: vi.fn().mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" }), @@ -2979,7 +2980,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( expect(checkoutWithBaseCommit).toBeTruthy(); }); - it("should ignore invalid base_commit values when creating the branch", async () => { + it("should ignore agent-supplied base_commit values when creating the branch", async () => { global.exec = { exec: vi.fn().mockResolvedValue(0), getExecOutput: vi.fn().mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" }), @@ -2991,7 +2992,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( expect(result.success).toBe(true); expect(global.exec.exec).not.toHaveBeenCalledWith("git", ["cat-file", "-e", "not-a-sha --bad"]); - expect(global.core.warning).toHaveBeenCalledWith("Ignoring invalid base_commit value for patch apply: not-a-sha --bad"); + expect(global.core.warning).not.toHaveBeenCalledWith(expect.stringContaining("base_commit")); }); it("should fall back to base branch when base_commit is unavailable", async () => { @@ -3221,6 +3222,8 @@ describe("create_pull_request - patch apply fallback to original base commit", ( }); it("should return error when no base_commit is provided and git am --3way fails", async () => { + const patchWithoutBaseCommit = PATCH_CONTENT.replace(`X-GH-AW-Base-Commit: ${MOCK_BASE_COMMIT_SHA}\n`, ""); + fs.writeFileSync(canonicalPatchPath("test-branch"), patchWithoutBaseCommit, "utf8"); global.exec = { exec: vi.fn().mockImplementation((cmd, args) => { if (isGitAm3Way(cmd, args)) { @@ -3241,7 +3244,7 @@ describe("create_pull_request - patch apply fallback to original base commit", ( expect(result.success).toBe(false); expect(result.error).toBe("Failed to apply patch"); - expect(global.core.warning).toHaveBeenCalledWith("No base_commit recorded in safe output entry - fallback not possible"); + expect(global.core.warning).toHaveBeenCalledWith("No base_commit embedded in patch - fallback not possible"); }); it("should reuse existing remote branch when preserve-branch-name and recreate-ref are true (force-delete then recreate)", async () => { diff --git a/actions/setup/js/generate_git_patch.cjs b/actions/setup/js/generate_git_patch.cjs index edbf0cb58df..215e7aaeeb7 100644 --- a/actions/setup/js/generate_git_patch.cjs +++ b/actions/setup/js/generate_git_patch.cjs @@ -29,6 +29,17 @@ function debugLog(message) { } } +function embedBaseCommit(patchContent, baseCommitSha) { + if (!baseCommitSha || typeof patchContent !== "string") { + return patchContent; + } + const firstNewline = patchContent.indexOf("\n"); + if (firstNewline < 0) { + return patchContent; + } + return `${patchContent.slice(0, firstNewline + 1)}X-GH-AW-Base-Commit: ${baseCommitSha}\n${patchContent.slice(firstNewline + 1)}`; +} + /** * Generates a git patch file for the current changes * @param {string} branchName - The branch name to generate patch for @@ -290,7 +301,7 @@ async function generateGitPatch(branchName, baseBranch, options = {}) { const patchContent = execGitSync(["format-patch", `${baseRef}..${tipRef}`, "--stdout", ...excludeArgs()], { cwd }); if (patchContent && patchContent.trim()) { - fs.writeFileSync(patchPath, patchContent, "utf8"); + fs.writeFileSync(patchPath, embedBaseCommit(patchContent, baseCommitSha), "utf8"); patchGenerated = true; debugLog(`Strategy 1: SUCCESS - Generated patch with ${patchContent.split("\n").length} lines`); } @@ -407,7 +418,7 @@ async function generateGitPatch(branchName, baseBranch, options = {}) { const patchContent = execGitSync(["format-patch", `${githubSha}..HEAD`, "--stdout", ...excludeArgs()], { cwd }); if (patchContent && patchContent.trim()) { - fs.writeFileSync(patchPath, patchContent, "utf8"); + fs.writeFileSync(patchPath, embedBaseCommit(patchContent, baseCommitSha), "utf8"); patchGenerated = true; debugLog(`Strategy 2: SUCCESS - Generated patch with ${patchContent.split("\n").length} lines`); } @@ -490,7 +501,7 @@ async function generateGitPatch(branchName, baseBranch, options = {}) { const patchContent = execGitSync(["format-patch", `${bestBaseCommit}..${branchName}`, "--stdout", ...excludeArgs()], { cwd }); if (patchContent && patchContent.trim()) { - fs.writeFileSync(patchPath, patchContent, "utf8"); + fs.writeFileSync(patchPath, embedBaseCommit(patchContent, baseCommitSha), "utf8"); patchGenerated = true; debugLog(`Strategy 3: SUCCESS - Generated patch with ${patchContent.split("\n").length} lines`); } diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 4f797edd3f6..a742c0b3f7d 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -19,7 +19,7 @@ const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); const { renderTemplateFromFile, buildProtectedFileList, getPromptPath } = require("./messages_core.cjs"); const { withGitHubHostToken } = require("./git_auth_helpers.cjs"); const { ensureFullHistoryForBundle, extractBundlePrerequisiteCommits, isShallowOrSparseCheckout, linearizeRangeAsCommit, ensureSafeDirectoryTrust } = require("./git_helpers.cjs"); -const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); +const { extractPatchBaseCommit } = require("./commit_sha_helpers.cjs"); const { findRepoCheckout } = require("./find_repo_checkout.cjs"); const { getThreatWarningPresentation } = require("./threat_detection_warning.cjs"); const { attachExecutionState } = require("./safe_output_execution_metadata.cjs"); @@ -432,15 +432,8 @@ async function main(config = {}) { // Validate patch/bundle size against `max_patch_size`. // // Size-check source of truth, in order of preference: - // 1. `message.diff_size` — the incremental net diff size recorded at - // patch/bundle generation time (this is the correct quantity to cap: - // how much the PR branch will actually change as a result of the push). - // 2. For bundle transport: the on-disk bundle file size. - // 3. For patch transport: the format-patch file size. - // - // Using `diff_size` when present fixes the long-running branch case where - // the transport file accumulates per-commit metadata + per-commit diffs and - // can be many MB even when each iteration only changes a few KB. + // Use the uncompressed patch representation for both transport modes. + // Bundle size is compressed and can undercount highly compressible changes. if (!isEmpty) { const patchSizeBytes = Buffer.byteLength(patchContent, "utf8"); const patchSizeKb = Math.ceil(patchSizeBytes / 1024); @@ -455,21 +448,8 @@ async function main(config = {}) { } const bundleSizeKb = Math.ceil(bundleSizeBytes / 1024); - const diffSizeBytesRaw = message.diff_size; - const haveDiffSize = typeof diffSizeBytesRaw === "number" && diffSizeBytesRaw >= 0; - - let sizeForCheckBytes; - let sizeLabel; - if (haveDiffSize) { - sizeForCheckBytes = diffSizeBytesRaw; - sizeLabel = "Incremental diff size"; - } else if (hasBundleFile) { - sizeForCheckBytes = bundleSizeBytes; - sizeLabel = "Bundle size"; - } else { - sizeForCheckBytes = patchSizeBytes; - sizeLabel = "Patch size"; - } + const sizeForCheckBytes = patchSizeBytes; + const sizeLabel = "Patch size"; const sizeForCheckKb = Math.ceil(sizeForCheckBytes / 1024); if (hasBundleFile) { @@ -481,14 +461,7 @@ async function main(config = {}) { if (sizeForCheckKb > maxSizeKb) { let msg; - if (haveDiffSize) { - const transportLabel = hasBundleFile ? `Bundle size: ${bundleSizeKb} KB` : `Patch file size: ${patchSizeKb} KB`; - msg = `Incremental diff size (${sizeForCheckKb} KB) exceeds maximum allowed size (${maxSizeKb} KB). ${transportLabel}.`; - } else if (hasBundleFile) { - msg = `Bundle size (${sizeForCheckKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`; - } else { - msg = `Patch size (${sizeForCheckKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`; - } + msg = `Patch size (${sizeForCheckKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`; return { success: false, error: msg }; } @@ -965,8 +938,8 @@ async function main(config = {}) { // Pin patch application to the recorded base commit captured at patch-generation time. // This avoids applying a patch generated from an older branch tip onto a newer remote tip. // If the commit is unavailable (e.g. cross-repo/missing object), continue with current HEAD. - if (!hasBundleFile && message.base_commit) { - const recordedBaseCommit = normalizeCommitSHA(message.base_commit); + if (!hasBundleFile) { + const recordedBaseCommit = extractPatchBaseCommit(patchContent); if (recordedBaseCommit) { core.info(`Patch route base_commit resolved: ${recordedBaseCommit}`); try { @@ -992,8 +965,6 @@ async function main(config = {}) { } catch (baseCommitError) { core.warning(`Unable to use recorded base_commit ${recordedBaseCommit}; applying patch on current branch HEAD: ${getErrorMessage(baseCommitError)}`); } - } else if (String(message.base_commit).trim()) { - core.warning(`Ignoring invalid base_commit value for patch apply: ${String(message.base_commit).trim()}`); } } diff --git a/actions/setup/js/push_to_pull_request_branch.test.cjs b/actions/setup/js/push_to_pull_request_branch.test.cjs index ab9849b8e7c..190abc87261 100644 --- a/actions/setup/js/push_to_pull_request_branch.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.test.cjs @@ -5,6 +5,7 @@ import * as os from "os"; const { getPatchPathForBranch, getPatchPathForBranchInRepo } = require("./git_patch_utils.cjs"); const { getBundlePathForBranch, getBundlePathForBranchInRepo } = require("./generate_git_bundle.cjs"); +const promptsSourceDir = path.resolve(__dirname, "../md"); // The privileged handler derives patch/bundle paths from `branch` (and `repo`) // via resolveTransportPaths, so tests must write transport files at the @@ -38,6 +39,7 @@ function cleanupCanonicalTransports() { beforeEach(() => { cleanupCanonicalTransports(); + process.env.GH_AW_PROMPTS_DIR = promptsSourceDir; }); afterEach(() => { cleanupCanonicalTransports(); @@ -309,7 +311,7 @@ describe("push_to_pull_request_branch.cjs", () => { * message branch. The privileged handler always re-derives the patch path * from the validated branch, so tests must write at that canonical location. */ - function createPatchFile(branch, content = null) { + function createPatchFile(branch, content = null, baseCommit = "") { const patchPath = canonicalPatchPath(branch); const defaultPatch = `From abc123 Mon Sep 17 00:00:00 2001 From: Test Author @@ -328,7 +330,12 @@ index 0000000..abc1234 -- 2.34.1 `; - fs.writeFileSync(patchPath, content !== null ? content : defaultPatch); + let patchContent = content !== null ? content : defaultPatch; + if (baseCommit) { + const firstNewline = patchContent.indexOf("\n"); + patchContent = `${patchContent.slice(0, firstNewline + 1)}X-GH-AW-Base-Commit: ${baseCommit}\n${patchContent.slice(firstNewline + 1)}`; + } + fs.writeFileSync(patchPath, patchContent); return patchPath; } @@ -903,9 +910,9 @@ index 0000000..abc1234 expect(result.after_state).toEqual({ head_sha: "abc123" }); }); - it("should reset to message.base_commit before applying patch transport", async () => { - const patchPath = createPatchFile("should-reset-to-message-base-commit-before-applying-patch-tr"); + it("should reset to the patch-embedded base commit before applying patch transport", async () => { const recordedBaseCommit = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + const patchPath = createPatchFile("should-reset-to-message-base-commit-before-applying-patch-tr", null, recordedBaseCommit); mockExec.getExecOutput.mockResolvedValue({ exitCode: 0, stdout: "abc123\n", stderr: "" }); const pushSignedCommitsModule = require("./push_signed_commits.cjs"); const pushSignedSpy = vi.spyOn(pushSignedCommitsModule, "pushSignedCommits").mockResolvedValue("abc123"); @@ -913,7 +920,7 @@ index 0000000..abc1234 try { const module = await loadModule(); const handler = await module.main({}); - const result = await handler({ base_commit: recordedBaseCommit, branch: "should-reset-to-message-base-commit-before-applying-patch-tr" }, {}); + const result = await handler({ branch: "should-reset-to-message-base-commit-before-applying-patch-tr" }, {}); expect(result.success).toBe(true); expect(mockExec.exec).toHaveBeenCalledWith("git", ["cat-file", "-e", recordedBaseCommit], expect.any(Object)); @@ -924,9 +931,9 @@ index 0000000..abc1234 } }); - it("should fall back to current HEAD when base_commit is unavailable for patch transport", async () => { - const patchPath = createPatchFile("should-fall-back-to-current-head-when-base-commit-is-unavail"); + it("should fall back to current HEAD when the patch-embedded base commit is unavailable", async () => { const recordedBaseCommit = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + const patchPath = createPatchFile("should-fall-back-to-current-head-when-base-commit-is-unavail", null, recordedBaseCommit); mockExec.getExecOutput.mockResolvedValue({ exitCode: 0, stdout: "abc123\n", stderr: "" }); mockExec.exec.mockImplementation(async (cmd, args) => { if (cmd === "git" && Array.isArray(args) && args[0] === "cat-file" && args[1] === "-e" && args[2] === recordedBaseCommit) { @@ -941,7 +948,7 @@ index 0000000..abc1234 try { const module = await loadModule(); const handler = await module.main({}); - const result = await handler({ base_commit: recordedBaseCommit, branch: "should-fall-back-to-current-head-when-base-commit-is-unavail" }, {}); + const result = await handler({ branch: "should-fall-back-to-current-head-when-base-commit-is-unavail" }, {}); expect(result.success).toBe(true); expect(mockExec.exec).not.toHaveBeenCalledWith("git", ["reset", "--hard", recordedBaseCommit], expect.any(Object)); @@ -951,7 +958,7 @@ index 0000000..abc1234 } }); - it("should ignore invalid message.base_commit for patch transport", async () => { + it("should ignore agent-supplied base_commit for patch transport", async () => { const patchPath = createPatchFile("should-ignore-invalid-message-base-commit-for-patch-transpor"); mockExec.getExecOutput.mockResolvedValue({ exitCode: 0, stdout: "abc123\n", stderr: "" }); @@ -962,7 +969,7 @@ index 0000000..abc1234 expect(result.success).toBe(true); expect(mockExec.exec).not.toHaveBeenCalledWith("git", ["cat-file", "-e", "not-a-sha --bad"], expect.any(Object)); expect(mockExec.exec).not.toHaveBeenCalledWith("git", ["reset", "--hard", "not-a-sha --bad"], expect.any(Object)); - expect(mockCore.warning).toHaveBeenCalledWith("Ignoring invalid base_commit value for patch apply: not-a-sha --bad"); + expect(mockCore.warning).not.toHaveBeenCalledWith(expect.stringContaining("base_commit")); }); it("should use pushed commit SHA returned by pushSignedCommits for activation comment commit link", async () => { @@ -2059,11 +2066,7 @@ index 0000000..abc1234 expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); }); - it("should prefer message.diff_size (incremental net diff) over patch file size", async () => { - // Simulate the long-running branch case: a large format-patch file - // (e.g. 2 MB of cumulative commit metadata + per-commit diffs) but a - // tiny incremental net diff (e.g. 5 KB of actual changes since - // origin/). The size check must use diff_size and accept the push. + it("should ignore message.diff_size and enforce the patch file size", async () => { const largePatch = "x".repeat(2 * 1024 * 1024); // 2 MB format-patch file const patchPath = createPatchFile("should-prefer-message-diff-size-incremental-net-diff-over-pa", largePatch); @@ -2073,25 +2076,19 @@ index 0000000..abc1234 const handler = await module.main({ max_patch_size: 1024 }); // 1 MB max const result = await handler({ diff_size: 5 * 1024, branch: "should-prefer-message-diff-size-incremental-net-diff-over-pa" }, {}); - expect(result.success).toBe(true); - expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); - // Verify the size check used the incremental (diff_size) value, not the - // 2 MB file size. - expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Incremental diff size: 5 KB")); + expect(result.success).toBe(false); + expect(result.error).toContain("Patch size"); }); - it("should reject when message.diff_size exceeds max size even if file size is small", async () => { - // Inverse case: small file (defensive — shouldn't happen in practice) - // but a recorded large diff_size should still cause rejection. This - // proves diff_size is the source of truth for the size check. + it("should ignore an oversized message.diff_size when the patch is within limits", async () => { const patchPath = createPatchFile("should-reject-when-message-diff-size-exceeds-max-size-even-i"); // small valid patch const module = await loadModule(); const handler = await module.main({ max_patch_size: 1024 }); // 1 MB max const result = await handler({ diff_size: 2 * 1024 * 1024, branch: "should-reject-when-message-diff-size-exceeds-max-size-even-i" }, {}); - expect(result.success).toBe(false); - expect(result.error).toContain("exceeds maximum"); + expect(result.success).toBe(true); + expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); }); it("should fall back to patch file size when message.diff_size is not provided", async () => { @@ -2109,9 +2106,7 @@ index 0000000..abc1234 expect(result.error).toContain("exceeds maximum"); }); - it("should enforce max_patch_size against bundle size when bundle transport is used", async () => { - // Bundle transport still includes a patch for policy checks, but the size - // guard falls back to bundle size when diff_size is not provided. + it("should enforce max_patch_size against the uncompressed patch for bundle transport", async () => { const bundlePath = canonicalBundlePath("should-enforce-max-patch-size-against-bundle-size-when-bundl"); const patchPath = createPatchFile("should-enforce-max-patch-size-against-bundle-size-when-bundl", "small patch content"); // 2 MB dummy bundle file (contents don't matter; only size is checked) @@ -2121,15 +2116,11 @@ index 0000000..abc1234 const handler = await module.main({ max_patch_size: 1024 }); // 1 MB max const result = await handler({ branch: "should-enforce-max-patch-size-against-bundle-size-when-bundl" }, {}); - expect(result.success).toBe(false); - expect(result.error).toContain("exceeds maximum"); - expect(result.error).toMatch(/Bundle size|Incremental diff size/); + expect(result.success).toBe(true); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Patch size: 1 KB")); }); - it("should prefer diff_size over bundle file size for the limit check", async () => { - // Bundle is 2 MB on disk, but the incremental net diff is only 5 KB: - // the check must accept the push (limit reflects the real change, not the - // compressed transport size). + it("should ignore diff_size for bundle size checks", async () => { const bundlePath = canonicalBundlePath("should-prefer-diff-size-over-bundle-file-size-for-the-limit-"); const patchPath = createPatchFile("should-prefer-diff-size-over-bundle-file-size-for-the-limit-", "small patch content"); fs.writeFileSync(bundlePath, Buffer.alloc(2 * 1024 * 1024)); @@ -2142,7 +2133,7 @@ index 0000000..abc1234 expect(result.success).toBe(true); expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); - expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Incremental diff size: 5 KB")); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Patch size: 1 KB")); }); }); diff --git a/actions/setup/js/safe_output_type_validator.cjs b/actions/setup/js/safe_output_type_validator.cjs index 1900088f186..4b8d2d7a1ed 100644 --- a/actions/setup/js/safe_output_type_validator.cjs +++ b/actions/setup/js/safe_output_type_validator.cjs @@ -703,7 +703,10 @@ function validateItem(item, itemType, lineNum, options) { return { isValid: true, normalizedItem: item }; } - const normalizedItem = { ...item }; + // Build the downstream payload from the declared contract. The raw item is + // agent-controlled, so forwarding undeclared fields would let consumers act + // on values that were never validated. + const normalizedItem = { type: item.type }; const errors = []; // Run custom validation first if defined @@ -729,6 +732,8 @@ function validateItem(item, itemType, lineNum, options) { } } else if (result.normalizedValue !== undefined) { normalizedItem[fieldName] = result.normalizedValue; + } else if (fieldValue !== undefined) { + normalizedItem[fieldName] = fieldValue; } } diff --git a/actions/setup/js/safe_output_type_validator.test.cjs b/actions/setup/js/safe_output_type_validator.test.cjs index 3784f4844be..8a7f6ff4c14 100644 --- a/actions/setup/js/safe_output_type_validator.test.cjs +++ b/actions/setup/js/safe_output_type_validator.test.cjs @@ -113,6 +113,24 @@ const SAMPLE_VALIDATION_CONFIG = { repo: { type: "string", maxLength: 256 }, }, }, + upload_asset: { + defaultMax: 10, + fields: { + path: { required: true, type: "string" }, + }, + }, + close_issue: { + defaultMax: 1, + fields: { + issue_number: { optionalPositiveInteger: true }, + }, + }, + push_to_pull_request_branch: { + defaultMax: 1, + fields: { + message: { required: true, type: "string", sanitize: true, maxLength: 65000 }, + }, + }, set_issue_type: { defaultMax: 5, fields: { @@ -1343,8 +1361,8 @@ describe("safe_output_type_validator", () => { }); }); - describe("undeclared field passthrough", () => { - it("should preserve base_commit on normalizedItem", async () => { + describe("undeclared fields", () => { + it("should preserve the normalized type and declared fields", async () => { const { validateItem } = await import("./safe_output_type_validator.cjs"); const item = { @@ -1352,31 +1370,32 @@ describe("safe_output_type_validator", () => { title: "Fix bug", body: "Fixes the thing", branch: "fix/bug", - base_commit: "abc123deadbeef", }; const result = validateItem(item, "create_pull_request", 1); expect(result.isValid).toBe(true); - expect(result.normalizedItem.base_commit).toBe("abc123deadbeef"); + expect(result.normalizedItem).toEqual(item); }); - it("should preserve diff_size on normalizedItem", async () => { + it.each([ + { itemType: "add_comment", item: { type: "add_comment", body: "Test comment", comment_id: 123 }, fieldName: "comment_id" }, + { itemType: "update_pull_request", item: { type: "update_pull_request", title: "Updated title", base: "release", state: "closed" }, fieldName: "base" }, + { itemType: "update_pull_request", item: { type: "update_pull_request", title: "Updated title", base: "release", state: "closed" }, fieldName: "state" }, + { itemType: "upload_asset", item: { type: "upload_asset", path: "image.png", targetFileName: "../../.git/config" }, fieldName: "targetFileName" }, + { itemType: "create_issue", item: { type: "create_issue", title: "Test", body: "Detailed issue body text.", assignees: ["octocat"] }, fieldName: "assignees" }, + { itemType: "create_discussion", item: { type: "create_discussion", title: "Test", body: "This discussion body is intentionally long enough for validation.", labels: ["security"] }, fieldName: "labels" }, + { itemType: "close_issue", item: { type: "close_issue", issue_number: 1, state_reason: "not_planned" }, fieldName: "state_reason" }, + { itemType: "push_to_pull_request_branch", item: { type: "push_to_pull_request_branch", message: "Apply changes", diff_size: 0 }, fieldName: "diff_size" }, + { itemType: "create_pull_request", item: { type: "create_pull_request", title: "Fix bug", body: "Fixes the thing", branch: "fix/bug", base_commit: "abc123deadbeef" }, fieldName: "base_commit" }, + ])("should strip undeclared $itemType.$fieldName", async ({ itemType, item, fieldName }) => { const { validateItem } = await import("./safe_output_type_validator.cjs"); - const item = { - type: "create_pull_request", - title: "Fix bug", - body: "Fixes the thing", - branch: "fix/bug", - diff_size: 1, - }; - - const result = validateItem(item, "create_pull_request", 1); + const result = validateItem(item, itemType, 1); expect(result.isValid).toBe(true); - expect(result.normalizedItem.diff_size).toBe(1); + expect(result.normalizedItem).not.toHaveProperty(fieldName); }); - it("should preserve undeclared fields", async () => { + it("should preserve enabled structured data", async () => { const { validateItem } = await import("./safe_output_type_validator.cjs"); const item = { @@ -1386,7 +1405,7 @@ describe("safe_output_type_validator", () => { data: { project: "test" }, }; - const result = validateItem(item, "create_issue", 1); + const result = validateItem(item, "create_issue", 1, { dataEnabled: true }); expect(result.isValid).toBe(true); expect(result.normalizedItem.data).toEqual({ project: "test" }); }); diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 37b7b5d5be3..563e445248e 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -526,8 +526,10 @@ function createHandlers(server, appendSafeOutput, config = {}) { const fileName = path.basename(filePath); const fileExt = path.extname(fileName).toLowerCase(); - // Copy file to assets directory with original name - const targetPath = path.join(assetsDir, fileName); + // Key the staged file by its declared source path so same-basename assets + // cannot overwrite each other before the privileged publishing job. + const stagedFileName = `${crypto.createHash("sha256").update(filePath).digest("hex")}${fileExt}`; + const targetPath = path.join(assetsDir, stagedFileName); try { fs.copyFileSync(filePath, targetPath); } catch (err) { diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs index cbb607d435a..1da447434fe 100644 --- a/actions/setup/js/safe_outputs_handlers.test.cjs +++ b/actions/setup/js/safe_outputs_handlers.test.cjs @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import crypto from "crypto"; import fs from "fs"; import path from "path"; import { execSync } from "child_process"; @@ -351,7 +352,8 @@ describe("safe_outputs_handlers", () => { // File must be staged under RUNNER_TEMP, not hardcoded /tmp const expectedDir = path.join(testRunnerTemp, "gh-aw", "safeoutputs", "assets"); - expect(fs.existsSync(path.join(expectedDir, "chart.png"))).toBe(true); + const stagedFileName = `${crypto.createHash("sha256").update(testFile).digest("hex")}.png`; + expect(fs.existsSync(path.join(expectedDir, stagedFileName))).toBe(true); }); it("should throw error if GH_AW_ASSETS_BRANCH not set", () => { diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index 03b10fe4553..21d8b00b840 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -327,11 +327,6 @@ "description": "Node ID of the discussion comment to reply to, enabling threaded discussion comments. When provided, the new comment is posted as a reply to the specified top-level discussion comment. If the given node ID belongs to a nested reply, the handler automatically resolves it to the top-level parent. Only applicable for discussion comments \u2014 ignored for issue and pull request comments.", "x-synonyms": ["replyToId"] }, - "comment_id": { - "type": ["number", "string"], - "description": "Existing issue or pull request comment ID to update instead of creating a new comment.", - "x-synonyms": ["commentId"] - }, "target": { "type": "string", "enum": ["status"], diff --git a/actions/setup/js/upload_assets.cjs b/actions/setup/js/upload_assets.cjs index 2e4ce07edcc..ee6873c059e 100644 --- a/actions/setup/js/upload_assets.cjs +++ b/actions/setup/js/upload_assets.cjs @@ -10,7 +10,7 @@ const { ERR_API, ERR_CONFIG, ERR_SYSTEM, ERR_VALIDATION } = require("./error_cod const { normalizeBranchName } = require("./normalize_branch_name.cjs"); /** - * @typedef {{ type: string, fileName: string, sha: string, size: number, targetFileName: string, url?: string }} UploadAssetItem + * @typedef {{ type: string, path?: string, fileName: string, sha: string, size: number, targetFileName: string, url?: string }} UploadAssetItem */ async function main() { @@ -58,6 +58,7 @@ async function main() { let uploadCount = 0; let missingAssetCount = 0; let hasChanges = false; + const processedAssets = []; try { // Check if orphaned branch already exists, if not create it @@ -85,15 +86,23 @@ async function main() { // Process each asset for (const asset of uploadItems) { - const { fileName, sha, size, targetFileName } = asset; + const declaredPath = typeof asset.path === "string" ? asset.path : ""; + const pathFileName = declaredPath ? path.basename(declaredPath) : ""; + const rawFileName = typeof asset.fileName === "string" ? asset.fileName : pathFileName; + const fileName = path.basename(rawFileName); - if (!fileName || !sha || !targetFileName) { + if (!fileName || (asset.fileName && asset.fileName !== fileName)) { + core.setFailed(`${ERR_VALIDATION}: Invalid asset filename: ${JSON.stringify(asset)}`); + return; + } + if (!pathFileName && (!asset.sha || !asset.targetFileName)) { core.setFailed(`${ERR_VALIDATION}: Invalid asset entry missing required fields: ${JSON.stringify(asset)}`); return; } // Check if file exists in the staged-assets directory - const assetSourcePath = path.join(assetsDir, fileName); + const stagedFileName = pathFileName ? `${crypto.createHash("sha256").update(declaredPath).digest("hex")}${path.extname(fileName).toLowerCase()}` : fileName; + const assetSourcePath = path.join(assetsDir, stagedFileName); if (!fs.existsSync(assetSourcePath)) { core.warning(`${ERR_SYSTEM}: Asset file not found: ${assetSourcePath} — skipping`); missingAssetCount++; @@ -104,11 +113,24 @@ async function main() { const fileContent = fs.readFileSync(assetSourcePath); const computedSha = crypto.createHash("sha256").update(fileContent).digest("hex"); - if (computedSha !== sha) { - core.setFailed(`${ERR_VALIDATION}: SHA mismatch for ${fileName}: expected ${sha}, got ${computedSha}`); + if (asset.sha && computedSha !== asset.sha) { + core.setFailed(`${ERR_VALIDATION}: SHA mismatch for ${fileName}: expected ${asset.sha}, got ${computedSha}`); + return; + } + + const generatedTargetFileName = `${computedSha}${path.extname(fileName).toLowerCase()}`; + const targetFileName = asset.targetFileName || generatedTargetFileName; + if (targetFileName !== path.basename(targetFileName)) { + core.setFailed(`${ERR_VALIDATION}: Invalid asset target filename: ${targetFileName}`); return; } + const size = fileContent.length; + const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; + const repo = process.env.GITHUB_REPOSITORY || "owner/repo"; + const url = `${githubServer}/${repo}/blob/${normalizedBranchName}/${targetFileName}?raw=true`; + processedAssets.push({ fileName, sha: computedSha, size, targetFileName, url }); + // Check if file already exists in the branch if (fs.existsSync(targetFileName)) { core.info(`Asset ${targetFileName} already exists, skipping`); @@ -149,10 +171,8 @@ async function main() { core.info(`Successfully uploaded ${uploadCount} assets to branch ${normalizedBranchName}`); } - for (const asset of uploadItems) { - if (asset.fileName && asset.sha && asset.size && asset.url) { - core.summary.addRaw(`- [\`${asset.fileName}\`](${asset.url}) → \`${asset.targetFileName}\` (${asset.size} bytes)`); - } + for (const asset of processedAssets) { + core.summary.addRaw(`- [\`${asset.fileName}\`](${asset.url}) → \`${asset.targetFileName}\` (${asset.size} bytes)`); } await core.summary.write(); } else { diff --git a/actions/setup/js/upload_assets.test.cjs b/actions/setup/js/upload_assets.test.cjs index e04b43ae859..79dbcfb6777 100644 --- a/actions/setup/js/upload_assets.test.cjs +++ b/actions/setup/js/upload_assets.test.cjs @@ -229,6 +229,38 @@ describe("upload_assets.cjs", () => { await executeScript(); expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("missing required fields")); }); + + it("should derive trusted metadata from a validated asset path", async () => { + process.env.GH_AW_ASSETS_BRANCH = "assets/test-workflow"; + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "false"; + const assetDir = getAssetsDir(); + fs.mkdirSync(assetDir, { recursive: true }); + const declaredPath = "/workspace/test.png"; + const stagedFileName = `${crypto.createHash("sha256").update(declaredPath).digest("hex")}.png`; + const { sha } = makeAsset(assetDir, stagedFileName, "actual content"); + trackCwdArtifact(`${sha}.png`); + setAgentOutput({ items: [{ type: "upload_asset", path: declaredPath }] }); + mockBranchMissing(); + + await executeScript(); + expect(mockCore.setFailed).not.toHaveBeenCalled(); + expect(mockCore.setOutput).toHaveBeenCalledWith("upload_count", "1"); + }); + + it("should reject target filenames outside the checkout root", async () => { + process.env.GH_AW_ASSETS_BRANCH = "assets/test-workflow"; + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "false"; + const assetDir = getAssetsDir(); + fs.mkdirSync(assetDir, { recursive: true }); + const { sha, size } = makeAsset(assetDir, "test.png", "actual content"); + setAgentOutput({ + items: [{ type: "upload_asset", fileName: "test.png", sha, size, targetFileName: "../../.git/config" }], + }); + mockBranchMissing(); + + await executeScript(); + expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("Invalid asset target filename")); + }); }); describe("missing asset handling", () => { diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 03b10fe4553..21d8b00b840 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -327,11 +327,6 @@ "description": "Node ID of the discussion comment to reply to, enabling threaded discussion comments. When provided, the new comment is posted as a reply to the specified top-level discussion comment. If the given node ID belongs to a nested reply, the handler automatically resolves it to the top-level parent. Only applicable for discussion comments \u2014 ignored for issue and pull request comments.", "x-synonyms": ["replyToId"] }, - "comment_id": { - "type": ["number", "string"], - "description": "Existing issue or pull request comment ID to update instead of creating a new comment.", - "x-synonyms": ["commentId"] - }, "target": { "type": "string", "enum": ["status"], diff --git a/pkg/workflow/safe_output_validation_config_test.go b/pkg/workflow/safe_output_validation_config_test.go index 83e5c6e1844..4a666f72cce 100644 --- a/pkg/workflow/safe_output_validation_config_test.go +++ b/pkg/workflow/safe_output_validation_config_test.go @@ -413,6 +413,35 @@ func TestValidationConfigConsistency(t *testing.T) { } } +func TestValidationConfigCoversToolInputSchemas(t *testing.T) { + var tools []struct { + Name string `json:"name"` + InputSchema struct { + Properties map[string]json.RawMessage `json:"properties"` + } `json:"inputSchema"` + } + if err := json.Unmarshal([]byte(safeOutputsToolsJSONContent), &tools); err != nil { + t.Fatalf("failed to parse safe outputs tool schema: %v", err) + } + + metadataFields := map[string]bool{"secrecy": true, "integrity": true} + for _, tool := range tools { + config, ok := ValidationConfig[tool.Name] + if !ok { + t.Errorf("%s tool is missing from ValidationConfig", tool.Name) + continue + } + for fieldName := range tool.InputSchema.Properties { + if metadataFields[fieldName] { + continue + } + if _, ok := config.Fields[fieldName]; !ok { + t.Errorf("%s tool input %q is missing from ValidationConfig", tool.Name, fieldName) + } + } + } +} + func TestCreateDiscussionBodyMinLength(t *testing.T) { config, ok := ValidationConfig["create_discussion"] if !ok { diff --git a/pkg/workflow/safe_outputs_validation_config.go b/pkg/workflow/safe_outputs_validation_config.go index 7537adf311b..1a2e236988a 100644 --- a/pkg/workflow/safe_outputs_validation_config.go +++ b/pkg/workflow/safe_outputs_validation_config.go @@ -81,10 +81,14 @@ var ValidationConfig = map[string]TypeValidationConfig{ "add_comment": { DefaultMax: 1, Fields: map[string]FieldValidation{ - "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, - "item_number": {IssueOrPRNumber: true}, - "reply_to_id": {Type: "string", MaxLength: 256}, // Optional: node ID of discussion comment to reply to (threading) - "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" + "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "item_number": {IssueOrPRNumber: true}, + "pr_number": {IssueOrPRNumber: true}, + "pr": {IssueOrPRNumber: true}, + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, + "reply_to_id": {Type: "string", MaxLength: 256}, // Optional: node ID of discussion comment to reply to (threading) + "target": {Type: "string", Enum: []string{"status"}}, + "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" }, }, "comment_memory": { @@ -99,13 +103,14 @@ var ValidationConfig = map[string]TypeValidationConfig{ "create_pull_request": { DefaultMax: 1, Fields: map[string]FieldValidation{ - "title": {Required: true, Type: "string", Sanitize: true, MaxLength: 128}, - "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, - "branch": {Required: true, Type: "string", Sanitize: true, MaxLength: 256}, - "base": {Type: "string", Sanitize: true, MaxLength: 128}, - "labels": {Type: "array", ItemType: "string", ItemSanitize: true, ItemMaxLength: 128}, - "draft": {Type: "boolean"}, - "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" + "title": {Required: true, Type: "string", Sanitize: true, MaxLength: 128}, + "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "branch": {Required: true, Type: "string", Sanitize: true, MaxLength: 256}, + "base": {Type: "string", Sanitize: true, MaxLength: 128}, + "labels": {Type: "array", ItemType: "string", ItemSanitize: true, ItemMaxLength: 128}, + "draft": {Type: "boolean"}, + "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, }, }, "add_labels": { @@ -212,6 +217,8 @@ var ValidationConfig = map[string]TypeValidationConfig{ "update_branch": {Type: "boolean"}, "draft": {Type: "boolean"}, "pull_request_number": {IssueOrPRNumber: true}, + "pr_number": {IssueOrPRNumber: true}, + "pr": {IssueOrPRNumber: true}, "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" }, }, @@ -231,6 +238,7 @@ var ValidationConfig = map[string]TypeValidationConfig{ "message": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, "pull_request_number": {IssueOrPRNumber: true}, "branch": {Type: "string", Sanitize: true, MaxLength: 256}, // Optional: stripped before MCP call; validated for type/length when present. + "repo": {Type: "string", MaxLength: 256}, }, }, "create_pull_request_review_comment": { @@ -293,6 +301,7 @@ var ValidationConfig = map[string]TypeValidationConfig{ Fields: map[string]FieldValidation{ "body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, "issue_number": {OptionalPositiveInteger: true}, + "duplicate_of": {IssueOrPRNumber: true}, "rationale": {Type: "string", Sanitize: true, MaxLength: 280, StripOnError: true}, "confidence": {Type: "string", Enum: []string{"LOW", "MEDIUM", "HIGH"}, StripOnError: true}, "suggest": {Type: "boolean"}, @@ -337,6 +346,33 @@ var ValidationConfig = map[string]TypeValidationConfig{ "path": {Required: true, Type: "string"}, }, }, + "upload_artifact": { + DefaultMax: 10, + Fields: map[string]FieldValidation{ + "path": {Type: "string"}, + "filters": {Type: "object"}, + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, + }, + }, + "push_repo_memory": { + DefaultMax: 1, + Fields: map[string]FieldValidation{ + "memory_id": {Type: "string", Sanitize: true, MaxLength: 128}, + }, + }, + "create_check_run": { + DefaultMax: 1, + Fields: map[string]FieldValidation{ + "conclusion": {Required: true, Type: "string", Enum: []string{"success", "failure", "neutral", "cancelled", "skipped", "timed_out", "action_required"}}, + "title": {Required: true, Type: "string", Sanitize: true, MaxLength: 256}, + "summary": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "text": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "pull_request_number": {IssueOrPRNumber: true}, + "pr_number": {IssueOrPRNumber: true}, + "pr": {IssueOrPRNumber: true}, + "pull_number": {IssueOrPRNumber: true}, + }, + }, "noop": { DefaultMax: 1, Fields: map[string]FieldValidation{ @@ -366,23 +402,31 @@ var ValidationConfig = map[string]TypeValidationConfig{ "update_project": { DefaultMax: 10, Fields: map[string]FieldValidation{ - "project": {Required: true, Type: "string", Sanitize: true, MaxLength: 512, Pattern: "^https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", PatternError: "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"}, - "content_type": {Type: "string", Enum: []string{"issue", "pull_request", "draft_issue"}}, - "content_number": {IssueNumberOrTemporaryID: true}, - "issue": {OptionalPositiveInteger: true}, // Legacy - "pull_request": {OptionalPositiveInteger: true}, // Legacy - "draft_title": {Type: "string", Sanitize: true, MaxLength: 256}, - "draft_body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, - "fields": {Type: "object"}, + "project": {Required: true, Type: "string", Sanitize: true, MaxLength: 512, Pattern: "^https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", PatternError: "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"}, + "operation": {Type: "string", Enum: []string{"create_fields", "create_view"}}, + "content_type": {Type: "string", Enum: []string{"issue", "pull_request", "draft_issue"}}, + "content_number": {IssueNumberOrTemporaryID: true}, + "target_repo": {Type: "string", Pattern: "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$"}, + "issue": {OptionalPositiveInteger: true}, // Legacy + "pull_request": {OptionalPositiveInteger: true}, // Legacy + "draft_title": {Type: "string", Sanitize: true, MaxLength: 256}, + "draft_body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "draft_issue_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, + "fields": {Type: "object"}, + "field_definitions": {Type: "array"}, + "view": {Type: "object"}, + "create_if_missing": {Type: "boolean"}, }, }, "create_project": { DefaultMax: 1, Fields: map[string]FieldValidation{ - "title": {Type: "string", Sanitize: true, MaxLength: 256}, - "owner": {Type: "string", Sanitize: true, MaxLength: 128}, - "owner_type": {Type: "string", Enum: []string{"org", "user"}}, - "item_url": {Type: "string", Sanitize: true, MaxLength: 512}, + "title": {Type: "string", Sanitize: true, MaxLength: 256}, + "owner": {Type: "string", Sanitize: true, MaxLength: 128}, + "owner_type": {Type: "string", Enum: []string{"org", "user"}}, + "item_url": {Type: "string", Sanitize: true, MaxLength: 512}, + "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, }, }, "create_project_status_update": { From 80b0655dd75ee8b7f3fa56e931f2b805d518a205 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 19:02:55 +0000 Subject: [PATCH 02/23] Update safe outputs specification Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../docs/specs/safe-outputs-specification.md | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/docs/src/content/docs/specs/safe-outputs-specification.md b/docs/src/content/docs/specs/safe-outputs-specification.md index 28150ed1fa4..9de1132dee1 100644 --- a/docs/src/content/docs/specs/safe-outputs-specification.md +++ b/docs/src/content/docs/specs/safe-outputs-specification.md @@ -7,11 +7,11 @@ sidebar: # Safe Outputs MCP Gateway Specification -**Version**: 1.28.0 -**Status**: Working Draft -**Publication Date**: 2026-07-31 -**Editor**: GitHub Agentic Workflows Team -**This Version**: [safe-outputs-specification](/gh-aw/specs/safe-outputs-specification/) +**Version**: 1.28.1
+**Status**: Working Draft
+**Publication Date**: 2026-08-07
+**Editor**: GitHub Agentic Workflows Team
+**This Version**: [safe-outputs-specification](/gh-aw/specs/safe-outputs-specification/)
**Latest Published Version**: This document --- @@ -2150,6 +2150,14 @@ The following table defines the exact `createHandlers()` function used for each | `missing_data` | `defaultHandler("missing_data")` | | `report_incomplete` | `defaultHandler("report_incomplete")` | +### 7.0.3 Declared Field Payload Construction + +The MCP Gateway and Safe Output Processor MUST construct downstream safe-output payloads only from the `type` field and fields declared by the applicable MCP schema, built-in validation configuration, or custom safe-job configuration. Agent-supplied fields that are not declared by that contract MUST NOT be forwarded to handlers, privileged jobs, or API clients. + +If an optional advisory or enrichment field is declared with `x-strip-on-error: true`, implementations MAY omit that field from the normalized downstream payload when the field is invalid. Implementations MUST NOT use stripped fields for authorization, target selection, transport metadata, or other privileged decisions. + +Fields used for privileged transport metadata, including patch anchoring and upload asset file metadata, MUST be derived by trusted workflow steps or privileged processors rather than accepted from agent-controlled NDJSON. + ### 7.1 Core Issue Operations #### Type: create_issue @@ -2290,10 +2298,11 @@ The following table defines the exact `createHandlers()` function used for each This extension applies to safe-output processor messages for `add_comment` (including system-generated status updates). It is distinct from the MCP input schema in this section. -1. When `target: "status"` is set and a reusable status comment ID is available, implementations MUST update the existing issue/PR comment instead of creating a new comment. -2. When `target: "status"` is set but no reusable status comment ID is available, implementations MUST create a new comment. -3. `target: "status"` and `comment_id` MUST be rejected for discussion comments; they are valid only for issue and pull request comments. -4. When updating an existing comment through status-comment reuse, implementations SHOULD skip hide-older-comments behavior for that operation. +1. The MCP input schema for `add_comment` MUST NOT expose `comment_id` as an agent-controlled input. +2. When `target: "status"` is set and a reusable status comment ID is available from trusted workflow state, implementations MUST update the existing issue/PR comment instead of creating a new comment. +3. When `target: "status"` is set but no reusable status comment ID is available from trusted workflow state, implementations MUST create a new comment. +4. `target: "status"` MUST be rejected for discussion comments; status-comment reuse is valid only for issue and pull request comments. +5. When updating an existing comment through status-comment reuse, implementations SHOULD skip hide-older-comments behavior for that operation. **Enforced Constraints**: @@ -3248,6 +3257,8 @@ This section provides complete definitions for all remaining safe output types. - Requires `contents: write` for git push operations - Enforces maximum patch size limit (default: 10 KB, range: 1–100 KB) - Validates changes don't exceed size limits before pushing +- The handler MUST ignore agent-supplied `diff_size` values and validate patch size from the generated patch artifact. +- For patch transport, the generated patch SHOULD embed `X-GH-AW-Base-Commit` metadata derived by the trusted patch-generation step. The privileged processor MUST derive patch re-anchoring metadata from the generated patch and MUST NOT trust agent-supplied base-commit metadata. - Base-branch resolution MUST NOT depend on interactive credential prompts; git operations issued by the handler MUST run with `GIT_TERMINAL_PROMPT=0` and an enforced timeout so credential-less environments fail fast rather than hanging - When `safe-outputs.push-to-pull-request-branch.target` is `"*"`, requests MUST include `pull_request_number`. - The handler MUST refuse pushes unless the resolved pull request head repository exactly matches the configured `head-repo` (or `target-repo` when `head-repo` is omitted) @@ -3770,6 +3781,9 @@ safe-outputs: - Creates or updates orphaned branch for asset storage - Enforces maximum file size limit (default: 10 MB = 10240 KB) - Files accessible via raw.githubusercontent.com URLs +- Staged asset filenames MUST be keyed by a hash of the declared source path, with the original extension preserved where applicable, so distinct source paths with the same basename do not collide. +- The privileged upload job MUST validate that staged asset paths remain contained within the staged-assets directory and that the expected staged filename matches the declared source path. +- The privileged upload job MUST compute asset size, SHA-256 digest, target filename, and published URL from staged file contents and trusted runtime context. It MUST NOT trust agent-supplied `size`, `sha`, `path`, `targetFileName`, or URL metadata for privileged decisions. --- @@ -5388,15 +5402,25 @@ safe-outputs: ## Appendix F: Document History -### Changelog Alignment (Reviewer and Status-Comment Updates) +### Changelog Alignment (Reviewer, Status-Comment, and Hardening Updates) This specification revision aligns with directly relevant `CHANGELOG.md` entries and with the current reviewer/status-comment PR updates: +- **Commit 9d80a262**: safe-output field validation was hardened so normalized downstream payloads contain only schema/config-declared fields, agent-controlled `add_comment.comment_id` was removed, upload asset metadata is re-derived by the privileged job, and patch base metadata is embedded in the generated patch. - **v0.40.1**: `add_comment` discussion handling was updated to auto-detect discussion context without requiring a `discussion` flag. - **v0.40.1**: append-only status comment behavior was documented for smoke workflow execution. - **Earlier changelog entry**: status comments were decoupled from default AI reaction behavior; explicit `on.status-comment` configuration is required when status comments are desired. - **Earlier changelog entry**: `command` trigger was renamed to `slash_command` with deprecation compatibility. +**Version 1.28.1** (2026-08-07): + +- **Specified**: Normalized downstream safe-output payloads MUST include only `type` plus schema/config-declared fields, with undeclared agent-supplied fields stripped before handler or privileged-job consumption. +- **Removed**: Agent-controlled `add_comment.comment_id` from the MCP input contract; status-comment reuse is limited to `target: "status"` with reusable comment IDs obtained from trusted workflow state. +- **Specified**: Optional advisory/enrichment fields marked `x-strip-on-error` MAY be omitted when invalid. +- **Specified**: Upload asset staging and publication MUST derive collision-resistant staged filenames and asset metadata from trusted staged files rather than agent-supplied metadata. +- **Specified**: Patch base metadata MUST be derived from the generated patch, and agent-supplied `diff_size` and base-commit metadata MUST NOT control privileged patch processing. +- **Updated**: Publication metadata to 1.28.1. + **Version 1.28.0** (2026-07-31): - **Added**: GP5a specifying `safe-outputs..github-app` as a per-handler GitHub App override for safe output types. @@ -5578,7 +5602,9 @@ This section maps normative specification requirements (§3–§11) to implement | §5.2 Global Parameters | `footer`, `staged`, global max limits | `actions/setup/js/safe_outputs_config.cjs`, `pkg/workflow/compiler_safe_outputs.go` | | §6 Universal Feature Interpretation | Max limit semantics (MR1–MR4), staged mode (SM1–SM4), footer attribution (FA1–FA6) | `actions/setup/js/safe_outputs_handlers.cjs`, `actions/setup/js/safe_outputs_mcp_server.cjs` | | §7 Safe Output Type Definitions | Handler implementations for each type | `actions/setup/js/safe_outputs_handlers.cjs`, `actions/setup/js/safe_outputs_tools.json` | +| §7.0.3 Declared Field Payload Construction | Normalized payload construction, undeclared field stripping, `x-strip-on-error` advisory field handling | `actions/setup/js/collect_ndjson_output.cjs`, `actions/setup/js/safe_output_type_validator.cjs`, `pkg/workflow/safe_outputs_validation_config.go` | | §7.1 Core Issue Operations | `create_issue`, `add_comment`, `hide_comment`, `close_issue` | `actions/setup/js/add_comment.cjs`, `actions/setup/js/safe_outputs_handlers.cjs` | +| §7.3 `push_to_pull_request_branch` and `upload_asset` | Trusted patch metadata derivation and upload asset staged-file metadata derivation | `actions/setup/js/generate_git_patch.cjs`, `actions/setup/js/push_to_pull_request_branch.cjs`, `actions/setup/js/upload_assets.cjs` | | §8 Protocol Exchange Patterns | stdio container transport, tool invocation, MCP server constraint enforcement | `actions/setup/js/safe_outputs_mcp_server.cjs`, `actions/setup/js/safe_outputs_mcp_server_http.cjs` | | §8.3 MCE1 Early Validation | Invocation-time validation wiring through MCP server startup | `actions/setup/js/safe_outputs_mcp_server.cjs` (`startSafeOutputsServer` → `createHandlers()`), `actions/setup/js/safe_outputs_handlers.cjs` (`addCommentHandler`, `createIssueHandler`, `updatePullRequestHandler`) | | §8.3 MCE2 Tool Description Disclosure | Tool descriptions/schemas exposed during MCP tool registration | `actions/setup/js/safe_outputs_mcp_server.cjs` (`registerPredefinedTools` call), `actions/setup/js/safe_outputs_tools_loader.cjs` (`registerPredefinedTools`) | From 2a01f29fe97022e7e27b92ed44b1c2616a7079b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 19:11:59 +0000 Subject: [PATCH 03/23] Start PR finisher pass Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 26 +++++++ .../workflows/agentic-token-audit.lock.yml | 10 +++ .../agentic-token-optimizer.lock.yml | 10 +++ .github/workflows/approach-validator.lock.yml | 31 ++++++++ .github/workflows/archie.lock.yml | 16 ++++ ...rchivx-agentic-workflows-analyzer.lock.yml | 15 ++++ .github/workflows/audit-workflows.lock.yml | 10 +++ .github/workflows/avenger.lock.yml | 4 + .github/workflows/changeset.lock.yml | 10 +++ .../workflows/chaos-pr-bundle-fuzzer.lock.yml | 4 + .github/workflows/ci-coach.lock.yml | 4 + .github/workflows/ci-doctor.lock.yml | 16 ++++ .github/workflows/cloclo.lock.yml | 20 +++++ .../workflows/code-scanning-fixer.lock.yml | 4 + .github/workflows/code-simplifier.lock.yml | 4 + .github/workflows/contribution-check.lock.yml | 16 ++++ .../workflows/copilot-agent-analysis.lock.yml | 10 +++ .../copilot-centralization-optimizer.lock.yml | 10 +++ .../copilot-cli-deep-research.lock.yml | 10 +++ .../copilot-pr-nlp-analysis.lock.yml | 10 +++ .../copilot-pr-prompt-analysis.lock.yml | 10 +++ .../copilot-session-insights.lock.yml | 10 +++ .github/workflows/craft.lock.yml | 20 +++++ ...aily-agent-of-the-day-blog-writer.lock.yml | 14 ++++ .../daily-architecture-diagram.lock.yml | 4 + .../daily-assign-issue-to-user.lock.yml | 16 ++++ ...strostylelite-markdown-spellcheck.lock.yml | 4 + ...daily-awf-spec-compiler-surfacing.lock.yml | 10 +++ .../daily-caveman-optimizer.lock.yml | 4 + .../workflows/daily-cli-performance.lock.yml | 26 +++++++ .../daily-community-attribution.lock.yml | 14 ++++ ...ly-compiler-threat-spec-optimizer.lock.yml | 4 + .github/workflows/daily-doc-healer.lock.yml | 4 + .github/workflows/daily-doc-updater.lock.yml | 4 + .../daily-elixir-credo-snippet-audit.lock.yml | 4 + .../daily-experiment-report.lock.yml | 16 ++++ .github/workflows/daily-fact.lock.yml | 16 ++++ .../daily-formal-spec-verifier.lock.yml | 10 +++ .../daily-go-test-parallelizer.lock.yml | 4 + .../daily-multi-device-docs-tester.lock.yml | 15 ++++ .github/workflows/daily-news.lock.yml | 25 ++++++ .../daily-rendering-scripts-verifier.lock.yml | 4 + .../daily-safe-output-integrator.lock.yml | 4 + .../daily-safeoutputs-git-simulator.lock.yml | 18 +++++ .../workflows/daily-sentrux-report.lock.yml | 10 +++ .../daily-testify-uber-super-expert.lock.yml | 10 +++ .../workflows/daily-workflow-updater.lock.yml | 4 + .../workflows/daily-yamllint-fixer.lock.yml | 4 + .../dataflow-pr-discussion-dataset.lock.yml | 15 ++++ .github/workflows/dead-code-remover.lock.yml | 4 + .github/workflows/deep-report.lock.yml | 41 ++++++++++ .github/workflows/delight.lock.yml | 10 +++ .github/workflows/dependabot-burner.lock.yml | 20 +++++ .../workflows/dependabot-go-checker.lock.yml | 3 + .../workflows/design-decision-gate.lock.yml | 20 +++++ .github/workflows/dev-hawk.lock.yml | 16 ++++ .../developer-docs-consolidator.lock.yml | 14 ++++ .github/workflows/dictation-prompt.lock.yml | 4 + .github/workflows/draft-pr-cleanup.lock.yml | 16 ++++ .github/workflows/eslint-miner.lock.yml | 4 + .github/workflows/eslint-monster.lock.yml | 3 + .github/workflows/eslint-refiner.lock.yml | 10 +++ .github/workflows/evoskill-evolver.lock.yml | 4 + .github/workflows/firewall-escape.lock.yml | 10 +++ .../workflows/functional-pragmatist.lock.yml | 4 + .../github-mcp-tools-report.lock.yml | 4 + .../workflows/glossary-maintainer.lock.yml | 14 ++++ .github/workflows/go-logger.lock.yml | 4 + .github/workflows/grumpy-reviewer.lock.yml | 47 +++++++++++ .github/workflows/hourly-ci-cleaner.lock.yml | 4 + .../impeccable-skills-reviewer.lock.yml | 63 +++++++++++++++ .../workflows/instructions-janitor.lock.yml | 4 + .github/workflows/issue-monster.lock.yml | 16 ++++ .github/workflows/issue-triage-agent.lock.yml | 16 ++++ .github/workflows/jsweep.lock.yml | 4 + .../workflows/layout-spec-maintainer.lock.yml | 4 + .github/workflows/lint-monster.lock.yml | 3 + .github/workflows/linter-miner.lock.yml | 4 + .../mattpocock-skills-reviewer.lock.yml | 63 +++++++++++++++ .github/workflows/mergefest.lock.yml | 4 + .github/workflows/metrics-collector.lock.yml | 10 +++ .github/workflows/necromancer.lock.yml | 20 +++++ .../objective-impact-report.lock.yml | 3 + .github/workflows/org-health-report.lock.yml | 15 ++++ .github/workflows/pdf-summary.lock.yml | 16 ++++ .github/workflows/poem-bot.lock.yml | 39 ++++++++++ .../pr-code-quality-reviewer.lock.yml | 47 +++++++++++ .../workflows/pr-description-caveman.lock.yml | 6 ++ .../workflows/pr-nitpick-reviewer.lock.yml | 47 +++++++++++ .github/workflows/pr-sous-chef.lock.yml | 26 +++++++ .github/workflows/pr-triage-agent.lock.yml | 73 +++++++++++++++++ .github/workflows/purelock.lock.yml | 4 + .github/workflows/python-data-charts.lock.yml | 15 ++++ .github/workflows/q.lock.yml | 20 +++++ .github/workflows/refiner.lock.yml | 67 ++++++++++++++++ .github/workflows/ruflo-backed-task.lock.yml | 20 +++++ .../schema-feature-coverage.lock.yml | 4 + .github/workflows/scout.lock.yml | 16 ++++ .../workflows/security-compliance.lock.yml | 10 +++ .github/workflows/security-review.lock.yml | 47 +++++++++++ .../semantic-function-refactor.lock.yml | 3 + .github/workflows/sergo.lock.yml | 10 +++ .github/workflows/skillet.lock.yml | 47 +++++++++++ .../workflows/slide-deck-maintainer.lock.yml | 4 + .../workflows/smoke-agent-all-merged.lock.yml | 16 ++++ .../workflows/smoke-agent-all-none.lock.yml | 16 ++++ .../smoke-agent-public-approved.lock.yml | 16 ++++ .../smoke-agent-public-none.lock.yml | 16 ++++ .../smoke-agent-scoped-approved.lock.yml | 16 ++++ .github/workflows/smoke-aider.lock.yml | 16 ++++ .../smoke-checkout-pr-dispatch.lock.yml | 16 ++++ .github/workflows/smoke-ci.lock.yml | 32 ++++++++ .../smoke-claude-on-copilot.lock.yml | 16 ++++ .github/workflows/smoke-claude.lock.yml | 73 +++++++++++++++++ .github/workflows/smoke-codex.lock.yml | 16 ++++ .../smoke-copilot-aoai-apikey.lock.yml | 78 +++++++++++++++++++ .../smoke-copilot-aoai-entra.lock.yml | 78 +++++++++++++++++++ .github/workflows/smoke-copilot-arm.lock.yml | 16 ++++ .github/workflows/smoke-copilot-auto.lock.yml | 16 ++++ .github/workflows/smoke-copilot-mai.lock.yml | 16 ++++ .github/workflows/smoke-copilot.lock.yml | 78 +++++++++++++++++++ .../smoke-create-cross-repo-pr.lock.yml | 20 +++++ .github/workflows/smoke-crush.lock.yml | 16 ++++ .github/workflows/smoke-cursor.lock.yml | 16 ++++ .github/workflows/smoke-gemini.lock.yml | 16 ++++ .../workflows/smoke-github-claude.lock.yml | 16 ++++ .github/workflows/smoke-goose.lock.yml | 16 ++++ .github/workflows/smoke-kiro.lock.yml | 16 ++++ .github/workflows/smoke-multi-pr.lock.yml | 20 +++++ .github/workflows/smoke-opencode.lock.yml | 16 ++++ .github/workflows/smoke-pi.lock.yml | 16 ++++ .github/workflows/smoke-project.lock.yml | 48 ++++++++++++ .../workflows/smoke-service-ports.lock.yml | 16 ++++ .github/workflows/smoke-temporary-id.lock.yml | 16 ++++ .github/workflows/smoke-test-tools.lock.yml | 16 ++++ .../smoke-update-cross-repo-pr.lock.yml | 20 +++++ .../workflows/smoke-workflow-call.lock.yml | 16 ++++ .github/workflows/spec-enforcer.lock.yml | 4 + .github/workflows/spec-extractor.lock.yml | 4 + .github/workflows/stale-pr-cleanup.lock.yml | 16 ++++ .../workflows/stale-repo-identifier.lock.yml | 31 ++++++++ .../workflows/static-analysis-report.lock.yml | 16 ++++ .github/workflows/sub-issue-closer.lock.yml | 16 ++++ .../workflows/technical-doc-writer.lock.yml | 45 +++++++++++ .../workflows/test-quality-sentinel.lock.yml | 16 ++++ .github/workflows/tidy.lock.yml | 8 ++ .../workflows/ubuntu-image-analyzer.lock.yml | 4 + .github/workflows/unbloat-docs.lock.yml | 20 +++++ .github/workflows/update-astro.lock.yml | 4 + .../visual-regression-checker.lock.yml | 16 ++++ .../weekly-blog-post-writer.lock.yml | 14 ++++ .../weekly-editors-health-check.lock.yml | 4 + .../weekly-safe-outputs-spec-review.lock.yml | 4 + .../workflow-health-manager.lock.yml | 26 +++++++ 154 files changed, 2628 insertions(+) diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index c210b219f4d..7cc59ce82d8 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -736,6 +736,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -743,6 +749,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -867,6 +883,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/agentic-token-audit.lock.yml b/.github/workflows/agentic-token-audit.lock.yml index 45cc5b6af09..b9856c92519 100644 --- a/.github/workflows/agentic-token-audit.lock.yml +++ b/.github/workflows/agentic-token-audit.lock.yml @@ -765,6 +765,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/agentic-token-optimizer.lock.yml b/.github/workflows/agentic-token-optimizer.lock.yml index 051e78ae97c..a6b05abcf00 100644 --- a/.github/workflows/agentic-token-optimizer.lock.yml +++ b/.github/workflows/agentic-token-optimizer.lock.yml @@ -688,6 +688,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/approach-validator.lock.yml b/.github/workflows/approach-validator.lock.yml index f052c7caeab..832367c8c6d 100644 --- a/.github/workflows/approach-validator.lock.yml +++ b/.github/workflows/approach-validator.lock.yml @@ -680,6 +680,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -687,6 +693,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -778,6 +794,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index c893f426e79..148e7fee9ee 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -638,6 +638,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -645,6 +651,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/archivx-agentic-workflows-analyzer.lock.yml b/.github/workflows/archivx-agentic-workflows-analyzer.lock.yml index 65e9ae8906f..f8af32eb9ac 100644 --- a/.github/workflows/archivx-agentic-workflows-analyzer.lock.yml +++ b/.github/workflows/archivx-agentic-workflows-analyzer.lock.yml @@ -751,6 +751,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 9251be72a87..737ae0e0c59 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -839,6 +839,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/avenger.lock.yml b/.github/workflows/avenger.lock.yml index a7094ba55bb..d10bf3ca980 100644 --- a/.github/workflows/avenger.lock.yml +++ b/.github/workflows/avenger.lock.yml @@ -646,6 +646,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 37cf83794a7..b7d195a72d0 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -702,6 +702,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, @@ -740,6 +744,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml b/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml index 3f50b7f07c9..bf1183064f7 100644 --- a/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml +++ b/.github/workflows/chaos-pr-bundle-fuzzer.lock.yml @@ -615,6 +615,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index 9280465d65f..ef3374b7d37 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -697,6 +697,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 668dd41298f..16eacce6867 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -714,6 +714,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -721,6 +727,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index d4286fc0ca3..73e7ccd1a9c 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -801,6 +801,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -808,6 +814,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -844,6 +860,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/code-scanning-fixer.lock.yml b/.github/workflows/code-scanning-fixer.lock.yml index 204c8d415dd..7fe408d4ec2 100644 --- a/.github/workflows/code-scanning-fixer.lock.yml +++ b/.github/workflows/code-scanning-fixer.lock.yml @@ -651,6 +651,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml index e8651621bc1..56a5ef9fb11 100644 --- a/.github/workflows/code-simplifier.lock.yml +++ b/.github/workflows/code-simplifier.lock.yml @@ -628,6 +628,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/contribution-check.lock.yml b/.github/workflows/contribution-check.lock.yml index 9f9b26aef1c..aa4dd8ea999 100644 --- a/.github/workflows/contribution-check.lock.yml +++ b/.github/workflows/contribution-check.lock.yml @@ -683,6 +683,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -690,6 +696,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 4ce88233627..99501d72d0a 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -757,6 +757,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-centralization-optimizer.lock.yml b/.github/workflows/copilot-centralization-optimizer.lock.yml index 35380a82723..01fbb840c37 100644 --- a/.github/workflows/copilot-centralization-optimizer.lock.yml +++ b/.github/workflows/copilot-centralization-optimizer.lock.yml @@ -680,6 +680,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index e4549eddd60..e2bab2441a1 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -681,6 +681,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 29900d11b92..48c46fcac07 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -745,6 +745,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 31d78695db6..45f2807c4f8 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -709,6 +709,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 31f1032872a..67531b056fe 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -755,6 +755,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 3826033cd53..5df565af8da 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -643,6 +643,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -650,6 +656,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -726,6 +742,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml b/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml index af8947a58ec..e1fa288f292 100644 --- a/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml +++ b/.github/workflows/daily-agent-of-the-day-blog-writer.lock.yml @@ -715,6 +715,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -780,6 +784,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-architecture-diagram.lock.yml b/.github/workflows/daily-architecture-diagram.lock.yml index 31e7c119733..2d928f35f25 100644 --- a/.github/workflows/daily-architecture-diagram.lock.yml +++ b/.github/workflows/daily-architecture-diagram.lock.yml @@ -727,6 +727,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index a012ce2cf59..475a5fcd3c1 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -567,6 +567,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -574,6 +580,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml b/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml index 0ff4cd82727..eb7ba1a424b 100644 --- a/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml +++ b/.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml @@ -647,6 +647,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml index c45e9252c37..caef0bba094 100644 --- a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml +++ b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml @@ -672,6 +672,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-caveman-optimizer.lock.yml b/.github/workflows/daily-caveman-optimizer.lock.yml index 856ab11892f..9429cec3e2a 100644 --- a/.github/workflows/daily-caveman-optimizer.lock.yml +++ b/.github/workflows/daily-caveman-optimizer.lock.yml @@ -666,6 +666,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index 68c64b8a459..99a7ed83740 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -640,6 +640,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -647,6 +653,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -771,6 +787,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-community-attribution.lock.yml b/.github/workflows/daily-community-attribution.lock.yml index 5af21a3c59c..29c593afdd6 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -726,6 +726,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -791,6 +795,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml b/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml index 2b0cdba8700..203fa298dd6 100644 --- a/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml +++ b/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml @@ -633,6 +633,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-doc-healer.lock.yml b/.github/workflows/daily-doc-healer.lock.yml index 6f9171c6718..82330bef9f7 100644 --- a/.github/workflows/daily-doc-healer.lock.yml +++ b/.github/workflows/daily-doc-healer.lock.yml @@ -738,6 +738,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index cb1928adc69..061d285651a 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -670,6 +670,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml b/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml index 580192eb3df..4b86cb1eaf2 100644 --- a/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml +++ b/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml @@ -602,6 +602,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-experiment-report.lock.yml b/.github/workflows/daily-experiment-report.lock.yml index 2d1e3b28ae2..42f71770691 100644 --- a/.github/workflows/daily-experiment-report.lock.yml +++ b/.github/workflows/daily-experiment-report.lock.yml @@ -634,6 +634,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -641,6 +647,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index 1b6bda01656..c02bba4f94e 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -764,6 +764,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -771,6 +777,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index afc8fd501c2..5670110a303 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -713,6 +713,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-go-test-parallelizer.lock.yml b/.github/workflows/daily-go-test-parallelizer.lock.yml index 9a18bd63891..d6c86d1e99f 100644 --- a/.github/workflows/daily-go-test-parallelizer.lock.yml +++ b/.github/workflows/daily-go-test-parallelizer.lock.yml @@ -606,6 +606,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index 632dcd5f500..a87960ab4f0 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -760,6 +760,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index d11bac35e83..1a2c4144da1 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -838,6 +838,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { @@ -854,6 +864,21 @@ jobs: } } }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, "upload_asset": { "defaultMax": 10, "fields": { diff --git a/.github/workflows/daily-rendering-scripts-verifier.lock.yml b/.github/workflows/daily-rendering-scripts-verifier.lock.yml index f07a1ef743d..f0b6de7ee1e 100644 --- a/.github/workflows/daily-rendering-scripts-verifier.lock.yml +++ b/.github/workflows/daily-rendering-scripts-verifier.lock.yml @@ -738,6 +738,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-safe-output-integrator.lock.yml b/.github/workflows/daily-safe-output-integrator.lock.yml index 6bfd31ef05a..a6edae23cd1 100644 --- a/.github/workflows/daily-safe-output-integrator.lock.yml +++ b/.github/workflows/daily-safe-output-integrator.lock.yml @@ -626,6 +626,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml index 62842d45015..4209cd3df51 100644 --- a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml +++ b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml @@ -665,6 +665,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -730,6 +734,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "push_to_pull_request_branch": { "defaultMax": 1, "fields": { @@ -746,6 +760,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/daily-sentrux-report.lock.yml b/.github/workflows/daily-sentrux-report.lock.yml index 29cb835239d..a83b31dc162 100644 --- a/.github/workflows/daily-sentrux-report.lock.yml +++ b/.github/workflows/daily-sentrux-report.lock.yml @@ -684,6 +684,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index 123e2202cc8..8197e7dc700 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -698,6 +698,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/daily-workflow-updater.lock.yml b/.github/workflows/daily-workflow-updater.lock.yml index 0665b81c828..08ea2265bdf 100644 --- a/.github/workflows/daily-workflow-updater.lock.yml +++ b/.github/workflows/daily-workflow-updater.lock.yml @@ -592,6 +592,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-yamllint-fixer.lock.yml b/.github/workflows/daily-yamllint-fixer.lock.yml index 4d3dcf595d0..4b29821854a 100644 --- a/.github/workflows/daily-yamllint-fixer.lock.yml +++ b/.github/workflows/daily-yamllint-fixer.lock.yml @@ -622,6 +622,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml index d29d9f51845..e8b3255b39a 100644 --- a/.github/workflows/dataflow-pr-discussion-dataset.lock.yml +++ b/.github/workflows/dataflow-pr-discussion-dataset.lock.yml @@ -1037,6 +1037,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/dead-code-remover.lock.yml b/.github/workflows/dead-code-remover.lock.yml index 816a510702e..2acfa9f35aa 100644 --- a/.github/workflows/dead-code-remover.lock.yml +++ b/.github/workflows/dead-code-remover.lock.yml @@ -646,6 +646,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index d078a16bc85..36c91cbceea 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -960,6 +960,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -967,6 +973,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -1091,6 +1107,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { @@ -1106,6 +1132,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index b7b811c9754..2f3538ab804 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -712,6 +712,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/dependabot-burner.lock.yml b/.github/workflows/dependabot-burner.lock.yml index d84a8fc9fc7..26e8b7dfde8 100644 --- a/.github/workflows/dependabot-burner.lock.yml +++ b/.github/workflows/dependabot-burner.lock.yml @@ -667,6 +667,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -674,6 +680,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -710,6 +726,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index 87ab9274ae8..07cfa8b0d71 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -636,6 +636,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/design-decision-gate.lock.yml b/.github/workflows/design-decision-gate.lock.yml index fb690c9b65a..7602313ecb9 100644 --- a/.github/workflows/design-decision-gate.lock.yml +++ b/.github/workflows/design-decision-gate.lock.yml @@ -697,6 +697,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -704,6 +710,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -780,6 +796,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index a31f935abeb..5893952d498 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -675,6 +675,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -682,6 +688,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 3c6efe6222b..8d78c17829a 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -690,6 +690,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -755,6 +759,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 38dadae1c6b..c1a34e3856b 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -594,6 +594,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/draft-pr-cleanup.lock.yml b/.github/workflows/draft-pr-cleanup.lock.yml index 68715e0a2be..7b399d93f36 100644 --- a/.github/workflows/draft-pr-cleanup.lock.yml +++ b/.github/workflows/draft-pr-cleanup.lock.yml @@ -570,6 +570,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -577,6 +583,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/eslint-miner.lock.yml b/.github/workflows/eslint-miner.lock.yml index d644213fe11..7639bb7975b 100644 --- a/.github/workflows/eslint-miner.lock.yml +++ b/.github/workflows/eslint-miner.lock.yml @@ -607,6 +607,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/eslint-monster.lock.yml b/.github/workflows/eslint-monster.lock.yml index f6291ed5da1..faafadd7d6d 100644 --- a/.github/workflows/eslint-monster.lock.yml +++ b/.github/workflows/eslint-monster.lock.yml @@ -628,6 +628,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/eslint-refiner.lock.yml b/.github/workflows/eslint-refiner.lock.yml index 2e4aa7ccb00..4e18378930e 100644 --- a/.github/workflows/eslint-refiner.lock.yml +++ b/.github/workflows/eslint-refiner.lock.yml @@ -714,6 +714,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/evoskill-evolver.lock.yml b/.github/workflows/evoskill-evolver.lock.yml index 07637d5ec26..e7bf508be42 100644 --- a/.github/workflows/evoskill-evolver.lock.yml +++ b/.github/workflows/evoskill-evolver.lock.yml @@ -595,6 +595,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/firewall-escape.lock.yml b/.github/workflows/firewall-escape.lock.yml index 14d39ed4431..e65a4088e9b 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -718,6 +718,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/functional-pragmatist.lock.yml b/.github/workflows/functional-pragmatist.lock.yml index 4712bb31cb8..fd27e8bf683 100644 --- a/.github/workflows/functional-pragmatist.lock.yml +++ b/.github/workflows/functional-pragmatist.lock.yml @@ -600,6 +600,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index 8ed4d2ad9db..6e6e4375101 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -659,6 +659,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 67f49bc39e3..bf58259f7da 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -690,6 +690,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -755,6 +759,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 59ffc5edb39..b89dcec41bb 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -649,6 +649,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index 15f4353f9f7..f50d024c925 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -641,6 +641,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_pull_request_review_comment": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index 68df1efc79f..9c3b19f2da4 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -653,6 +653,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/impeccable-skills-reviewer.lock.yml b/.github/workflows/impeccable-skills-reviewer.lock.yml index ff3cba3162d..d8e23ddb2fc 100644 --- a/.github/workflows/impeccable-skills-reviewer.lock.yml +++ b/.github/workflows/impeccable-skills-reviewer.lock.yml @@ -643,6 +643,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -650,6 +656,63 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 } } }, diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 988ec8f1196..28ce05f383b 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -625,6 +625,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index f76dd2cf831..efe55daa588 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1054,6 +1054,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -1061,6 +1067,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index ebff1d6899b..cb399f9ace7 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -721,6 +721,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -728,6 +734,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } }, "dataEnabled": true, diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index b357ba83328..f83ce2816f6 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -639,6 +639,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/layout-spec-maintainer.lock.yml b/.github/workflows/layout-spec-maintainer.lock.yml index 8514571bd18..79f84e36114 100644 --- a/.github/workflows/layout-spec-maintainer.lock.yml +++ b/.github/workflows/layout-spec-maintainer.lock.yml @@ -609,6 +609,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/lint-monster.lock.yml b/.github/workflows/lint-monster.lock.yml index 6262bf581ae..9363e8ec571 100644 --- a/.github/workflows/lint-monster.lock.yml +++ b/.github/workflows/lint-monster.lock.yml @@ -623,6 +623,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/linter-miner.lock.yml b/.github/workflows/linter-miner.lock.yml index caafa0a851b..5e6681f38c7 100644 --- a/.github/workflows/linter-miner.lock.yml +++ b/.github/workflows/linter-miner.lock.yml @@ -635,6 +635,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/mattpocock-skills-reviewer.lock.yml b/.github/workflows/mattpocock-skills-reviewer.lock.yml index efefd50741a..5c0828dc21f 100644 --- a/.github/workflows/mattpocock-skills-reviewer.lock.yml +++ b/.github/workflows/mattpocock-skills-reviewer.lock.yml @@ -791,6 +791,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -798,6 +804,63 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 } } }, diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index da1ca2238f0..fbb1f4bc3b4 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -701,6 +701,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index 5dc2a489c07..fe29b80dec7 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -753,6 +753,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/necromancer.lock.yml b/.github/workflows/necromancer.lock.yml index fc1807249a5..85ea71e0765 100644 --- a/.github/workflows/necromancer.lock.yml +++ b/.github/workflows/necromancer.lock.yml @@ -655,6 +655,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -662,6 +668,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -738,6 +754,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/objective-impact-report.lock.yml b/.github/workflows/objective-impact-report.lock.yml index 8d1f5d3a9a8..7ba815e4bce 100644 --- a/.github/workflows/objective-impact-report.lock.yml +++ b/.github/workflows/objective-impact-report.lock.yml @@ -591,6 +591,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/org-health-report.lock.yml b/.github/workflows/org-health-report.lock.yml index a4d5acb6326..4a56857c706 100644 --- a/.github/workflows/org-health-report.lock.yml +++ b/.github/workflows/org-health-report.lock.yml @@ -735,6 +735,21 @@ jobs: } } }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, "upload_asset": { "defaultMax": 10, "fields": { diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 879da447787..3217c7ec45b 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -682,6 +682,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -689,6 +695,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 059ae28f69b..df17ac4736c 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -679,6 +679,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -686,6 +692,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -835,6 +851,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -971,6 +991,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, @@ -1040,6 +1064,21 @@ jobs: } }, "customValidation": "requiresOneOf:status,title,body,labels,assignees,milestone" + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/pr-code-quality-reviewer.lock.yml b/.github/workflows/pr-code-quality-reviewer.lock.yml index 09a4b48f406..4053c69fa79 100644 --- a/.github/workflows/pr-code-quality-reviewer.lock.yml +++ b/.github/workflows/pr-code-quality-reviewer.lock.yml @@ -690,6 +690,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_pull_request_review_comment": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/pr-description-caveman.lock.yml b/.github/workflows/pr-description-caveman.lock.yml index 514591c3e11..5a970d9355f 100644 --- a/.github/workflows/pr-description-caveman.lock.yml +++ b/.github/workflows/pr-description-caveman.lock.yml @@ -668,6 +668,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index c1291e06598..f6054fe3047 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -643,6 +643,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_discussion": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/pr-sous-chef.lock.yml b/.github/workflows/pr-sous-chef.lock.yml index 0a0f513958a..f7b71db24e6 100644 --- a/.github/workflows/pr-sous-chef.lock.yml +++ b/.github/workflows/pr-sous-chef.lock.yml @@ -914,6 +914,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -921,6 +927,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -1067,6 +1083,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, @@ -1114,6 +1134,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index 0722de15e77..c539ff6db72 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -780,6 +780,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -787,6 +793,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } }, "dataEnabled": true, @@ -831,6 +847,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_issue": { "defaultMax": 1, "fields": { @@ -1012,6 +1075,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/purelock.lock.yml b/.github/workflows/purelock.lock.yml index df0496754e7..26ed507a926 100644 --- a/.github/workflows/purelock.lock.yml +++ b/.github/workflows/purelock.lock.yml @@ -652,6 +652,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 837f1a24aac..4a575d34693 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -782,6 +782,21 @@ jobs: } } }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, "upload_asset": { "defaultMax": 10, "fields": { diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index cb31ad7d87d..45e77993f30 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -745,6 +745,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -752,6 +758,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -804,6 +820,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/refiner.lock.yml b/.github/workflows/refiner.lock.yml index 0fdfaba033c..3153df13349 100644 --- a/.github/workflows/refiner.lock.yml +++ b/.github/workflows/refiner.lock.yml @@ -634,6 +634,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -641,6 +647,63 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 } } }, @@ -677,6 +740,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/ruflo-backed-task.lock.yml b/.github/workflows/ruflo-backed-task.lock.yml index 050771a6ebe..e8027b44ccc 100644 --- a/.github/workflows/ruflo-backed-task.lock.yml +++ b/.github/workflows/ruflo-backed-task.lock.yml @@ -633,6 +633,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -640,6 +646,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -713,6 +729,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/schema-feature-coverage.lock.yml b/.github/workflows/schema-feature-coverage.lock.yml index bfff45a028f..56f99448af2 100644 --- a/.github/workflows/schema-feature-coverage.lock.yml +++ b/.github/workflows/schema-feature-coverage.lock.yml @@ -618,6 +618,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 0c6b3ab363b..a85de38cbb8 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -709,6 +709,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -716,6 +722,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index 8bf9d5932e0..e6cbb9bc257 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -696,6 +696,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/security-review.lock.yml b/.github/workflows/security-review.lock.yml index e7875e02368..ff822429976 100644 --- a/.github/workflows/security-review.lock.yml +++ b/.github/workflows/security-review.lock.yml @@ -858,6 +858,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_pull_request_review_comment": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index e83ba268b12..565b8125476 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -610,6 +610,9 @@ jobs: ], "x-strip-on-error": true }, + "duplicate_of": { + "issueOrPRNumber": true + }, "issue_number": { "optionalPositiveInteger": true }, diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index 4ccfd89d196..ce8bfafb36e 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -724,6 +724,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/skillet.lock.yml b/.github/workflows/skillet.lock.yml index 972a5572a89..a985b9d54e6 100644 --- a/.github/workflows/skillet.lock.yml +++ b/.github/workflows/skillet.lock.yml @@ -651,6 +651,53 @@ jobs: } GH_AW_VALIDATION_JSON: | { + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_pull_request_review_comment": { "defaultMax": 1, "fields": { diff --git a/.github/workflows/slide-deck-maintainer.lock.yml b/.github/workflows/slide-deck-maintainer.lock.yml index 5bb043d95ec..0e5b9895077 100644 --- a/.github/workflows/slide-deck-maintainer.lock.yml +++ b/.github/workflows/slide-deck-maintainer.lock.yml @@ -700,6 +700,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/smoke-agent-all-merged.lock.yml b/.github/workflows/smoke-agent-all-merged.lock.yml index 927d9885e47..a91b207e15e 100644 --- a/.github/workflows/smoke-agent-all-merged.lock.yml +++ b/.github/workflows/smoke-agent-all-merged.lock.yml @@ -647,6 +647,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -654,6 +660,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-agent-all-none.lock.yml b/.github/workflows/smoke-agent-all-none.lock.yml index f5a14f242d7..6c7d2fb32f3 100644 --- a/.github/workflows/smoke-agent-all-none.lock.yml +++ b/.github/workflows/smoke-agent-all-none.lock.yml @@ -647,6 +647,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -654,6 +660,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-agent-public-approved.lock.yml b/.github/workflows/smoke-agent-public-approved.lock.yml index d00c02af774..6688bf82491 100644 --- a/.github/workflows/smoke-agent-public-approved.lock.yml +++ b/.github/workflows/smoke-agent-public-approved.lock.yml @@ -652,6 +652,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -659,6 +665,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-agent-public-none.lock.yml b/.github/workflows/smoke-agent-public-none.lock.yml index e73befaf75b..1bdd3607d4b 100644 --- a/.github/workflows/smoke-agent-public-none.lock.yml +++ b/.github/workflows/smoke-agent-public-none.lock.yml @@ -647,6 +647,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -654,6 +660,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-agent-scoped-approved.lock.yml b/.github/workflows/smoke-agent-scoped-approved.lock.yml index fd681abd0b3..1d590ffb30e 100644 --- a/.github/workflows/smoke-agent-scoped-approved.lock.yml +++ b/.github/workflows/smoke-agent-scoped-approved.lock.yml @@ -650,6 +650,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -657,6 +663,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-aider.lock.yml b/.github/workflows/smoke-aider.lock.yml index 3c122b37166..c2741ef3de3 100644 --- a/.github/workflows/smoke-aider.lock.yml +++ b/.github/workflows/smoke-aider.lock.yml @@ -623,6 +623,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -630,6 +636,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-checkout-pr-dispatch.lock.yml b/.github/workflows/smoke-checkout-pr-dispatch.lock.yml index 162d0275466..29d2acf16bb 100644 --- a/.github/workflows/smoke-checkout-pr-dispatch.lock.yml +++ b/.github/workflows/smoke-checkout-pr-dispatch.lock.yml @@ -641,6 +641,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -648,6 +654,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index f40c673566c..d4282a140d4 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -675,6 +675,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -682,6 +688,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -820,6 +836,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "remove_labels": { "defaultMax": 5, "fields": { @@ -922,6 +948,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-claude-on-copilot.lock.yml b/.github/workflows/smoke-claude-on-copilot.lock.yml index d086cdf00a9..b06cd54e624 100644 --- a/.github/workflows/smoke-claude-on-copilot.lock.yml +++ b/.github/workflows/smoke-claude-on-copilot.lock.yml @@ -610,6 +610,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -617,6 +623,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index e96d6b41085..acad665912b 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -957,6 +957,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -964,6 +970,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -1026,6 +1042,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_code_scanning_alert": { "defaultMax": 40, "fields": { @@ -1214,6 +1277,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, @@ -1286,6 +1353,12 @@ jobs: "prepend" ] }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "pull_request_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 260fa0088ac..ad41f0dca20 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -773,6 +773,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -780,6 +786,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml index 970523d4156..06cec04d6e3 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml @@ -901,6 +901,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -908,6 +914,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -952,6 +968,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_discussion": { "defaultMax": 1, "fields": { @@ -1248,6 +1311,21 @@ jobs: "maxLength": 256 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/smoke-copilot-aoai-entra.lock.yml b/.github/workflows/smoke-copilot-aoai-entra.lock.yml index 4a2f62bcd98..32308a0acae 100644 --- a/.github/workflows/smoke-copilot-aoai-entra.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-entra.lock.yml @@ -918,6 +918,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -925,6 +931,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -969,6 +985,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_discussion": { "defaultMax": 1, "fields": { @@ -1265,6 +1328,21 @@ jobs: "maxLength": 256 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index 7e2af616adf..548dd7a96ac 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -818,6 +818,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -825,6 +831,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-copilot-auto.lock.yml b/.github/workflows/smoke-copilot-auto.lock.yml index 38af2165164..3da957e1b72 100644 --- a/.github/workflows/smoke-copilot-auto.lock.yml +++ b/.github/workflows/smoke-copilot-auto.lock.yml @@ -595,6 +595,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -602,6 +608,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-copilot-mai.lock.yml b/.github/workflows/smoke-copilot-mai.lock.yml index 90fa543bbc0..eb74f3710b1 100644 --- a/.github/workflows/smoke-copilot-mai.lock.yml +++ b/.github/workflows/smoke-copilot-mai.lock.yml @@ -630,6 +630,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -637,6 +643,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 32407a95727..6888cdbbdf3 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -921,6 +921,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -928,6 +934,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -972,6 +988,53 @@ jobs: } } }, + "create_check_run": { + "defaultMax": 1, + "fields": { + "conclusion": { + "required": true, + "type": "string", + "enum": [ + "success", + "failure", + "neutral", + "cancelled", + "skipped", + "timed_out", + "action_required" + ] + }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, + "pull_number": { + "issueOrPRNumber": true + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "summary": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "text": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, "create_discussion": { "defaultMax": 1, "fields": { @@ -1268,6 +1331,21 @@ jobs: "maxLength": 256 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index 2a2c8aa18a8..c7040f9b950 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -674,6 +674,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -681,6 +687,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -754,6 +770,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/smoke-crush.lock.yml b/.github/workflows/smoke-crush.lock.yml index 2c6cfa943d0..74d64a7c7a9 100644 --- a/.github/workflows/smoke-crush.lock.yml +++ b/.github/workflows/smoke-crush.lock.yml @@ -642,6 +642,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -649,6 +655,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-cursor.lock.yml b/.github/workflows/smoke-cursor.lock.yml index d72d62c3ceb..ab248cf8595 100644 --- a/.github/workflows/smoke-cursor.lock.yml +++ b/.github/workflows/smoke-cursor.lock.yml @@ -641,6 +641,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -648,6 +654,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index b742cc9419d..b089a4941cb 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -708,6 +708,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -715,6 +721,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-github-claude.lock.yml b/.github/workflows/smoke-github-claude.lock.yml index 778096786d4..160970db76a 100644 --- a/.github/workflows/smoke-github-claude.lock.yml +++ b/.github/workflows/smoke-github-claude.lock.yml @@ -610,6 +610,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -617,6 +623,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-goose.lock.yml b/.github/workflows/smoke-goose.lock.yml index 5abe748488a..1e3d4cfe29d 100644 --- a/.github/workflows/smoke-goose.lock.yml +++ b/.github/workflows/smoke-goose.lock.yml @@ -636,6 +636,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -643,6 +649,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-kiro.lock.yml b/.github/workflows/smoke-kiro.lock.yml index 98b01e6ef44..a175933d170 100644 --- a/.github/workflows/smoke-kiro.lock.yml +++ b/.github/workflows/smoke-kiro.lock.yml @@ -641,6 +641,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -648,6 +654,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-multi-pr.lock.yml b/.github/workflows/smoke-multi-pr.lock.yml index 0428592bcc7..5284723f9fc 100644 --- a/.github/workflows/smoke-multi-pr.lock.yml +++ b/.github/workflows/smoke-multi-pr.lock.yml @@ -639,6 +639,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -646,6 +652,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -682,6 +698,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index c67155ce545..a402b550bcd 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -649,6 +649,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -656,6 +662,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-pi.lock.yml b/.github/workflows/smoke-pi.lock.yml index 9d9c7b7979c..ab42a1b2e1a 100644 --- a/.github/workflows/smoke-pi.lock.yml +++ b/.github/workflows/smoke-pi.lock.yml @@ -672,6 +672,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -679,6 +685,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index 391f69003eb..0e5dca98ec1 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -690,6 +690,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -697,6 +703,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -825,6 +841,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -936,22 +956,39 @@ jobs: "draft_issue" ] }, + "create_if_missing": { + "type": "boolean" + }, "draft_body": { "type": "string", "sanitize": true, "maxLength": 65000 }, + "draft_issue_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "draft_title": { "type": "string", "sanitize": true, "maxLength": 256 }, + "field_definitions": { + "type": "array" + }, "fields": { "type": "object" }, "issue": { "optionalPositiveInteger": true }, + "operation": { + "type": "string", + "enum": [ + "create_fields", + "create_view" + ] + }, "project": { "required": true, "type": "string", @@ -962,6 +999,17 @@ jobs: }, "pull_request": { "optionalPositiveInteger": true + }, + "target_repo": { + "type": "string", + "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, + "view": { + "type": "object" } } } diff --git a/.github/workflows/smoke-service-ports.lock.yml b/.github/workflows/smoke-service-ports.lock.yml index 768827114c8..d0a6f4432ba 100644 --- a/.github/workflows/smoke-service-ports.lock.yml +++ b/.github/workflows/smoke-service-ports.lock.yml @@ -633,6 +633,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -640,6 +646,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-temporary-id.lock.yml b/.github/workflows/smoke-temporary-id.lock.yml index 8a61d4c4740..7e7b614560a 100644 --- a/.github/workflows/smoke-temporary-id.lock.yml +++ b/.github/workflows/smoke-temporary-id.lock.yml @@ -678,6 +678,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -685,6 +691,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-test-tools.lock.yml b/.github/workflows/smoke-test-tools.lock.yml index 08a75659d52..05d0a41423f 100644 --- a/.github/workflows/smoke-test-tools.lock.yml +++ b/.github/workflows/smoke-test-tools.lock.yml @@ -665,6 +665,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -672,6 +678,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 9637e1aa05c..54cbaa83cbb 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -708,6 +708,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -715,6 +721,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -828,6 +844,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/smoke-workflow-call.lock.yml b/.github/workflows/smoke-workflow-call.lock.yml index 8a2b8588d8f..956829da189 100644 --- a/.github/workflows/smoke-workflow-call.lock.yml +++ b/.github/workflows/smoke-workflow-call.lock.yml @@ -655,6 +655,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -662,6 +668,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/spec-enforcer.lock.yml b/.github/workflows/spec-enforcer.lock.yml index 9d4194061fa..8f1a4b03e11 100644 --- a/.github/workflows/spec-enforcer.lock.yml +++ b/.github/workflows/spec-enforcer.lock.yml @@ -632,6 +632,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/spec-extractor.lock.yml b/.github/workflows/spec-extractor.lock.yml index 2bfad91543e..0d50dde77d9 100644 --- a/.github/workflows/spec-extractor.lock.yml +++ b/.github/workflows/spec-extractor.lock.yml @@ -632,6 +632,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/stale-pr-cleanup.lock.yml b/.github/workflows/stale-pr-cleanup.lock.yml index 1c56172c113..711f2c0d8b7 100644 --- a/.github/workflows/stale-pr-cleanup.lock.yml +++ b/.github/workflows/stale-pr-cleanup.lock.yml @@ -569,6 +569,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -576,6 +582,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index 7846667f65f..c1fc26bd3ad 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -730,6 +730,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -737,6 +743,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -877,6 +893,21 @@ jobs: } } }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } + }, "upload_asset": { "defaultMax": 10, "fields": { diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index 9a24f55bfb7..f9baaa9bba9 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -697,6 +697,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -704,6 +710,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/sub-issue-closer.lock.yml b/.github/workflows/sub-issue-closer.lock.yml index ead02790d74..ddd2ad1783d 100644 --- a/.github/workflows/sub-issue-closer.lock.yml +++ b/.github/workflows/sub-issue-closer.lock.yml @@ -573,6 +573,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -580,6 +586,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 3154427e1fc..ca4458fb10c 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -663,6 +663,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -670,6 +676,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -706,6 +722,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -771,6 +791,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { @@ -786,6 +816,21 @@ jobs: "maxLength": 1024 } } + }, + "upload_artifact": { + "defaultMax": 10, + "fields": { + "filters": { + "type": "object" + }, + "path": { + "type": "string" + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + } + } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/test-quality-sentinel.lock.yml b/.github/workflows/test-quality-sentinel.lock.yml index 8c795015df2..dd93213fa36 100644 --- a/.github/workflows/test-quality-sentinel.lock.yml +++ b/.github/workflows/test-quality-sentinel.lock.yml @@ -696,6 +696,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -703,6 +709,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index f24487ee4e4..f99d70759e0 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -674,6 +674,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -755,6 +759,10 @@ jobs: }, "pull_request_number": { "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/ubuntu-image-analyzer.lock.yml b/.github/workflows/ubuntu-image-analyzer.lock.yml index d5b4c62dc02..0fe9100e18f 100644 --- a/.github/workflows/ubuntu-image-analyzer.lock.yml +++ b/.github/workflows/ubuntu-image-analyzer.lock.yml @@ -611,6 +611,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 391bf1d6157..e406f92f2e5 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -677,6 +677,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -684,6 +690,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -720,6 +736,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/update-astro.lock.yml b/.github/workflows/update-astro.lock.yml index 0c9ab5a3ee6..471bb7d9aab 100644 --- a/.github/workflows/update-astro.lock.yml +++ b/.github/workflows/update-astro.lock.yml @@ -616,6 +616,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/visual-regression-checker.lock.yml b/.github/workflows/visual-regression-checker.lock.yml index 66ec9959edb..514d6f91416 100644 --- a/.github/workflows/visual-regression-checker.lock.yml +++ b/.github/workflows/visual-regression-checker.lock.yml @@ -653,6 +653,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -660,6 +666,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index 997ee44c143..fcc891979f2 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -790,6 +790,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -855,6 +859,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { diff --git a/.github/workflows/weekly-editors-health-check.lock.yml b/.github/workflows/weekly-editors-health-check.lock.yml index ecac40e0ef5..c09c4ae0aac 100644 --- a/.github/workflows/weekly-editors-health-check.lock.yml +++ b/.github/workflows/weekly-editors-health-check.lock.yml @@ -615,6 +615,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml index d406c278b6e..732d233d211 100644 --- a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml +++ b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml @@ -607,6 +607,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 45bcfee9377..8a3803a99d1 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -611,6 +611,12 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -618,6 +624,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -715,6 +731,16 @@ jobs: } } }, + "push_repo_memory": { + "defaultMax": 1, + "fields": { + "memory_id": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { From 5be57975a09d583989287cf6568bd6b76c931afb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 19:26:56 +0000 Subject: [PATCH 04/23] Address safe output review findings Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-project.lock.yml | 4 +- .../setup/js/push_to_pull_request_branch.cjs | 39 +++++++++++++++++++ .../js/push_to_pull_request_branch.test.cjs | 25 ++++++++++++ actions/setup/js/safe_outputs_handlers.cjs | 5 +++ .../setup/js/safe_outputs_handlers.test.cjs | 11 ++++++ actions/setup/js/upload_assets.cjs | 21 +++++++++- actions/setup/js/upload_assets.test.cjs | 22 +++++++++++ .../safe_output_validation_config_test.go | 23 +++++++++++ .../safe_outputs_validation_config.go | 2 +- 9 files changed, 148 insertions(+), 4 deletions(-) diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index 0e5dca98ec1..8bb070ec9eb 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -994,8 +994,8 @@ jobs: "type": "string", "sanitize": true, "maxLength": 512, - "pattern": "^https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", - "patternError": "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)" + "pattern": "^(https://[^/]+/(orgs|users)/[^/]+/projects/\\d+|#?aw_[A-Za-z0-9_]{3,12})$", + "patternError": "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42) or temporary project ID (e.g., #aw_project1)" }, "pull_request": { "optionalPositiveInteger": true diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index a742c0b3f7d..989a0f590c1 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -3,6 +3,7 @@ /** @type {typeof import("fs")} */ const fs = require("fs"); +const path = require("path"); const { generateStagedPreview } = require("./staged_preview.cjs"); const { isStagedMode } = require("./safe_output_helpers.cjs"); const { pushSignedCommits } = require("./push_signed_commits.cjs"); @@ -120,6 +121,35 @@ async function getBundlePreApplyFiles(exec, gitOptions, rangeBaseRef, bundleRef) .filter(Boolean); } +/** + * Measure the expanded blob size of files changed by the applied agent commits. + * Deleted files contribute zero bytes because no new content is being introduced. + * + * @param {Record} gitOptions + * @param {string[]} files + * @returns {number} + */ +function getChangedBlobSizeBytes(gitOptions, files) { + let total = 0; + const cwd = typeof gitOptions.cwd === "string" && gitOptions.cwd ? gitOptions.cwd : process.cwd(); + const resolvedCwd = path.resolve(cwd); + for (const file of files) { + const filePath = path.resolve(resolvedCwd, file); + if (filePath !== resolvedCwd && !filePath.startsWith(`${resolvedCwd}${path.sep}`)) { + continue; + } + try { + const stats = fs.statSync(filePath); + if (stats.isFile()) { + total += stats.size; + } + } catch { + // Deleted files have no expanded content to add to the limit. + } + } + return total; +} + /** * Checks if a git push stderr output indicates that the 'workflows' scope is required. * GitHub rejects branch pushes that contain .github/workflows/** changes when the token @@ -1211,6 +1241,15 @@ async function main(config = {}) { await exec.exec("git", ["reset", "--hard", rangeBaseRef], baseGitOpts); return await createProtectedFilesFallbackIssue(postApplyProtection.files); } + + const changedBlobSizeBytes = getChangedBlobSizeBytes(baseGitOpts, actualFiles); + const changedBlobSizeKb = Math.ceil(changedBlobSizeBytes / 1024); + core.info(`Changed content size: ${changedBlobSizeKb} KB (maximum allowed: ${maxSizeKb} KB)`); + if (changedBlobSizeKb > maxSizeKb) { + const msg = `Changed content size (${changedBlobSizeKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`; + await exec.exec("git", ["reset", "--hard", rangeBaseRef], baseGitOpts); + return { success: false, error: msg }; + } } } diff --git a/actions/setup/js/push_to_pull_request_branch.test.cjs b/actions/setup/js/push_to_pull_request_branch.test.cjs index 190abc87261..30013940539 100644 --- a/actions/setup/js/push_to_pull_request_branch.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.test.cjs @@ -2135,6 +2135,31 @@ index 0000000..abc1234 expect(mockCore.info).toHaveBeenCalledWith("Patch size validation passed"); expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Patch size: 1 KB")); }); + + it("should enforce max_patch_size against expanded post-apply content", async () => { + const branch = "should-reject-expanded-post-apply-content"; + const patchPath = createPatchFile(branch, "small git binary patch"); + const changedFilePath = path.join(process.cwd(), "test.txt"); + fs.writeFileSync(changedFilePath, Buffer.alloc(2 * 1024 * 1024)); + mockExec.getExecOutput.mockImplementation(async (cmd, args) => { + const argList = Array.isArray(args) ? args : []; + if (cmd === "git" && argList[0] === "diff" && argList[1] === "--name-only") { + return { exitCode: 0, stdout: "test.txt\n", stderr: "" }; + } + return { exitCode: 0, stdout: "abc123\n", stderr: "" }; + }); + + try { + const module = await loadModule(); + const handler = await module.main({ max_patch_size: 1024 }); // 1 MB max + const result = await handler({ branch }, {}); + + expect(result.success).toBe(false); + expect(result.error).toContain("Changed content size"); + } finally { + fs.rmSync(changedFilePath, { force: true }); + } + }); }); // ────────────────────────────────────────────────────── diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 563e445248e..b3346cf4a4e 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -231,6 +231,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { * @type {Map} */ const operationCounts = new Map(); + const uploadedAssetPaths = new Set(); /** * Return the explicitly user-configured max for a safe-output type, or null if not set / unlimited. @@ -461,6 +462,9 @@ function createHandlers(server, appendSafeOutput, config = {}) { if (!isInWorkspace && !isInTmp) { throw new Error(`${ERR_CONFIG}: File path must be within workspace directory (${workspaceDir}) or /tmp directory. ` + `Provided path: ${filePath} (resolved to: ${absolutePath})`); } + if (uploadedAssetPaths.has(absolutePath)) { + throw new Error(`${ERR_VALIDATION}: Duplicate upload_asset source path is not allowed: ${filePath}`); + } // Validate file exists if (!fs.existsSync(filePath)) { @@ -566,6 +570,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { }; appendSafeOutputCounted(entry); + uploadedAssetPaths.add(absolutePath); return { content: [ diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs index 1da447434fe..e4fec8c4cc7 100644 --- a/actions/setup/js/safe_outputs_handlers.test.cjs +++ b/actions/setup/js/safe_outputs_handlers.test.cjs @@ -356,6 +356,17 @@ describe("safe_outputs_handlers", () => { expect(fs.existsSync(path.join(expectedDir, stagedFileName))).toBe(true); }); + it("should reject duplicate upload_asset source paths", () => { + process.env.GH_AW_ASSETS_BRANCH = "assets/test"; + const testFile = path.join(testWorkspaceDir, "duplicate.png"); + fs.writeFileSync(testFile, "first content"); + const args = { path: testFile }; + + handlers.uploadAssetHandler(args); + + expect(() => handlers.uploadAssetHandler(args)).toThrow("Duplicate upload_asset source path is not allowed"); + }); + it("should throw error if GH_AW_ASSETS_BRANCH not set", () => { delete process.env.GH_AW_ASSETS_BRANCH; diff --git a/actions/setup/js/upload_assets.cjs b/actions/setup/js/upload_assets.cjs index ee6873c059e..6a7f2cc2d0a 100644 --- a/actions/setup/js/upload_assets.cjs +++ b/actions/setup/js/upload_assets.cjs @@ -13,6 +13,25 @@ const { normalizeBranchName } = require("./normalize_branch_name.cjs"); * @typedef {{ type: string, path?: string, fileName: string, sha: string, size: number, targetFileName: string, url?: string }} UploadAssetItem */ +/** + * @param {string} githubServer + * @param {string} repo + * @param {string} branchName + * @param {string} targetFileName + * @returns {string} + */ +function buildAssetUrl(githubServer, repo, branchName, targetFileName) { + try { + const serverHostname = new URL(githubServer).hostname; + if (serverHostname === "github.com") { + return `https://github.com/${repo}/blob/${branchName}/${targetFileName}?raw=true`; + } + } catch { + // Fall through to the GHES-compatible raw URL. + } + return `${githubServer}/${repo}/raw/${branchName}/${targetFileName}`; +} + async function main() { // Check if we're in staged mode const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; @@ -128,7 +147,7 @@ async function main() { const size = fileContent.length; const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; const repo = process.env.GITHUB_REPOSITORY || "owner/repo"; - const url = `${githubServer}/${repo}/blob/${normalizedBranchName}/${targetFileName}?raw=true`; + const url = buildAssetUrl(githubServer, repo, normalizedBranchName, targetFileName); processedAssets.push({ fileName, sha: computedSha, size, targetFileName, url }); // Check if file already exists in the branch diff --git a/actions/setup/js/upload_assets.test.cjs b/actions/setup/js/upload_assets.test.cjs index 79dbcfb6777..98008f8a58f 100644 --- a/actions/setup/js/upload_assets.test.cjs +++ b/actions/setup/js/upload_assets.test.cjs @@ -70,6 +70,8 @@ describe("upload_assets.cjs", () => { delete process.env.GH_AW_AGENT_OUTPUT; delete process.env.GH_AW_ASSETS_DIR; delete process.env.GH_AW_SAFE_OUTPUTS_STAGED; + delete process.env.GITHUB_SERVER_URL; + delete process.env.GITHUB_REPOSITORY; tempBase = fs.mkdtempSync(path.join("/tmp", "test-gh-aw-")); cwdArtifacts = new Set(); @@ -247,6 +249,26 @@ describe("upload_assets.cjs", () => { expect(mockCore.setOutput).toHaveBeenCalledWith("upload_count", "1"); }); + it("should derive GHES raw URLs from trusted metadata", async () => { + process.env.GH_AW_ASSETS_BRANCH = "assets/test-workflow"; + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "false"; + process.env.GITHUB_SERVER_URL = "https://ghe.example.com"; + process.env.GITHUB_REPOSITORY = "octo/repo"; + const assetDir = getAssetsDir(); + fs.mkdirSync(assetDir, { recursive: true }); + const declaredPath = "/workspace/test.png"; + const stagedFileName = `${crypto.createHash("sha256").update(declaredPath).digest("hex")}.png`; + const { sha } = makeAsset(assetDir, stagedFileName, "actual content"); + trackCwdArtifact(`${sha}.png`); + setAgentOutput({ items: [{ type: "upload_asset", path: declaredPath }] }); + mockBranchMissing(); + + await executeScript(); + + expect(mockCore.setFailed).not.toHaveBeenCalled(); + expect(mockCore.summary.addRaw).toHaveBeenCalledWith(expect.stringContaining(`https://ghe.example.com/octo/repo/raw/assets/test-workflow/${sha}.png`)); + }); + it("should reject target filenames outside the checkout root", async () => { process.env.GH_AW_ASSETS_BRANCH = "assets/test-workflow"; process.env.GH_AW_SAFE_OUTPUTS_STAGED = "false"; diff --git a/pkg/workflow/safe_output_validation_config_test.go b/pkg/workflow/safe_output_validation_config_test.go index 4a666f72cce..52452baedc3 100644 --- a/pkg/workflow/safe_output_validation_config_test.go +++ b/pkg/workflow/safe_output_validation_config_test.go @@ -4,6 +4,7 @@ package workflow import ( "encoding/json" + "strings" "testing" ) @@ -305,6 +306,28 @@ func TestUpdatePullRequestValidationConfig(t *testing.T) { } } +func TestUpdateProjectAcceptsProjectURLOrTemporaryID(t *testing.T) { + jsonStr, err := GetValidationConfigJSONWithDataSchema([]string{"update_project"}, nil, false, nil) + if err != nil { + t.Fatalf("GetValidationConfigJSON() error = %v", err) + } + + var parsed map[string]TypeValidationConfig + if err := json.Unmarshal([]byte(jsonStr), &parsed); err != nil { + t.Fatalf("Failed to parse validation config JSON: %v", err) + } + + project := parsed["update_project"].Fields["project"] + for _, expected := range []string{ + "https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", + "#?aw_[A-Za-z0-9_]{3,12}", + } { + if !strings.Contains(project.Pattern, expected) { + t.Fatalf("update_project.project pattern %q does not contain %q", project.Pattern, expected) + } + } +} + func TestUpdateIssueValidationConfig(t *testing.T) { config, ok := ValidationConfig["update_issue"] if !ok { diff --git a/pkg/workflow/safe_outputs_validation_config.go b/pkg/workflow/safe_outputs_validation_config.go index 1a2e236988a..3cdf911b059 100644 --- a/pkg/workflow/safe_outputs_validation_config.go +++ b/pkg/workflow/safe_outputs_validation_config.go @@ -402,7 +402,7 @@ var ValidationConfig = map[string]TypeValidationConfig{ "update_project": { DefaultMax: 10, Fields: map[string]FieldValidation{ - "project": {Required: true, Type: "string", Sanitize: true, MaxLength: 512, Pattern: "^https://[^/]+/(orgs|users)/[^/]+/projects/\\d+", PatternError: "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"}, + "project": {Required: true, Type: "string", Sanitize: true, MaxLength: 512, Pattern: "^(https://[^/]+/(orgs|users)/[^/]+/projects/\\d+|#?aw_[A-Za-z0-9_]{3,12})$", PatternError: "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42) or temporary project ID (e.g., #aw_project1)"}, "operation": {Type: "string", Enum: []string{"create_fields", "create_view"}}, "content_type": {Type: "string", Enum: []string{"issue", "pull_request", "draft_issue"}}, "content_number": {IssueNumberOrTemporaryID: true}, From c7aef55e6d416f5c270bb4835db0e8f4d244c5ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 19:47:10 +0000 Subject: [PATCH 05/23] Update generated workflow locks Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/workflows/daily-code-debt-aider.lock.yml | 4 ++++ .github/workflows/daily-go-test-stubs-aider.lock.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/daily-code-debt-aider.lock.yml b/.github/workflows/daily-code-debt-aider.lock.yml index a4dcdfaf23b..765fbda7d7f 100644 --- a/.github/workflows/daily-code-debt-aider.lock.yml +++ b/.github/workflows/daily-code-debt-aider.lock.yml @@ -595,6 +595,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", diff --git a/.github/workflows/daily-go-test-stubs-aider.lock.yml b/.github/workflows/daily-go-test-stubs-aider.lock.yml index e02fc885bc3..6cf8d46983a 100644 --- a/.github/workflows/daily-go-test-stubs-aider.lock.yml +++ b/.github/workflows/daily-go-test-stubs-aider.lock.yml @@ -595,6 +595,10 @@ jobs: "type": "string", "maxLength": 256 }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", From d909c0635e22d122e3afa45a5587405fb89f102c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 19:53:35 +0000 Subject: [PATCH 06/23] Address safe-output review edge cases Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/collect_ndjson_output.cjs | 2 +- actions/setup/js/collect_ndjson_output.test.cjs | 1 + actions/setup/js/commit_sha_helpers.test.cjs | 4 ++++ actions/setup/js/generate_git_patch.cjs | 7 +++++-- actions/setup/js/generate_git_patch.test.cjs | 8 ++++++++ actions/setup/js/safe_output_type_validator.cjs | 17 +++++++++++++---- actions/setup/js/upload_assets.cjs | 4 +++- actions/setup/js/upload_assets.test.cjs | 3 +++ 8 files changed, 38 insertions(+), 8 deletions(-) diff --git a/actions/setup/js/collect_ndjson_output.cjs b/actions/setup/js/collect_ndjson_output.cjs index c00e3744a12..8d3d30c31e9 100644 --- a/actions/setup/js/collect_ndjson_output.cjs +++ b/actions/setup/js/collect_ndjson_output.cjs @@ -113,7 +113,7 @@ async function main() { function validateItemWithSafeJobConfig(item, jobConfig, lineNum) { const errors = []; const normalizedItem = { type: item.type }; - if (!jobConfig.inputs) { + if (!jobConfig || typeof jobConfig !== "object" || !jobConfig.inputs) { return { isValid: true, errors: [], diff --git a/actions/setup/js/collect_ndjson_output.test.cjs b/actions/setup/js/collect_ndjson_output.test.cjs index fe5190c1d2e..169e36f79ff 100644 --- a/actions/setup/js/collect_ndjson_output.test.cjs +++ b/actions/setup/js/collect_ndjson_output.test.cjs @@ -274,6 +274,7 @@ describe("collect_ndjson_output.cjs", () => { expect(outputCall).toBeDefined(); const parsedOutput = JSON.parse(outputCall[1]); (expect(parsedOutput.errors).toHaveLength(0), expect(parsedOutput.items).toEqual([{ type: "post_to_slack", text: slackText }])); + expect(parsedOutput.items[0]).not.toHaveProperty("channel"); }), it("should reject items with unexpected output types", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt", diff --git a/actions/setup/js/commit_sha_helpers.test.cjs b/actions/setup/js/commit_sha_helpers.test.cjs index 0f8c1a4f5ba..846abeb1b05 100644 --- a/actions/setup/js/commit_sha_helpers.test.cjs +++ b/actions/setup/js/commit_sha_helpers.test.cjs @@ -16,6 +16,10 @@ describe("normalizeCommitSHA", () => { expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\nX-GH-AW-Base-Commit: deadbeef\nFrom: Test\n")).toBe("deadbeef"); }); + it("handles CRLF line endings in patch metadata", () => { + expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\r\nX-GH-AW-Base-Commit: deadbeef\r\nFrom: Test\r\n\r\nBody\r\n")).toBe("deadbeef"); + }); + it("ignores missing or malformed patch metadata", () => { expect(extractPatchBaseCommit("From abc123 Mon Sep 17 00:00:00 2001\nFrom: Test\n")).toBe(""); expect(extractPatchBaseCommit("X-GH-AW-Base-Commit: main\n")).toBe(""); diff --git a/actions/setup/js/generate_git_patch.cjs b/actions/setup/js/generate_git_patch.cjs index 215e7aaeeb7..a1903e8f3e7 100644 --- a/actions/setup/js/generate_git_patch.cjs +++ b/actions/setup/js/generate_git_patch.cjs @@ -13,6 +13,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { ensureOriginRemoteTrackingRef, execGitSync } = require("./git_helpers.cjs"); const { ERR_SYSTEM } = require("./error_codes.cjs"); const { sanitizeForFilename, sanitizeBranchNameForPatch, sanitizeRepoSlugForPatch, getPatchPathForBranch, getPatchPathForBranchInRepo, buildExcludePathspecs, computeIncrementalDiffSize } = require("./git_patch_utils.cjs"); +const { normalizeCommitSHA } = require("./commit_sha_helpers.cjs"); // sanitizeForFilename is re-exported below for backward compatibility with // existing callers that imported it from this module. @@ -30,14 +31,15 @@ function debugLog(message) { } function embedBaseCommit(patchContent, baseCommitSha) { - if (!baseCommitSha || typeof patchContent !== "string") { + const normalizedBaseCommitSha = normalizeCommitSHA(baseCommitSha); + if (!normalizedBaseCommitSha || typeof patchContent !== "string") { return patchContent; } const firstNewline = patchContent.indexOf("\n"); if (firstNewline < 0) { return patchContent; } - return `${patchContent.slice(0, firstNewline + 1)}X-GH-AW-Base-Commit: ${baseCommitSha}\n${patchContent.slice(firstNewline + 1)}`; + return `${patchContent.slice(0, firstNewline + 1)}X-GH-AW-Base-Commit: ${normalizedBaseCommitSha}\n${patchContent.slice(firstNewline + 1)}`; } /** @@ -657,4 +659,5 @@ module.exports = { getPatchPathForBranchInRepo, sanitizeBranchNameForPatch, sanitizeRepoSlugForPatch, + embedBaseCommit, }; diff --git a/actions/setup/js/generate_git_patch.test.cjs b/actions/setup/js/generate_git_patch.test.cjs index 33bf67e3084..fba055db80e 100644 --- a/actions/setup/js/generate_git_patch.test.cjs +++ b/actions/setup/js/generate_git_patch.test.cjs @@ -31,6 +31,14 @@ describe("generateGitPatch", () => { }); }); + it("does not embed invalid base commit metadata", async () => { + const { embedBaseCommit } = await import("./generate_git_patch.cjs"); + + const patchContent = "From abc123 Mon Sep 17 00:00:00 2001\nFrom: Test\n"; + + expect(embedBaseCommit(patchContent, "main\nX-Injected: true")).toBe(patchContent); + }); + it("should return error when no commits can be found", async () => { delete process.env.GITHUB_SHA; process.env.GITHUB_WORKSPACE = "/tmp/test-repo"; diff --git a/actions/setup/js/safe_output_type_validator.cjs b/actions/setup/js/safe_output_type_validator.cjs index 4b8d2d7a1ed..521bb7d036d 100644 --- a/actions/setup/js/safe_output_type_validator.cjs +++ b/actions/setup/js/safe_output_type_validator.cjs @@ -353,7 +353,7 @@ function validateOptionalPositiveInteger(value, fieldName, lineNum) { * @param {any} value - Value to validate * @param {string} fieldName - Field name for error messages * @param {number} lineNum - Line number for error messages - * @returns {{isValid: boolean, error?: string}} + * @returns {{isValid: boolean, normalizedValue?: number|string, error?: string}} */ function validateIssueOrPRNumber(value, fieldName, lineNum) { if (value === undefined) { @@ -365,7 +365,7 @@ function validateIssueOrPRNumber(value, fieldName, lineNum) { error: `Line ${lineNum}: ${fieldName} must be a number or string`, }; } - return { isValid: true }; + return { isValid: true, normalizedValue: value }; } /** @@ -453,6 +453,17 @@ function validateField(value, fieldName, validation, itemType, lineNum, options) return validateIssueOrPRNumber(value, `${itemType} '${fieldName}'`, lineNum); } + if (Array.isArray(validation.type)) { + const actualType = Array.isArray(value) ? "array" : typeof value; + if (!validation.type.includes(actualType)) { + return { + isValid: false, + error: `Line ${lineNum}: ${itemType} '${fieldName}' must be one of: ${validation.type.join(", ")}`, + }; + } + return { isValid: true, normalizedValue: value }; + } + // Handle type validation if (validation.type === "string") { if (typeof value !== "string") { @@ -732,8 +743,6 @@ function validateItem(item, itemType, lineNum, options) { } } else if (result.normalizedValue !== undefined) { normalizedItem[fieldName] = result.normalizedValue; - } else if (fieldValue !== undefined) { - normalizedItem[fieldName] = fieldValue; } } diff --git a/actions/setup/js/upload_assets.cjs b/actions/setup/js/upload_assets.cjs index 6a7f2cc2d0a..998f42bd613 100644 --- a/actions/setup/js/upload_assets.cjs +++ b/actions/setup/js/upload_assets.cjs @@ -148,11 +148,12 @@ async function main() { const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; const repo = process.env.GITHUB_REPOSITORY || "owner/repo"; const url = buildAssetUrl(githubServer, repo, normalizedBranchName, targetFileName); - processedAssets.push({ fileName, sha: computedSha, size, targetFileName, url }); + const processedAsset = { fileName, sha: computedSha, size, targetFileName, url }; // Check if file already exists in the branch if (fs.existsSync(targetFileName)) { core.info(`Asset ${targetFileName} already exists, skipping`); + processedAssets.push(processedAsset); continue; } @@ -165,6 +166,7 @@ async function main() { uploadCount++; hasChanges = true; + processedAssets.push(processedAsset); core.info(`Added asset: ${targetFileName} (${size} bytes)`); } catch (error) { diff --git a/actions/setup/js/upload_assets.test.cjs b/actions/setup/js/upload_assets.test.cjs index 98008f8a58f..87b23d3ce1d 100644 --- a/actions/setup/js/upload_assets.test.cjs +++ b/actions/setup/js/upload_assets.test.cjs @@ -308,6 +308,9 @@ describe("upload_assets.cjs", () => { const uploadCountCall = mockCore.setOutput.mock.calls.find(call => call[0] === "upload_count"); expect(uploadCountCall).toBeDefined(); if (uploadCountCall) expect(uploadCountCall[1]).toBe("1"); + const summary = mockCore.summary.addRaw.mock.calls.map(call => String(call[0])).join("\n"); + expect(summary).toContain("present-uploaded.png"); + expect(summary).not.toContain("missing-uploaded.png"); }); it("should fail when all declared assets are missing", async () => { From df26e06930ae0599fe6baa0889bb00c4615f2120 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:02:46 +0000 Subject: [PATCH 07/23] Include commit SHA helper in setup bundle Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/setup.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh index 3460859b4c2..5e60263d1ad 100755 --- a/actions/setup/setup.sh +++ b/actions/setup/setup.sh @@ -324,6 +324,7 @@ SAFE_OUTPUTS_FILES=( "generate_git_patch.cjs" "generate_git_bundle.cjs" "git_patch_utils.cjs" + "commit_sha_helpers.cjs" "get_base_branch.cjs" "get_current_branch.cjs" "normalize_branch_name.cjs" From fbf9d62fd3d1c9d20a6378918179bc92d0e4a5a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:04:43 +0000 Subject: [PATCH 08/23] Reject unnormalized validated safe-output fields Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/safe_output_type_validator.cjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actions/setup/js/safe_output_type_validator.cjs b/actions/setup/js/safe_output_type_validator.cjs index 521bb7d036d..f5f65a1c80e 100644 --- a/actions/setup/js/safe_output_type_validator.cjs +++ b/actions/setup/js/safe_output_type_validator.cjs @@ -743,6 +743,8 @@ function validateItem(item, itemType, lineNum, options) { } } else if (result.normalizedValue !== undefined) { normalizedItem[fieldName] = result.normalizedValue; + } else if (fieldValue !== undefined) { + errors.push(`Line ${lineNum}: ${itemType} '${fieldName}' validation did not produce a normalized value`); } } From 70398c7a9a82b3a2b0b7425a5a910a6e4c077bf7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:06:33 +0000 Subject: [PATCH 09/23] Clarify unnormalized safe-output field errors Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/safe_output_type_validator.cjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/safe_output_type_validator.cjs b/actions/setup/js/safe_output_type_validator.cjs index f5f65a1c80e..5c5fe0b54ca 100644 --- a/actions/setup/js/safe_output_type_validator.cjs +++ b/actions/setup/js/safe_output_type_validator.cjs @@ -744,7 +744,9 @@ function validateItem(item, itemType, lineNum, options) { } else if (result.normalizedValue !== undefined) { normalizedItem[fieldName] = result.normalizedValue; } else if (fieldValue !== undefined) { - errors.push(`Line ${lineNum}: ${itemType} '${fieldName}' validation did not produce a normalized value`); + const validationKind = validation.type ? `type=${Array.isArray(validation.type) ? validation.type.join("|") : validation.type}` : Object.keys(validation).sort().join(",") || "unspecified"; + const fieldValueType = Array.isArray(fieldValue) ? "array" : typeof fieldValue; + errors.push(`Line ${lineNum}: ${itemType} '${fieldName}' validation (${validationKind}) accepted ${fieldValueType} but did not produce a normalized value`); } } From bee34d4d79ba507821497180d873034caa464c0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:08:08 +0000 Subject: [PATCH 10/23] Simplify safe-output validation diagnostics Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/safe_output_type_validator.cjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/safe_output_type_validator.cjs b/actions/setup/js/safe_output_type_validator.cjs index 5c5fe0b54ca..6e2881bdf5f 100644 --- a/actions/setup/js/safe_output_type_validator.cjs +++ b/actions/setup/js/safe_output_type_validator.cjs @@ -744,7 +744,11 @@ function validateItem(item, itemType, lineNum, options) { } else if (result.normalizedValue !== undefined) { normalizedItem[fieldName] = result.normalizedValue; } else if (fieldValue !== undefined) { - const validationKind = validation.type ? `type=${Array.isArray(validation.type) ? validation.type.join("|") : validation.type}` : Object.keys(validation).sort().join(",") || "unspecified"; + let validationKind = Object.keys(validation).sort().join(",") || "unspecified"; + if (validation.type) { + const validationType = Array.isArray(validation.type) ? validation.type.join("|") : validation.type; + validationKind = `type=${validationType}`; + } const fieldValueType = Array.isArray(fieldValue) ? "array" : typeof fieldValue; errors.push(`Line ${lineNum}: ${itemType} '${fieldName}' validation (${validationKind}) accepted ${fieldValueType} but did not produce a normalized value`); } From 89f954abe102a4da4049d50803c0d46882483cf8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:39:45 +0000 Subject: [PATCH 11/23] Allow safe add_comment comment ID allowlists Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 3 + .github/workflows/approach-validator.lock.yml | 3 + .github/workflows/archie.lock.yml | 3 + .github/workflows/ci-doctor.lock.yml | 3 + .github/workflows/cloclo.lock.yml | 3 + .github/workflows/contribution-check.lock.yml | 3 + .github/workflows/craft.lock.yml | 3 + .../daily-assign-issue-to-user.lock.yml | 3 + .../workflows/daily-cli-performance.lock.yml | 3 + .../daily-experiment-report.lock.yml | 3 + .github/workflows/daily-fact.lock.yml | 3 + .github/workflows/deep-report.lock.yml | 3 + .github/workflows/dependabot-burner.lock.yml | 3 + .../workflows/design-decision-gate.lock.yml | 3 + .github/workflows/dev-hawk.lock.yml | 3 + .github/workflows/draft-pr-cleanup.lock.yml | 3 + .../impeccable-skills-reviewer.lock.yml | 3 + .github/workflows/issue-monster.lock.yml | 3 + .github/workflows/issue-triage-agent.lock.yml | 3 + .../mattpocock-skills-reviewer.lock.yml | 3 + .github/workflows/necromancer.lock.yml | 3 + .github/workflows/pdf-summary.lock.yml | 3 + .github/workflows/poem-bot.lock.yml | 3 + .github/workflows/pr-sous-chef.lock.yml | 3 + .github/workflows/pr-triage-agent.lock.yml | 3 + .github/workflows/q.lock.yml | 3 + .github/workflows/refiner.lock.yml | 3 + .github/workflows/ruflo-backed-task.lock.yml | 3 + .github/workflows/scout.lock.yml | 3 + .../workflows/smoke-agent-all-merged.lock.yml | 3 + .../workflows/smoke-agent-all-none.lock.yml | 3 + .../smoke-agent-public-approved.lock.yml | 3 + .../smoke-agent-public-none.lock.yml | 3 + .../smoke-agent-scoped-approved.lock.yml | 3 + .github/workflows/smoke-aider.lock.yml | 3 + .../smoke-checkout-pr-dispatch.lock.yml | 3 + .github/workflows/smoke-ci.lock.yml | 3 + .../smoke-claude-on-copilot.lock.yml | 3 + .github/workflows/smoke-claude.lock.yml | 3 + .github/workflows/smoke-codex.lock.yml | 3 + .../smoke-copilot-aoai-apikey.lock.yml | 3 + .../smoke-copilot-aoai-entra.lock.yml | 3 + .github/workflows/smoke-copilot-arm.lock.yml | 3 + .github/workflows/smoke-copilot-auto.lock.yml | 3 + .github/workflows/smoke-copilot-mai.lock.yml | 3 + .github/workflows/smoke-copilot.lock.yml | 3 + .../smoke-create-cross-repo-pr.lock.yml | 3 + .github/workflows/smoke-crush.lock.yml | 3 + .github/workflows/smoke-cursor.lock.yml | 3 + .github/workflows/smoke-gemini.lock.yml | 3 + .../workflows/smoke-github-claude.lock.yml | 3 + .github/workflows/smoke-goose.lock.yml | 3 + .github/workflows/smoke-kiro.lock.yml | 3 + .github/workflows/smoke-multi-pr.lock.yml | 3 + .github/workflows/smoke-opencode.lock.yml | 3 + .github/workflows/smoke-pi.lock.yml | 3 + .github/workflows/smoke-project.lock.yml | 3 + .../workflows/smoke-service-ports.lock.yml | 3 + .github/workflows/smoke-temporary-id.lock.yml | 3 + .github/workflows/smoke-test-tools.lock.yml | 3 + .../smoke-update-cross-repo-pr.lock.yml | 3 + .../workflows/smoke-workflow-call.lock.yml | 3 + .github/workflows/stale-pr-cleanup.lock.yml | 3 + .../workflows/stale-repo-identifier.lock.yml | 3 + .../workflows/static-analysis-report.lock.yml | 3 + .github/workflows/sub-issue-closer.lock.yml | 3 + .../workflows/technical-doc-writer.lock.yml | 3 + .../workflows/test-quality-sentinel.lock.yml | 3 + .github/workflows/unbloat-docs.lock.yml | 3 + .../visual-regression-checker.lock.yml | 3 + .../workflow-health-manager.lock.yml | 3 + actions/setup/js/add_comment.cjs | 161 +++++++++++++++--- actions/setup/js/add_comment.test.cjs | 36 +++- .../js/safe_output_type_validator.test.cjs | 10 +- actions/setup/js/safe_outputs_handlers.cjs | 73 ++++++++ .../setup/js/safe_outputs_handlers.test.cjs | 33 ++++ actions/setup/js/safe_outputs_tools.json | 7 +- .../content/docs/reference/safe-outputs.md | 3 + .../docs/specs/safe-outputs-specification.md | 21 ++- pkg/workflow/add_comment.go | 5 + pkg/workflow/js/safe_outputs_tools.json | 7 +- .../safe_outputs_config_generation_test.go | 20 +++ pkg/workflow/safe_outputs_handler_registry.go | 1 + .../safe_outputs_validation_config.go | 1 + 84 files changed, 557 insertions(+), 34 deletions(-) diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index 7cc59ce82d8..8daf934552f 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -733,6 +733,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/approach-validator.lock.yml b/.github/workflows/approach-validator.lock.yml index 832367c8c6d..ccd5a508a9e 100644 --- a/.github/workflows/approach-validator.lock.yml +++ b/.github/workflows/approach-validator.lock.yml @@ -677,6 +677,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 148e7fee9ee..3dc0ee887e7 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -635,6 +635,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 16eacce6867..fe3072b6e41 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -711,6 +711,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 73e7ccd1a9c..114b189a957 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -798,6 +798,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/contribution-check.lock.yml b/.github/workflows/contribution-check.lock.yml index aa4dd8ea999..5a919ffccb5 100644 --- a/.github/workflows/contribution-check.lock.yml +++ b/.github/workflows/contribution-check.lock.yml @@ -680,6 +680,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 5df565af8da..0a1a288ac42 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -640,6 +640,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index 475a5fcd3c1..8abbe17d4c9 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -564,6 +564,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index 99a7ed83740..5686a8a1cd1 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -637,6 +637,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/daily-experiment-report.lock.yml b/.github/workflows/daily-experiment-report.lock.yml index 42f71770691..4dbb9cf174f 100644 --- a/.github/workflows/daily-experiment-report.lock.yml +++ b/.github/workflows/daily-experiment-report.lock.yml @@ -631,6 +631,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index c02bba4f94e..ba0d2b498df 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -761,6 +761,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 36c91cbceea..aba0dfa42c0 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -957,6 +957,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/dependabot-burner.lock.yml b/.github/workflows/dependabot-burner.lock.yml index 26e8b7dfde8..3374d6fd41c 100644 --- a/.github/workflows/dependabot-burner.lock.yml +++ b/.github/workflows/dependabot-burner.lock.yml @@ -664,6 +664,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/design-decision-gate.lock.yml b/.github/workflows/design-decision-gate.lock.yml index 7602313ecb9..df2851360e4 100644 --- a/.github/workflows/design-decision-gate.lock.yml +++ b/.github/workflows/design-decision-gate.lock.yml @@ -694,6 +694,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index 5893952d498..b88f235e7ca 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -672,6 +672,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/draft-pr-cleanup.lock.yml b/.github/workflows/draft-pr-cleanup.lock.yml index 7b399d93f36..30506e5d26d 100644 --- a/.github/workflows/draft-pr-cleanup.lock.yml +++ b/.github/workflows/draft-pr-cleanup.lock.yml @@ -567,6 +567,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/impeccable-skills-reviewer.lock.yml b/.github/workflows/impeccable-skills-reviewer.lock.yml index d8e23ddb2fc..fa949dcefb8 100644 --- a/.github/workflows/impeccable-skills-reviewer.lock.yml +++ b/.github/workflows/impeccable-skills-reviewer.lock.yml @@ -640,6 +640,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index efe55daa588..a7edc4a3f40 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1051,6 +1051,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index cb399f9ace7..c4e30018daa 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -718,6 +718,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/mattpocock-skills-reviewer.lock.yml b/.github/workflows/mattpocock-skills-reviewer.lock.yml index 5c0828dc21f..4cefde6e72e 100644 --- a/.github/workflows/mattpocock-skills-reviewer.lock.yml +++ b/.github/workflows/mattpocock-skills-reviewer.lock.yml @@ -788,6 +788,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/necromancer.lock.yml b/.github/workflows/necromancer.lock.yml index 85ea71e0765..205471f637c 100644 --- a/.github/workflows/necromancer.lock.yml +++ b/.github/workflows/necromancer.lock.yml @@ -652,6 +652,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 3217c7ec45b..e0d134528fc 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -679,6 +679,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index df17ac4736c..79f65398688 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -676,6 +676,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/pr-sous-chef.lock.yml b/.github/workflows/pr-sous-chef.lock.yml index f7b71db24e6..b96db011d87 100644 --- a/.github/workflows/pr-sous-chef.lock.yml +++ b/.github/workflows/pr-sous-chef.lock.yml @@ -911,6 +911,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index c539ff6db72..17c6df30cfd 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -777,6 +777,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 45e77993f30..0a566028300 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -742,6 +742,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/refiner.lock.yml b/.github/workflows/refiner.lock.yml index 3153df13349..e505281d32f 100644 --- a/.github/workflows/refiner.lock.yml +++ b/.github/workflows/refiner.lock.yml @@ -631,6 +631,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/ruflo-backed-task.lock.yml b/.github/workflows/ruflo-backed-task.lock.yml index e8027b44ccc..d435cc16cb8 100644 --- a/.github/workflows/ruflo-backed-task.lock.yml +++ b/.github/workflows/ruflo-backed-task.lock.yml @@ -630,6 +630,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index a85de38cbb8..98df19a26ff 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -706,6 +706,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-agent-all-merged.lock.yml b/.github/workflows/smoke-agent-all-merged.lock.yml index a91b207e15e..ddb5ae88cc1 100644 --- a/.github/workflows/smoke-agent-all-merged.lock.yml +++ b/.github/workflows/smoke-agent-all-merged.lock.yml @@ -644,6 +644,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-agent-all-none.lock.yml b/.github/workflows/smoke-agent-all-none.lock.yml index 6c7d2fb32f3..e769960da2e 100644 --- a/.github/workflows/smoke-agent-all-none.lock.yml +++ b/.github/workflows/smoke-agent-all-none.lock.yml @@ -644,6 +644,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-agent-public-approved.lock.yml b/.github/workflows/smoke-agent-public-approved.lock.yml index 6688bf82491..9d06c634d8e 100644 --- a/.github/workflows/smoke-agent-public-approved.lock.yml +++ b/.github/workflows/smoke-agent-public-approved.lock.yml @@ -649,6 +649,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-agent-public-none.lock.yml b/.github/workflows/smoke-agent-public-none.lock.yml index 1bdd3607d4b..4bb6e7d2150 100644 --- a/.github/workflows/smoke-agent-public-none.lock.yml +++ b/.github/workflows/smoke-agent-public-none.lock.yml @@ -644,6 +644,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-agent-scoped-approved.lock.yml b/.github/workflows/smoke-agent-scoped-approved.lock.yml index 1d590ffb30e..8568c313762 100644 --- a/.github/workflows/smoke-agent-scoped-approved.lock.yml +++ b/.github/workflows/smoke-agent-scoped-approved.lock.yml @@ -647,6 +647,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-aider.lock.yml b/.github/workflows/smoke-aider.lock.yml index c2741ef3de3..481ca2a0a90 100644 --- a/.github/workflows/smoke-aider.lock.yml +++ b/.github/workflows/smoke-aider.lock.yml @@ -620,6 +620,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-checkout-pr-dispatch.lock.yml b/.github/workflows/smoke-checkout-pr-dispatch.lock.yml index 29d2acf16bb..030db05a8f3 100644 --- a/.github/workflows/smoke-checkout-pr-dispatch.lock.yml +++ b/.github/workflows/smoke-checkout-pr-dispatch.lock.yml @@ -638,6 +638,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index d4282a140d4..ee5d9a9a997 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -672,6 +672,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-claude-on-copilot.lock.yml b/.github/workflows/smoke-claude-on-copilot.lock.yml index b06cd54e624..d63a088b4c6 100644 --- a/.github/workflows/smoke-claude-on-copilot.lock.yml +++ b/.github/workflows/smoke-claude-on-copilot.lock.yml @@ -607,6 +607,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index acad665912b..ba8c32d9dd0 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -954,6 +954,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index ad41f0dca20..4baa207b8c9 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -770,6 +770,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml index 06cec04d6e3..db8ef4e87b5 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml @@ -898,6 +898,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-copilot-aoai-entra.lock.yml b/.github/workflows/smoke-copilot-aoai-entra.lock.yml index 32308a0acae..5d4a2d211b3 100644 --- a/.github/workflows/smoke-copilot-aoai-entra.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-entra.lock.yml @@ -915,6 +915,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index 548dd7a96ac..7b328694a32 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -815,6 +815,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-copilot-auto.lock.yml b/.github/workflows/smoke-copilot-auto.lock.yml index 3da957e1b72..1f7897b3e41 100644 --- a/.github/workflows/smoke-copilot-auto.lock.yml +++ b/.github/workflows/smoke-copilot-auto.lock.yml @@ -592,6 +592,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-copilot-mai.lock.yml b/.github/workflows/smoke-copilot-mai.lock.yml index eb74f3710b1..75ce95dec03 100644 --- a/.github/workflows/smoke-copilot-mai.lock.yml +++ b/.github/workflows/smoke-copilot-mai.lock.yml @@ -627,6 +627,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 6888cdbbdf3..84461f763b3 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -918,6 +918,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index c7040f9b950..797981300ac 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -671,6 +671,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-crush.lock.yml b/.github/workflows/smoke-crush.lock.yml index 74d64a7c7a9..b286447cba9 100644 --- a/.github/workflows/smoke-crush.lock.yml +++ b/.github/workflows/smoke-crush.lock.yml @@ -639,6 +639,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-cursor.lock.yml b/.github/workflows/smoke-cursor.lock.yml index ab248cf8595..82ea83c14a2 100644 --- a/.github/workflows/smoke-cursor.lock.yml +++ b/.github/workflows/smoke-cursor.lock.yml @@ -638,6 +638,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index b089a4941cb..52e9c54abab 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -705,6 +705,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-github-claude.lock.yml b/.github/workflows/smoke-github-claude.lock.yml index 160970db76a..10bd846716d 100644 --- a/.github/workflows/smoke-github-claude.lock.yml +++ b/.github/workflows/smoke-github-claude.lock.yml @@ -607,6 +607,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-goose.lock.yml b/.github/workflows/smoke-goose.lock.yml index 1e3d4cfe29d..c477aaad626 100644 --- a/.github/workflows/smoke-goose.lock.yml +++ b/.github/workflows/smoke-goose.lock.yml @@ -633,6 +633,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-kiro.lock.yml b/.github/workflows/smoke-kiro.lock.yml index a175933d170..9421ea6b523 100644 --- a/.github/workflows/smoke-kiro.lock.yml +++ b/.github/workflows/smoke-kiro.lock.yml @@ -638,6 +638,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-multi-pr.lock.yml b/.github/workflows/smoke-multi-pr.lock.yml index 5284723f9fc..8f7ad17f0e6 100644 --- a/.github/workflows/smoke-multi-pr.lock.yml +++ b/.github/workflows/smoke-multi-pr.lock.yml @@ -636,6 +636,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index a402b550bcd..36e8644da0a 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -646,6 +646,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-pi.lock.yml b/.github/workflows/smoke-pi.lock.yml index ab42a1b2e1a..3f08d068c76 100644 --- a/.github/workflows/smoke-pi.lock.yml +++ b/.github/workflows/smoke-pi.lock.yml @@ -669,6 +669,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index 8bb070ec9eb..5afc68f711a 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -687,6 +687,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-service-ports.lock.yml b/.github/workflows/smoke-service-ports.lock.yml index d0a6f4432ba..49ec46c6f37 100644 --- a/.github/workflows/smoke-service-ports.lock.yml +++ b/.github/workflows/smoke-service-ports.lock.yml @@ -630,6 +630,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-temporary-id.lock.yml b/.github/workflows/smoke-temporary-id.lock.yml index 7e7b614560a..88c3744777d 100644 --- a/.github/workflows/smoke-temporary-id.lock.yml +++ b/.github/workflows/smoke-temporary-id.lock.yml @@ -675,6 +675,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-test-tools.lock.yml b/.github/workflows/smoke-test-tools.lock.yml index 05d0a41423f..7750ae2bcbf 100644 --- a/.github/workflows/smoke-test-tools.lock.yml +++ b/.github/workflows/smoke-test-tools.lock.yml @@ -662,6 +662,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 54cbaa83cbb..95d50d99e69 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -705,6 +705,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/smoke-workflow-call.lock.yml b/.github/workflows/smoke-workflow-call.lock.yml index 956829da189..8544bdb437c 100644 --- a/.github/workflows/smoke-workflow-call.lock.yml +++ b/.github/workflows/smoke-workflow-call.lock.yml @@ -652,6 +652,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/stale-pr-cleanup.lock.yml b/.github/workflows/stale-pr-cleanup.lock.yml index 711f2c0d8b7..055a4803e72 100644 --- a/.github/workflows/stale-pr-cleanup.lock.yml +++ b/.github/workflows/stale-pr-cleanup.lock.yml @@ -566,6 +566,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index c1fc26bd3ad..7bbd21805ba 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -727,6 +727,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index f9baaa9bba9..bfc3c38d150 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -694,6 +694,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/sub-issue-closer.lock.yml b/.github/workflows/sub-issue-closer.lock.yml index ddd2ad1783d..0ff8b06c923 100644 --- a/.github/workflows/sub-issue-closer.lock.yml +++ b/.github/workflows/sub-issue-closer.lock.yml @@ -570,6 +570,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index ca4458fb10c..f57a3b5a2f8 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -660,6 +660,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/test-quality-sentinel.lock.yml b/.github/workflows/test-quality-sentinel.lock.yml index dd93213fa36..16572cdb6ca 100644 --- a/.github/workflows/test-quality-sentinel.lock.yml +++ b/.github/workflows/test-quality-sentinel.lock.yml @@ -693,6 +693,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index e406f92f2e5..5272033e82b 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -674,6 +674,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/visual-regression-checker.lock.yml b/.github/workflows/visual-regression-checker.lock.yml index 514d6f91416..7fae822653d 100644 --- a/.github/workflows/visual-regression-checker.lock.yml +++ b/.github/workflows/visual-regression-checker.lock.yml @@ -650,6 +650,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 8a3803a99d1..efd59228c49 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -608,6 +608,9 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index 00c86fde8a8..e207fa6910b 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -34,6 +34,93 @@ const HANDLER_TYPE = "add_comment"; // pre-handled by resolveSafeOutputIssueTarget) to preserve a defensive boundary check. const WILDCARD_TARGET_FIELDS = ["item_number", "issue_number", "pull_request_number", "pr_number", "pr", "pull_number"]; +/** + * Parse trusted comment IDs supplied by workflow configuration. + * @param {unknown} value + * @returns {Set} + */ +function parseAllowedCommentIds(value) { + const values = []; + const visit = item => { + if (Array.isArray(item)) { + for (const child of item) visit(child); + return; + } + if (typeof item === "number" && Number.isInteger(item) && item > 0) { + values.push(String(item)); + return; + } + if (typeof item !== "string") { + return; + } + const trimmed = item.trim(); + if (!trimmed) { + return; + } + if (trimmed.startsWith("[") || trimmed.startsWith('"')) { + try { + visit(JSON.parse(trimmed)); + return; + } catch { + // Fall through to delimiter parsing. + } + } + for (const part of trimmed.split(/[,\s]+/)) { + if (/^[1-9]\d*$/.test(part)) { + values.push(part); + } + } + }; + visit(value); + return new Set(values); +} + +/** + * @param {unknown} value + * @returns {{success: true, commentId: number} | {success: false, error: string}} + */ +function parsePositiveCommentId(value) { + const commentId = Number(value); + if (!Number.isInteger(commentId) || commentId <= 0) { + return { success: false, error: "comment_id must be a positive integer" }; + } + return { success: true, commentId }; +} + +/** + * Validate an agent-supplied comment_id against the trusted workflow allowlist. + * @param {unknown} value + * @param {any} config + * @param {string} commentTarget + * @returns {{success: true, commentId: number} | {success: false, error: string}} + */ +function resolveAllowedCommentId(value, config, commentTarget) { + const parsed = parsePositiveCommentId(value); + if (!parsed.success) { + return parsed; + } + if (commentTarget !== "*") { + return { + success: false, + error: "comment_id is only allowed when safe-outputs.add-comment.target is '*' and the ID is listed in safe-outputs.add-comment.allows-comment-ids", + }; + } + const allowedCommentIds = parseAllowedCommentIds(config.allows_comment_ids ?? config["allows-comment-ids"]); + if (allowedCommentIds.size === 0) { + return { + success: false, + error: "comment_id requires safe-outputs.add-comment.allows-comment-ids to list trusted comment IDs", + }; + } + if (!allowedCommentIds.has(String(parsed.commentId))) { + return { + success: false, + error: "comment_id is not listed in safe-outputs.add-comment.allows-comment-ids", + }; + } + return parsed; +} + /** * Deduplicate an array of strings using case-insensitive comparison, preserving original casing and order. * @param {string[]} aliases @@ -504,17 +591,60 @@ async function main(config = {}) { // Determine target number and type let itemNumber; let isDiscussion = false; + /** @type {any} */ + let commentIdToReuse = null; + const explicitCommentIdRaw = message.comment_id ?? message.commentId ?? message["comment-id"]; + const hasExplicitCommentId = explicitCommentIdRaw !== undefined && explicitCommentIdRaw !== null && String(explicitCommentIdRaw).trim() !== ""; + if (hasExplicitCommentId) { + const allowedCommentId = resolveAllowedCommentId(explicitCommentIdRaw, config, commentTarget); + if (!allowedCommentId.success) { + return { + success: false, + error: allowedCommentId.error, + }; + } + commentIdToReuse = allowedCommentId.commentId; + } // Check if item_number or issue_number was explicitly provided in the message. // item_number takes precedence over issue_number when both are present. // pr-number is accepted as an alias for item_number for robustness. - const itemTargetResult = resolveSafeOutputIssueTarget({ message, tempIdMap: temporaryIdMap, repoParts, handlerType: HANDLER_TYPE, aliases: ["item_number", "issue_number", "pr-number"] }); - if (!itemTargetResult.success) return itemTargetResult; + /** @type {{ success: boolean, number?: number | null, deferred?: boolean, error?: string }} */ + let itemTargetResult = { success: true, number: null }; + if (hasExplicitCommentId) { + try { + const { data: existingComment } = await githubClient.rest.issues.getComment({ + owner: repoParts.owner, + repo: repoParts.repo, + comment_id: commentIdToReuse, + }); + const targetURL = existingComment?.issue_url || existingComment?.html_url || ""; + const match = String(targetURL).match(/\/(?:issues|pulls?)\/(\d+)(?:[#/?]|$)/); + if (match) { + itemNumber = Number(match[1]); + } + if (!Number.isInteger(itemNumber) || itemNumber <= 0) { + return { + success: false, + error: `Could not derive issue or pull request number for allowed comment_id ${commentIdToReuse}`, + }; + } + core.info(`Using allowed existing comment ID: ${commentIdToReuse}`); + } catch (err) { + return { + success: false, + error: `Failed to fetch allowed comment_id ${commentIdToReuse}: ${getErrorMessage(err)}`, + }; + } + } else { + itemTargetResult = resolveSafeOutputIssueTarget({ message, tempIdMap: temporaryIdMap, repoParts, handlerType: HANDLER_TYPE, aliases: ["item_number", "issue_number", "pr-number"] }); + if (!itemTargetResult.success) return itemTargetResult; + } - if (itemTargetResult.number !== null) { + if (itemTargetResult.number != null) { itemNumber = itemTargetResult.number; core.info(`Using explicitly provided target number (item_number/issue_number/pr-number): #${itemNumber}`); - } else { + } else if (!hasExplicitCommentId) { // Check if this is a discussion context const isDiscussionContext = effectiveContext.eventName === "discussion" || effectiveContext.eventName === "discussion_comment"; @@ -609,7 +739,7 @@ async function main(config = {}) { const parentAuthors = []; if (!mentionsDisabled) { if (!isDiscussion) { - if (itemTargetResult.number !== null) { + if (itemTargetResult.number != null || hasExplicitCommentId) { // Explicit item_number/issue_number: fetch the issue/PR to get its author try { const { data: issueData } = await githubClient.rest.issues.get({ @@ -776,9 +906,6 @@ async function main(config = {}) { core.warning("Ignoring empty discussion reply_to_id after normalization"); } - // add_comment uses snake_case fields. camelCase and kebab-case aliases are - // accepted for compatibility with forwarded/legacy payload variants. - const explicitCommentIdRaw = message.comment_id ?? message.commentId ?? message["comment-id"]; const rawTarget = message.target; const allowedTargets = ["status", "issue", "discussion"]; if (rawTarget !== undefined && !allowedTargets.includes(rawTarget)) { @@ -786,17 +913,7 @@ async function main(config = {}) { } const isStatusCommentTarget = rawTarget === "status"; const statusCommentIdRaw = process.env.GH_AW_COMMENT_ID || ""; - /** @type {any} */ - let commentIdToReuse = null; - if (explicitCommentIdRaw !== undefined && explicitCommentIdRaw !== null && String(explicitCommentIdRaw).trim() !== "") { - commentIdToReuse = Number(explicitCommentIdRaw); - if (!Number.isInteger(commentIdToReuse) || commentIdToReuse <= 0) { - return { - success: false, - error: "comment_id must be a positive integer", - }; - } - } else if (isStatusCommentTarget) { + if (commentIdToReuse === null && isStatusCommentTarget) { const parsedStatusCommentId = Number(statusCommentIdRaw); if (Number.isInteger(parsedStatusCommentId) && parsedStatusCommentId > 0) { commentIdToReuse = parsedStatusCommentId; @@ -832,7 +949,7 @@ async function main(config = {}) { // reply as a threaded comment to the triggering comment instead of posting top-level. // GitHub Discussions only supports two nesting levels, so if the triggering comment is // itself a reply, we resolve the top-level parent's node ID to use as replyToId. - const hasExplicitItemNumber = itemTargetResult.number !== null; + const hasExplicitItemNumber = itemTargetResult.number != null; let replyToId; if (context.eventName === "discussion_comment" && !hasExplicitItemNumber) { // When triggered by a discussion_comment event, thread the reply under the triggering comment. @@ -850,7 +967,7 @@ async function main(config = {}) { } comment = await commentOnDiscussion(githubClient, repoParts.owner, repoParts.repo, itemNumber, processedBody, replyToId); } else { - const shouldReplyToTriggeringPRReviewComment = effectiveContext.eventName === "pull_request_review_comment" && itemTargetResult.number === null; + const shouldReplyToTriggeringPRReviewComment = !hasExplicitCommentId && effectiveContext.eventName === "pull_request_review_comment" && itemTargetResult.number == null; const triggeringReviewCommentId = Number(effectiveContext.payload?.comment?.id); if (shouldReplyToTriggeringPRReviewComment && Number.isInteger(triggeringReviewCommentId) && triggeringReviewCommentId > 0) { @@ -905,7 +1022,7 @@ async function main(config = {}) { // If 404 and item_number was explicitly provided and we tried as issue/PR, // retry as a discussion (the user may have provided a discussion number) - if (is404 && !isDiscussion && itemTargetResult.number !== null) { + if (is404 && !isDiscussion && itemTargetResult.number != null) { core.info(`Item #${itemNumber} not found as issue/PR, retrying as discussion...`); try { diff --git a/actions/setup/js/add_comment.test.cjs b/actions/setup/js/add_comment.test.cjs index 35a86a0a604..ead1d2f3f33 100644 --- a/actions/setup/js/add_comment.test.cjs +++ b/actions/setup/js/add_comment.test.cjs @@ -47,6 +47,13 @@ describe("add_comment", () => { html_url: "https://github.com/owner/repo/issues/42#issuecomment-12345", }, }), + getComment: async params => ({ + data: { + id: params.comment_id, + issue_url: "https://api.github.com/repos/owner/repo/issues/42", + html_url: `https://github.com/owner/repo/issues/42#issuecomment-${params.comment_id}`, + }, + }), listComments: async () => ({ data: [] }), }, pulls: { @@ -684,12 +691,19 @@ describe("add_comment", () => { delete process.env.GH_AW_COMMENT_ID; }); - it("should update existing comment when comment-id alias is provided", async () => { + it("should update existing comment when comment-id alias is allowed by target wildcard config", async () => { const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); /** @type {any} */ let capturedUpdateParams = null; let createCommentCalled = false; + mockGithub.rest.issues.getComment = async params => ({ + data: { + id: params.comment_id, + issue_url: "https://api.github.com/repos/owner/repo/issues/8535", + html_url: `https://github.com/owner/repo/issues/8535#issuecomment-${params.comment_id}`, + }, + }); mockGithub.rest.issues.updateComment = async params => { capturedUpdateParams = params; return { @@ -709,10 +723,11 @@ describe("add_comment", () => { }; }; - const handler = await eval(`(async () => { ${addCommentScript}; return await main({ target: 'triggering' }); })()`); + const handler = await eval(`(async () => { ${addCommentScript}; return await main({ target: '*', allows_comment_ids: ['55555'] }); })()`); const result = await handler({ type: "add_comment", body: "Updated output", "comment-id": 55555 }, {}); expect(result.success).toBe(true); + expect(result.itemNumber).toBe(8535); expect(capturedUpdateParams).toEqual( expect.objectContaining({ owner: "owner", @@ -723,6 +738,23 @@ describe("add_comment", () => { expect(createCommentCalled).toBe(false); }); + it("should reject comment_id when it is not in allows-comment-ids", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + let updateCommentCalled = false; + mockGithub.rest.issues.updateComment = async () => { + updateCommentCalled = true; + return { data: { id: 999, html_url: "https://github.com/owner/repo/issues/8535#issuecomment-999" } }; + }; + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({ target: '*', allows_comment_ids: ['12345'] }); })()`); + const result = await handler({ type: "add_comment", body: "Updated output", comment_id: 55555 }, {}); + + expect(result.success).toBe(false); + expect(result.error).toContain("allows-comment-ids"); + expect(updateCommentCalled).toBe(false); + }); + it("should warn and ignore unrecognized message-level target values instead of failing", async () => { const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); diff --git a/actions/setup/js/safe_output_type_validator.test.cjs b/actions/setup/js/safe_output_type_validator.test.cjs index 8a7f6ff4c14..472267f1c20 100644 --- a/actions/setup/js/safe_output_type_validator.test.cjs +++ b/actions/setup/js/safe_output_type_validator.test.cjs @@ -27,6 +27,7 @@ const SAMPLE_VALIDATION_CONFIG = { fields: { body: { required: true, type: "string", sanitize: true, maxLength: 65000 }, item_number: { issueOrPRNumber: true }, + comment_id: { optionalPositiveInteger: true }, }, }, create_pull_request: { @@ -1377,8 +1378,15 @@ describe("safe_output_type_validator", () => { expect(result.normalizedItem).toEqual(item); }); + it("should preserve declared add_comment.comment_id as a positive integer", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem({ type: "add_comment", body: "Test comment", comment_id: "123" }, "add_comment", 1); + expect(result.isValid).toBe(true); + expect(result.normalizedItem).toEqual({ type: "add_comment", body: "Test comment", comment_id: 123 }); + }); + it.each([ - { itemType: "add_comment", item: { type: "add_comment", body: "Test comment", comment_id: 123 }, fieldName: "comment_id" }, { itemType: "update_pull_request", item: { type: "update_pull_request", title: "Updated title", base: "release", state: "closed" }, fieldName: "base" }, { itemType: "update_pull_request", item: { type: "update_pull_request", title: "Updated title", base: "release", state: "closed" }, fieldName: "state" }, { itemType: "upload_asset", item: { type: "upload_asset", path: "image.png", targetFileName: "../../.git/config" }, fieldName: "targetFileName" }, diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index b3346cf4a4e..ec6d911ce93 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -158,6 +158,75 @@ function parseAllowedBranchPatterns(value) { return []; } +/** + * Parse trusted comment IDs supplied by workflow configuration. + * @param {unknown} value + * @returns {Set} + */ +function parseAllowedCommentIds(value) { + const values = []; + const visit = item => { + if (Array.isArray(item)) { + for (const child of item) visit(child); + return; + } + if (typeof item === "number" && Number.isInteger(item) && item > 0) { + values.push(String(item)); + return; + } + if (typeof item !== "string") { + return; + } + const trimmed = item.trim(); + if (!trimmed) { + return; + } + if (trimmed.startsWith("[") || trimmed.startsWith('"')) { + try { + visit(JSON.parse(trimmed)); + return; + } catch { + // Fall through to delimiter parsing. + } + } + for (const part of trimmed.split(/[,\s]+/)) { + if (/^[1-9]\d*$/.test(part)) { + values.push(part); + } + } + }; + visit(value); + return new Set(values); +} + +/** + * Validate an agent-supplied add_comment.comment_id against the trusted workflow allowlist. + * @param {Record} entry + * @param {Record} addCommentConfig + * @returns {{content: Array<{type: "text", text: string}>, isError: true} | null} + */ +function validateAllowedAddCommentId(entry, addCommentConfig) { + if (entry.comment_id === undefined || entry.comment_id === null || String(entry.comment_id).trim() === "") { + return null; + } + if (addCommentConfig.target !== "*") { + return buildIntentErrorResponse("add_comment comment_id is only allowed when safe-outputs.add-comment.target is '*' and the ID is listed in safe-outputs.add-comment.allows-comment-ids."); + } + const commentId = Number(entry.comment_id); + if (!Number.isInteger(commentId) || commentId <= 0) { + return buildIntentErrorResponse("add_comment comment_id must be a positive integer."); + } + const allowedCommentIds = parseAllowedCommentIds(addCommentConfig.allows_comment_ids ?? addCommentConfig["allows-comment-ids"]); + if (allowedCommentIds.size === 0) { + return buildIntentErrorResponse("add_comment comment_id requires safe-outputs.add-comment.allows-comment-ids to list trusted comment IDs."); + } + if (!allowedCommentIds.has(String(commentId))) { + return buildIntentErrorResponse("add_comment comment_id is not listed in safe-outputs.add-comment.allows-comment-ids."); + } + entry.comment_id = commentId; + return null; +} + /** * @param {string} branch * @param {string[]} allowedPatterns @@ -1962,6 +2031,10 @@ function createHandlers(server, appendSafeOutput, config = {}) { // Build the entry with a temporary_id const entry = { ...(args || {}), type: "add_comment" }; + const commentIdValidationError = validateAllowedAddCommentId(entry, addCommentConfig); + if (commentIdValidationError) { + return commentIdValidationError; + } const wildcardTargetValidationError = validateWildcardTargetRequirement(entry); if (wildcardTargetValidationError) { return wildcardTargetValidationError; diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs index e4fec8c4cc7..67c97315fcf 100644 --- a/actions/setup/js/safe_outputs_handlers.test.cjs +++ b/actions/setup/js/safe_outputs_handlers.test.cjs @@ -2348,6 +2348,39 @@ describe("safe_outputs_handlers", () => { expect(mockAppendSafeOutput).not.toHaveBeenCalled(); }); + it("should allow comment_id from allows-comment-ids when add_comment target is '*'", () => { + const wildcardHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + add_comment: { + target: "*", + allows_comment_ids: ["12345", "67890"], + }, + }); + + const result = wildcardHandlers.addCommentHandler({ body: "Update an existing status-style comment.", comment_id: "12345" }); + + expect(result).toHaveProperty("content"); + const responseData = JSON.parse(result.content[0].text); + expect(responseData.result).toBe("success"); + expect(mockAppendSafeOutput).toHaveBeenCalledWith(expect.objectContaining({ type: "add_comment", comment_id: 12345 })); + }); + + it("should reject comment_id that is not listed in allows-comment-ids", () => { + const wildcardHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + add_comment: { + target: "*", + allows_comment_ids: ["12345"], + }, + }); + + const result = wildcardHandlers.addCommentHandler({ body: "Update an existing status-style comment.", comment_id: "67890" }); + + expect(result.isError).toBe(true); + const responseData = JSON.parse(result.content[0].text); + expect(responseData.result).toBe("error"); + expect(responseData.error).toContain("allows-comment-ids"); + expect(mockAppendSafeOutput).not.toHaveBeenCalled(); + }); + it("should refuse reply_to_id when discussions are not enabled in config", () => { // Default handlers have no discussions: true in config // Discussion check precedes context check so this error surfaces regardless of event context diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index 21d8b00b840..2775fb4eb44 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -332,6 +332,11 @@ "enum": ["status"], "description": "When set to 'status', updates the activation status comment for this run (if available) instead of creating a new comment." }, + "comment_id": { + "type": ["number", "string"], + "description": "Existing issue or pull request comment ID to update. Only valid when the workflow config sets safe-outputs.add-comment.target to '*' and the ID is present in safe-outputs.add-comment.allows-comment-ids. Use this only for comment IDs precomputed by trusted workflow steps.", + "x-synonyms": ["commentId", "comment-id"] + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -346,7 +351,7 @@ "x-safe-outputs-target-requirements": { "*": { "primary": "item_number", - "anyOf": ["item_number", "pr_number", "pr"] + "anyOf": ["item_number", "pr_number", "pr", "comment_id"] } } }, diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index c8ecbbaaf3c..2f5a974d597 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -283,6 +283,7 @@ safe-outputs: add-comment: max: 3 # max comments (default: 1) target: "*" # "triggering" (default), "*", or number + allows-comment-ids: ${{ needs.prepare.outputs.comment_ids }} # comment IDs the agent may update when target is "*" discussions: true # request discussions:write permission (default: false) target-repo: "owner/repo" # cross-repository allowed-repos: ["org/repo1", "org/repo2"] # additional allowed repositories @@ -297,6 +298,8 @@ safe-outputs: > [!TIP] > Use `footer: false` to suppress the "Generated by..." attribution line in posted comments. See [Footer Control](/gh-aw/reference/footers/) for global and per-handler options. +When `target: "*"` is configured, the agent may update an existing issue or pull request comment by passing `comment_id` only if that ID appears in `allows-comment-ids`. Populate `allows-comment-ids` from trusted workflow state (for example, an earlier step output) rather than agent output. + #### Normalize closing keywords Set `normalize-closing-keywords: true` to strip wrapping backticks from recognized issue-closing keywords in body text (for example, `` `Closes #123` `` becomes `Closes #123` so GitHub can process it as a closing keyword). This field is supported by `create-issue` and `add-comment` on this page, and by `create-pull-request` in [Safe Outputs (Pull Requests)](/gh-aw/reference/safe-outputs-pull-requests/#pull-request-creation-create-pull-request). diff --git a/docs/src/content/docs/specs/safe-outputs-specification.md b/docs/src/content/docs/specs/safe-outputs-specification.md index 9de1132dee1..646afc503c5 100644 --- a/docs/src/content/docs/specs/safe-outputs-specification.md +++ b/docs/src/content/docs/specs/safe-outputs-specification.md @@ -2279,6 +2279,10 @@ Fields used for privileged transport metadata, including patch anchoring and upl "item_number": { "type": "number", "description": "Issue/PR/discussion number (auto-resolved from context if omitted)" + }, + "comment_id": { + "type": ["number", "string"], + "description": "Existing issue or pull request comment ID to update. Valid only when safe-outputs.add-comment.target is \"*\" and the ID appears in safe-outputs.add-comment.allows-comment-ids." } }, "additionalProperties": false @@ -2294,15 +2298,17 @@ Fields used for privileged transport metadata, including patch anchoring and upl 4. **Footer Injection**: Appends footer according to configuration (typically 200-500 characters). 5. **Cross-Repository**: Supports `target-repo` configuration. -**Status Comment Reuse Extension (`target: "status"`)**: +**Controlled Comment Reuse Extensions**: -This extension applies to safe-output processor messages for `add_comment` (including system-generated status updates). It is distinct from the MCP input schema in this section. +These extensions apply to safe-output processor messages for `add_comment` (including system-generated status updates). -1. The MCP input schema for `add_comment` MUST NOT expose `comment_id` as an agent-controlled input. -2. When `target: "status"` is set and a reusable status comment ID is available from trusted workflow state, implementations MUST update the existing issue/PR comment instead of creating a new comment. -3. When `target: "status"` is set but no reusable status comment ID is available from trusted workflow state, implementations MUST create a new comment. -4. `target: "status"` MUST be rejected for discussion comments; status-comment reuse is valid only for issue and pull request comments. -5. When updating an existing comment through status-comment reuse, implementations SHOULD skip hide-older-comments behavior for that operation. +1. When message-level `target: "status"` is set and a reusable status comment ID is available from trusted workflow state, implementations MUST update the existing issue/PR comment instead of creating a new comment. +2. When message-level `target: "status"` is set but no reusable status comment ID is available from trusted workflow state, implementations MUST create a new comment. +3. Message-level `target: "status"` MUST be rejected for discussion comments; status-comment reuse is valid only for issue and pull request comments. +4. The MCP input schema for `add_comment` MAY expose `comment_id` as an agent-controlled input only for workflows that configure `safe-outputs.add-comment.target: "*"`. +5. When an agent supplies `comment_id`, implementations MUST reject the operation unless `safe-outputs.add-comment.allows-comment-ids` is configured and contains that exact positive integer ID. The allowlist is trusted workflow state and MAY be computed by earlier workflow steps. +6. Agent-supplied `comment_id` MUST NOT be honored for discussion comments and MUST NOT be treated as a substitute for the trusted status comment ID used by `target: "status"`. +7. When updating an existing comment through either controlled reuse path, implementations SHOULD skip hide-older-comments behavior for that operation. **Enforced Constraints**: @@ -2318,6 +2324,7 @@ This extension applies to safe-output processor messages for `add_comment` (incl - `max`: Operation limit (default: 1) - `target`: Filter by type ("issue", "pull_request", "discussion", "*"). This configuration field applies to static workflow configuration (`safe-outputs.add-comment.target`) and is distinct from the runtime per-message `target: "status"` extension above. +- `allows-comment-ids`: Trusted allowlist of issue/PR comment IDs that the agent may update with `comment_id` when `target: "*"` is configured. Accepts an array of strings or a GitHub Actions expression that resolves to a list. - `hide-older-comments`: Hide previous workflow comments - `discussions`: Control `discussions:write` permission (default: false). Set to `true` to comment on discussions. - `target-repo`: Cross-repository target diff --git a/pkg/workflow/add_comment.go b/pkg/workflow/add_comment.go index bad1e7b2e69..e12e0f6e223 100644 --- a/pkg/workflow/add_comment.go +++ b/pkg/workflow/add_comment.go @@ -18,6 +18,7 @@ type AddCommentsConfig struct { Target string `yaml:"target,omitempty"` // Target for comments: "triggering" (default), "*" (any issue), or explicit issue number TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository comments AllowedRepos []string `yaml:"allowed-repos,omitempty"` // List of additional repositories that comments can be added to (additionally to the target-repo) + AllowedCommentIDs []string `yaml:"allows-comment-ids,omitempty"` // Trusted allowlist of issue/PR comment IDs the agent may update when target is "*" HideOlderComments *string `yaml:"hide-older-comments,omitempty"` // When true, minimizes/hides all previous comments from the same workflow before creating the new comment HideOlderCommentsMatch []string `yaml:"hide-older-comments-match,omitempty"` // Internal list populated from hide-older-comments.match and passed to the JS handler as exact workflow ID matches AllowedReasons []string `yaml:"allowed-reasons,omitempty"` // List of allowed reasons for hiding older comments (default: all reasons allowed) @@ -63,6 +64,10 @@ func (c *Compiler) parseCommentsConfig(outputMap map[string]any) *AddCommentsCon addCommentLog.Printf("Invalid allowed-repos value: %v", err) return nil } + if err := preprocessStringArrayFieldAsTemplatable(configData, "allows-comment-ids", addCommentLog); err != nil { + addCommentLog.Printf("Invalid allows-comment-ids value: %v", err) + return nil + } config := parseConfigScaffold(outputMap, "add-comment", addCommentLog, func(err error) *AddCommentsConfig { addCommentLog.Printf("Failed to unmarshal config: %v", err) diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 21d8b00b840..2775fb4eb44 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -332,6 +332,11 @@ "enum": ["status"], "description": "When set to 'status', updates the activation status comment for this run (if available) instead of creating a new comment." }, + "comment_id": { + "type": ["number", "string"], + "description": "Existing issue or pull request comment ID to update. Only valid when the workflow config sets safe-outputs.add-comment.target to '*' and the ID is present in safe-outputs.add-comment.allows-comment-ids. Use this only for comment IDs precomputed by trusted workflow steps.", + "x-synonyms": ["commentId", "comment-id"] + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -346,7 +351,7 @@ "x-safe-outputs-target-requirements": { "*": { "primary": "item_number", - "anyOf": ["item_number", "pr_number", "pr"] + "anyOf": ["item_number", "pr_number", "pr", "comment_id"] } } }, diff --git a/pkg/workflow/safe_outputs_config_generation_test.go b/pkg/workflow/safe_outputs_config_generation_test.go index 5c113bf316c..40803912435 100644 --- a/pkg/workflow/safe_outputs_config_generation_test.go +++ b/pkg/workflow/safe_outputs_config_generation_test.go @@ -238,6 +238,26 @@ func TestGenerateSafeOutputsConfigAddsDataFlagsForBodyHandlers(t *testing.T) { assert.Equal(t, true, addComment["data_enabled"]) } +func TestGenerateSafeOutputsConfigForwardsAllowedCommentIDs(t *testing.T) { + cfg := &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, + Target: "*", + AllowedCommentIDs: []string{"${{ needs.prepare.outputs.comment_ids }}"}, + }, + } + data := &WorkflowData{SafeOutputs: cfg} + result, err := generateSafeOutputsConfig(data) + require.NoError(t, err) + + var parsed map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &parsed)) + addComment, ok := parsed["add_comment"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "*", addComment["target"]) + assert.Equal(t, "${{ needs.prepare.outputs.comment_ids }}", addComment["allows_comment_ids"]) +} + func TestGenerateSafeOutputsConfigAddsRuntimeDataSchemaExpression(t *testing.T) { cfg := &SafeOutputsConfig{ DataEnabled: true, diff --git a/pkg/workflow/safe_outputs_handler_registry.go b/pkg/workflow/safe_outputs_handler_registry.go index 42f29f99fb6..546c5ba813a 100644 --- a/pkg/workflow/safe_outputs_handler_registry.go +++ b/pkg/workflow/safe_outputs_handler_registry.go @@ -86,6 +86,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddBoolPtr("discussions", c.Discussions). AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddTemplatableStringSlice("allowed_repos", c.AllowedRepos). + AddTemplatableStringSlice("allows_comment_ids", c.AllowedCommentIDs). AddIfNotEmpty("github-token", resolveHandlerGitHubToken(c.GitHubApp, "add-comment", c.GitHubToken)). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). AddBoolPtr("normalize_closing_keywords", c.NormalizeClosingKeywords). diff --git a/pkg/workflow/safe_outputs_validation_config.go b/pkg/workflow/safe_outputs_validation_config.go index 3cdf911b059..7bd7ead5082 100644 --- a/pkg/workflow/safe_outputs_validation_config.go +++ b/pkg/workflow/safe_outputs_validation_config.go @@ -88,6 +88,7 @@ var ValidationConfig = map[string]TypeValidationConfig{ "temporary_id": {Type: "string", Pattern: "^#?aw_[A-Za-z0-9_]{3,12}$"}, "reply_to_id": {Type: "string", MaxLength: 256}, // Optional: node ID of discussion comment to reply to (threading) "target": {Type: "string", Enum: []string{"status"}}, + "comment_id": {OptionalPositiveInteger: true}, "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" }, }, From 08021cb2ada32e8216aef29ec929950fff3c0d74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:41:55 +0000 Subject: [PATCH 12/23] Tighten add_comment target URL parsing Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/add_comment.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index e207fa6910b..ebdca813770 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -619,7 +619,7 @@ async function main(config = {}) { comment_id: commentIdToReuse, }); const targetURL = existingComment?.issue_url || existingComment?.html_url || ""; - const match = String(targetURL).match(/\/(?:issues|pulls?)\/(\d+)(?:[#/?]|$)/); + const match = String(targetURL).match(/\/issues\/(\d+)(?:[#/?]|$)/) || String(targetURL).match(/\/pull\/(\d+)(?:[#/?]|$)/); if (match) { itemNumber = Number(match[1]); } From 178ff3136db13aa8f684a66530720787d71fbd5b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:44:02 +0000 Subject: [PATCH 13/23] Use issue URL for comment target derivation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/add_comment.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index ebdca813770..6bb76ce6e8d 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -618,8 +618,8 @@ async function main(config = {}) { repo: repoParts.repo, comment_id: commentIdToReuse, }); - const targetURL = existingComment?.issue_url || existingComment?.html_url || ""; - const match = String(targetURL).match(/\/issues\/(\d+)(?:[#/?]|$)/) || String(targetURL).match(/\/pull\/(\d+)(?:[#/?]|$)/); + const targetURL = existingComment?.issue_url || ""; + const match = String(targetURL).match(/\/issues\/(\d+)(?:[#/?]|$)/); if (match) { itemNumber = Number(match[1]); } From c35f9fede45c5b8ad427b1e36f5e6f0e884b5e69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:18:13 +0000 Subject: [PATCH 14/23] Refresh branch and fix validation fallout Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/add_comment.test.cjs | 2 ++ pkg/workflow/awf_command_builder.go | 4 ++-- pkg/workflow/awf_command_builder_test.go | 3 --- pkg/workflow/awf_env.go | 2 +- pkg/workflow/awf_env_test.go | 3 --- pkg/workflow/awf_feature_flags_test.go | 11 ++--------- 6 files changed, 7 insertions(+), 18 deletions(-) diff --git a/actions/setup/js/add_comment.test.cjs b/actions/setup/js/add_comment.test.cjs index ead1d2f3f33..32b0d906c5c 100644 --- a/actions/setup/js/add_comment.test.cjs +++ b/actions/setup/js/add_comment.test.cjs @@ -3,9 +3,11 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; +import { syncRuntimePromptTemplates } from "./test_prompt_templates.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +syncRuntimePromptTemplates(import.meta.url); describe("add_comment", () => { let mockCore; diff --git a/pkg/workflow/awf_command_builder.go b/pkg/workflow/awf_command_builder.go index 4f3673575e5..898ca786a7b 100644 --- a/pkg/workflow/awf_command_builder.go +++ b/pkg/workflow/awf_command_builder.go @@ -38,8 +38,8 @@ func BuildAWFCommand(config AWFCommandConfig) string { // Auto-detect ARC/DinD split daemon topology at runtime: probe DOCKER_HOST for a // tcp:// scheme and pass it through to AWF via --docker-host. // All behaviors avoid requiring workflow-authored sandbox.agent.args for standard ARC DinD setups. - // When AWF also supports chroot config (v0.27.1+), the Python patch body is embedded inside - // the same if-block so the script only contains one DOCKER_HOST condition check. + // When AWF also supports chroot config (v0.27.1+), the chroot patch logic is embedded + // inside the same if-block so the script only contains one DOCKER_HOST condition check. arcDindPrefixProbe := "" arcDindDockerHostProbe := fmt.Sprintf(`%s="" if [[ "${DOCKER_HOST:-}" =~ %s ]]; then diff --git a/pkg/workflow/awf_command_builder_test.go b/pkg/workflow/awf_command_builder_test.go index f116cbf9a9c..76098a935d2 100644 --- a/pkg/workflow/awf_command_builder_test.go +++ b/pkg/workflow/awf_command_builder_test.go @@ -69,7 +69,6 @@ func TestBuildAWFArgsAuditDir(t *testing.T) { // TestBuildAWFArgsAllowHostPorts tests that BuildAWFArgs includes --allow-host-ports // with port 80, 443, and the MCP gateway port so the AWF agent container can reach // the gateway through the firewall's iptables rules. - func TestBuildAWFArgsAllowHostPorts(t *testing.T) { t.Run("includes default MCP gateway port 8080", func(t *testing.T) { config := AWFCommandConfig{ @@ -190,7 +189,6 @@ func TestBuildAWFArgsAllowHostPorts(t *testing.T) { // TestBuildAWFArgsDiagnosticLogs tests that BuildAWFArgs includes --diagnostic-logs // only when features.awf-diagnostic-logs is enabled. - func TestBuildAWFArgsDiagnosticLogs(t *testing.T) { baseWorkflow := func(features map[string]any) *WorkflowData { return &WorkflowData{ @@ -236,7 +234,6 @@ func TestBuildAWFArgsDiagnosticLogs(t *testing.T) { // TestBuildAWFArgsMemoryLimit tests that BuildAWFArgs passes --memory-limit // when sandbox.agent.memory is configured in the workflow frontmatter - func TestBuildAWFArgsMemoryLimit(t *testing.T) { t.Run("includes --memory-limit flag when memory is configured", func(t *testing.T) { workflowData := &WorkflowData{ diff --git a/pkg/workflow/awf_env.go b/pkg/workflow/awf_env.go index ed9c2065e4e..b5c24c4dfd9 100644 --- a/pkg/workflow/awf_env.go +++ b/pkg/workflow/awf_env.go @@ -43,7 +43,7 @@ func applyDefaultMaxAICreditsEnvToMap(env map[string]string, workflowData *Workf // GitHub Actions runtime expression to that variable, so the ${{ }} expression // lives on one clean, dedicated line rather than being embedded inside the JSON. // -// shellEscapeArgWithVarPreserved is then used to double-quote the JSON arg while +// shellEscapeArgWithVarsPreserved is then used to double-quote the JSON arg while // preserving the ${varName} reference for bash expansion and escaping bare $ signs // (e.g. "$schema" → "\$schema"). func injectMaxAICreditsExpression(awfConfigJSON string, expr string) string { diff --git a/pkg/workflow/awf_env_test.go b/pkg/workflow/awf_env_test.go index 29b69d81bba..78118c19adc 100644 --- a/pkg/workflow/awf_env_test.go +++ b/pkg/workflow/awf_env_test.go @@ -319,6 +319,3 @@ func TestMainAgentRunUsesStandardCreditsExpressionNotDetectionExpression(t *test assert.NotContains(t, stepContent, "vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS", "main-agent run must not use detection credits expression") } - -// TestGetAWFCommandPrefixNetworkIsolation tests that GetAWFCommandPrefix returns the correct -// command based on security mode: strict (default, no sudo) or legacy (sudo -E awf). diff --git a/pkg/workflow/awf_feature_flags_test.go b/pkg/workflow/awf_feature_flags_test.go index 938279b823f..e7697afaeb5 100644 --- a/pkg/workflow/awf_feature_flags_test.go +++ b/pkg/workflow/awf_feature_flags_test.go @@ -3,8 +3,9 @@ package workflow import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestAWFSupportsExcludeEnv(t *testing.T) { @@ -125,7 +126,6 @@ func TestAWFSupportsCliProxy(t *testing.T) { } // TestAWFSupportsAllowHostPorts tests the awfSupportsAllowHostPorts version gate function. - func TestAWFSupportsAllowHostPorts(t *testing.T) { tests := []struct { name string @@ -178,7 +178,6 @@ func TestAWFSupportsAllowHostPorts(t *testing.T) { } // TestAWFSupportsDockerHostPathPrefix tests the awfSupportsDockerHostPathPrefix version gate. - func TestAWFSupportsDockerHostPathPrefix(t *testing.T) { tests := []struct { name string @@ -262,7 +261,6 @@ func TestAWFSupportsTokenSteering(t *testing.T) { } // TestAWFSupportsChrootConfig tests the awfSupportsChrootConfig version gate. - func TestAWFSupportsChrootConfig(t *testing.T) { tests := []struct { name string @@ -310,7 +308,6 @@ func TestAWFSupportsChrootConfig(t *testing.T) { } // TestAWFSupportsAPIProxyProviders tests the awfSupportsAPIProxyProviders version gate. - func TestAWFSupportsAPIProxyProviders(t *testing.T) { tests := []struct { name string @@ -356,7 +353,3 @@ func TestAWFSupportsAPIProxyProviders(t *testing.T) { }) } } - -// TestBuildAWFCommand_IncludesChrootInjectScript verifies that BuildAWFCommand -// includes the chroot injection script in the generated run step when the AWF -// version supports it. From b9f7fe864138db44cc9f6bc40e7a86d72c00ccf3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:24:27 +0000 Subject: [PATCH 15/23] Start safe output spec follow-up Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/workflows/smoke-pydantic.lock.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index 06170cd9a15..934df1d8cbf 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -614,9 +614,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -624,6 +633,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, From 716d34c663cfd5e6ffe016a2402873b0077c9f29 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:33:15 +0000 Subject: [PATCH 16/23] Add changeset --- .changeset/patch-harden-safe-output-field-validation.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-harden-safe-output-field-validation.md diff --git a/.changeset/patch-harden-safe-output-field-validation.md b/.changeset/patch-harden-safe-output-field-validation.md new file mode 100644 index 00000000000..f078e54cbee --- /dev/null +++ b/.changeset/patch-harden-safe-output-field-validation.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Harden safe-output field validation so handlers only accept declared fields, comment reuse requires trusted allowlisted IDs, and privileged patch/upload processors derive sensitive metadata from trusted runtime state. From ac6e2922370914d194fd0628f0c085335e83fae7 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:20:19 -0700 Subject: [PATCH 17/23] Improve test quality: migrate tracker_id_integration_test.go to testify assertions (#51178) * Initial plan * Improve test quality: migrate tracker_id_integration_test.go to testify Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Fix engine definition custom lint findings Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Use consistent engine import empty checks Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Co-authored-by: Peli de Halleux Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/workflow/engine_definition.go | 22 ++- pkg/workflow/tracker_id_integration_test.go | 164 +++++++++++++------- 2 files changed, 126 insertions(+), 60 deletions(-) diff --git a/pkg/workflow/engine_definition.go b/pkg/workflow/engine_definition.go index 060cb7bd8a8..1e78160b3e4 100644 --- a/pkg/workflow/engine_definition.go +++ b/pkg/workflow/engine_definition.go @@ -408,14 +408,20 @@ func downloadKnownEngineImports(ctx context.Context, rawURL string) ([]byte, err // knownEngineImportsTimeout; fetch and parse failures are treated as an empty // catalog so engine validation remains unchanged. func knownEngineImportFor(id string) (string, bool) { - knownEngineImportsMu.Lock() - if knownEngineImportsLoaded { - importPath, ok := knownEngineImports[strings.ToLower(id)] - knownEngineImportsMu.Unlock() + importPath, ok, initialized, download := func() (string, bool, bool, func(context.Context) ([]byte, error)) { + knownEngineImportsMu.Lock() + defer knownEngineImportsMu.Unlock() + + if knownEngineImportsLoaded { + importPath, ok := knownEngineImports[strings.ToLower(id)] + return importPath, ok, true, nil + } + + return "", false, false, knownEngineImportsDownload + }() + if initialized { return importPath, ok } - download := knownEngineImportsDownload - knownEngineImportsMu.Unlock() // Avoid holding the catalog mutex during the network fetch. Concurrent cold // callers may each fetch once, but only the first completed result is cached. @@ -428,7 +434,7 @@ func knownEngineImportFor(id string) (string, bool) { knownEngineImportsLoaded = true } - importPath, ok := knownEngineImports[strings.ToLower(id)] + importPath, ok = knownEngineImports[strings.ToLower(id)] return importPath, ok } @@ -453,7 +459,7 @@ func loadKnownEngineImports(download func(context.Context) ([]byte, error)) map[ for _, engine := range catalog.Engines { id := strings.ToLower(strings.TrimSpace(engine.ID)) importPath := strings.TrimSpace(engine.Import) - if id == "" || importPath == "" { + if len(id) == 0 || len(importPath) == 0 { continue } loaded[id] = knownEngineImportWithCompilerRef(importPath) diff --git a/pkg/workflow/tracker_id_integration_test.go b/pkg/workflow/tracker_id_integration_test.go index de9ba221540..f11bd6b2e91 100644 --- a/pkg/workflow/tracker_id_integration_test.go +++ b/pkg/workflow/tracker_id_integration_test.go @@ -5,17 +5,25 @@ package workflow import ( "os" "path/filepath" - "strings" "testing" - "github.com/github/gh-aw/pkg/stringutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/github/gh-aw/pkg/stringutil" "github.com/github/gh-aw/pkg/testutil" ) -func TestTrackerIDIntegration(t *testing.T) { - tmpDir := testutil.TempDir(t, "test-*") +// assertScriptUsesRequire asserts that the compiled lock file content sets the +// GH_AW_TRACKER_ID environment variable and loads scripts via require() (file +// mode, not inline). +func assertScriptUsesRequire(t *testing.T, contentStr string) { + t.Helper() + assert.Contains(t, contentStr, "GH_AW_TRACKER_ID", "expected GH_AW_TRACKER_ID environment variable to be set") + assert.Contains(t, contentStr, "require(", "expected scripts to be loaded using require()") +} +func TestTrackerIDIntegration(t *testing.T) { tests := []struct { name string workflowContent string @@ -82,70 +90,122 @@ Create a pull request. shouldHaveInScript: true, expectedTrackerID: "pr-tracker-123", }, + { + name: "Workflow with tracker-id and multiple safe-outputs", + workflowContent: `--- +on: workflow_dispatch +permissions: + contents: read +tracker-id: multi-output-1 +safe-outputs: + create-issue: + create-pull-request: +--- + +# Test Multiple Safe Outputs + +Create an issue and a pull request. +`, + shouldCompile: true, + shouldHaveEnvVar: true, + shouldHaveInScript: true, + expectedTrackerID: "multi-output-1", + }, + { + name: "Workflow with too-short tracker-id", + workflowContent: `--- +on: workflow_dispatch +permissions: + contents: read +tracker-id: short +safe-outputs: + create-issue: +--- + +# Test Short Tracker ID + +Create a test issue. +`, + shouldCompile: false, + }, + { + name: "Workflow with tracker-id containing spaces", + workflowContent: `--- +on: workflow_dispatch +permissions: + contents: read +tracker-id: has spaces +safe-outputs: + create-issue: +--- + +# Test Tracker ID With Spaces + +Create a test issue. +`, + shouldCompile: false, + }, + { + name: "Workflow with tracker-id containing invalid characters", + workflowContent: `--- +on: workflow_dispatch +permissions: + contents: read +tracker-id: bad!chars! +safe-outputs: + create-issue: +--- + +# Test Tracker ID With Invalid Characters + +Create a test issue. +`, + shouldCompile: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Each subtest gets its own tmpDir, which testutil.TempDir already + // registers for cleanup via t.Cleanup, so generated workflow and + // lock files are removed automatically even on early failures. + tmpDir := testutil.TempDir(t, "test-*") workflowFile := filepath.Join(tmpDir, "test.md") - err := os.WriteFile(workflowFile, []byte(tt.workflowContent), 0644) - if err != nil { - t.Fatalf("Failed to write test workflow: %v", err) - } + require.NoError(t, os.WriteFile(workflowFile, []byte(tt.workflowContent), 0644)) compiler := NewCompiler() // Use dev mode to test with local action paths compiler.SetActionMode(ActionModeDev) compiler.verbose = false - err = compiler.CompileWorkflow(workflowFile) + err := compiler.CompileWorkflow(workflowFile) - if tt.shouldCompile && err != nil { - t.Fatalf("Expected compilation to succeed, got error: %v", err) - } - if !tt.shouldCompile && err == nil { - t.Fatal("Expected compilation to fail, but it succeeded") + if tt.shouldCompile { + require.NoError(t, err, "expected compilation to succeed") + } else { + require.Error(t, err, "expected compilation to fail") + return } - if tt.shouldCompile { - lockFile := stringutil.MarkdownToLockFile(workflowFile) - content, err := os.ReadFile(lockFile) - if err != nil { - t.Fatalf("Failed to read lock file: %v", err) - } - - contentStr := string(content) - - if tt.shouldHaveEnvVar { - envVarLine := "GH_AW_TRACKER_ID: \"" + tt.expectedTrackerID + "\"" - if !strings.Contains(contentStr, envVarLine) { - t.Errorf("Expected lock file to contain env var '%s', but it didn't", envVarLine) - } - } else { - // The JavaScript code will always read process.env.GH_AW_TRACKER_ID - // but the environment variable should not be set - envVarLine := "GH_AW_TRACKER_ID: \"" - if strings.Contains(contentStr, envVarLine) { - t.Error("Expected lock file to NOT set GH_AW_TRACKER_ID env var, but it did") - } - } - - if tt.shouldHaveInScript { - // Check that tracker-id environment variable is set - if !strings.Contains(contentStr, "GH_AW_TRACKER_ID") { - t.Error("Expected GH_AW_TRACKER_ID environment variable to be set") - } - // Check that scripts are loaded using require() (file mode, not inline) - if !strings.Contains(contentStr, "require(") { - t.Error("Expected scripts to be loaded using require()") - } - } - - // Clean up lock file - os.Remove(lockFile) + lockFile := stringutil.MarkdownToLockFile(workflowFile) + + content, err := os.ReadFile(lockFile) + require.NoError(t, err, "failed to read lock file") + + contentStr := string(content) + + if tt.shouldHaveEnvVar { + envVarLine := "GH_AW_TRACKER_ID: \"" + tt.expectedTrackerID + "\"" + assert.Contains(t, contentStr, envVarLine, "expected lock file to contain tracker-id env var") + } else { + // The JavaScript code will always read process.env.GH_AW_TRACKER_ID + // but the environment variable should not be set + assert.NotContains(t, contentStr, "GH_AW_TRACKER_ID: \"", "expected lock file to NOT set GH_AW_TRACKER_ID env var") } - // Clean up workflow file - os.Remove(workflowFile) + if tt.shouldHaveInScript { + assertScriptUsesRequire(t, contentStr) + } }) } } From 23a10f0386aa409cd4038671be2acbf5cf4abbc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:35:30 +0000 Subject: [PATCH 18/23] Update safe output comment ID spec Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../docs/specs/safe-outputs-specification.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/src/content/docs/specs/safe-outputs-specification.md b/docs/src/content/docs/specs/safe-outputs-specification.md index 646afc503c5..f3718d729b0 100644 --- a/docs/src/content/docs/specs/safe-outputs-specification.md +++ b/docs/src/content/docs/specs/safe-outputs-specification.md @@ -7,7 +7,7 @@ sidebar: # Safe Outputs MCP Gateway Specification -**Version**: 1.28.1
+**Version**: 1.28.2
**Status**: Working Draft
**Publication Date**: 2026-08-07
**Editor**: GitHub Agentic Workflows Team
@@ -1672,6 +1672,7 @@ create-issue: ```yaml add-comment: target: "issue" | "pull_request" | "discussion" | "*" + allows-comment-ids: [12345, 67890] # Required before agents may supply comment_id with target: "*" hide-older-comments: true # Hide previous workflow comments discussions: false # Exclude discussions:write permission (optional) target-repo: owner/repo @@ -2324,7 +2325,7 @@ These extensions apply to safe-output processor messages for `add_comment` (incl - `max`: Operation limit (default: 1) - `target`: Filter by type ("issue", "pull_request", "discussion", "*"). This configuration field applies to static workflow configuration (`safe-outputs.add-comment.target`) and is distinct from the runtime per-message `target: "status"` extension above. -- `allows-comment-ids`: Trusted allowlist of issue/PR comment IDs that the agent may update with `comment_id` when `target: "*"` is configured. Accepts an array of strings or a GitHub Actions expression that resolves to a list. +- `allows-comment-ids`: Trusted allowlist of issue/PR comment IDs that the agent may update with `comment_id` when `target: "*"` is configured. This field is REQUIRED before any agent-supplied `comment_id` is honored. Accepts an array of positive integer IDs, strings containing positive integer IDs, or a GitHub Actions expression that resolves to such a list. - `hide-older-comments`: Hide previous workflow comments - `discussions`: Control `discussions:write` permission (default: false). Set to `true` to comment on discussions. - `target-repo`: Cross-repository target @@ -5413,16 +5414,23 @@ safe-outputs: This specification revision aligns with directly relevant `CHANGELOG.md` entries and with the current reviewer/status-comment PR updates: -- **Commit 9d80a262**: safe-output field validation was hardened so normalized downstream payloads contain only schema/config-declared fields, agent-controlled `add_comment.comment_id` was removed, upload asset metadata is re-derived by the privileged job, and patch base metadata is embedded in the generated patch. +- **Commit 9d80a262**: safe-output field validation was hardened so normalized downstream payloads contain only schema/config-declared fields, unconditional agent-controlled `add_comment.comment_id` was removed, upload asset metadata is re-derived by the privileged job, and patch base metadata is embedded in the generated patch. +- **Commit 178ff313**: `add_comment.comment_id` was reintroduced only for workflows that configure `safe-outputs.add-comment.target: "*"` and provide a trusted `safe-outputs.add-comment.allows-comment-ids` allowlist containing the requested comment ID. - **v0.40.1**: `add_comment` discussion handling was updated to auto-detect discussion context without requiring a `discussion` flag. - **v0.40.1**: append-only status comment behavior was documented for smoke workflow execution. - **Earlier changelog entry**: status comments were decoupled from default AI reaction behavior; explicit `on.status-comment` configuration is required when status comments are desired. - **Earlier changelog entry**: `command` trigger was renamed to `slash_command` with deprecation compatibility. +**Version 1.28.2** (2026-08-07): + +- **Added**: Controlled `add_comment.comment_id` support for wildcard comment targets. Agent-supplied comment IDs MAY be accepted only when `safe-outputs.add-comment.target` is `"*"` and the exact positive integer ID appears in trusted `safe-outputs.add-comment.allows-comment-ids` workflow state. +- **Specified**: `allows-comment-ids` is REQUIRED before any agent-supplied `comment_id` is honored and accepts literal positive integer IDs, stringified positive integer IDs, or GitHub Actions expressions that resolve to such a list. +- **Updated**: Publication metadata to 1.28.2. + **Version 1.28.1** (2026-08-07): - **Specified**: Normalized downstream safe-output payloads MUST include only `type` plus schema/config-declared fields, with undeclared agent-supplied fields stripped before handler or privileged-job consumption. -- **Removed**: Agent-controlled `add_comment.comment_id` from the MCP input contract; status-comment reuse is limited to `target: "status"` with reusable comment IDs obtained from trusted workflow state. +- **Removed**: Unconditional agent-controlled `add_comment.comment_id` from the default MCP input contract; status-comment reuse is limited to `target: "status"` with reusable comment IDs obtained from trusted workflow state. - **Specified**: Optional advisory/enrichment fields marked `x-strip-on-error` MAY be omitted when invalid. - **Specified**: Upload asset staging and publication MUST derive collision-resistant staged filenames and asset metadata from trusted staged files rather than agent-supplied metadata. - **Specified**: Patch base metadata MUST be derived from the generated patch, and agent-supplied `diff_size` and base-commit metadata MUST NOT control privileged patch processing. From a15b0f7b9bf30600aca82a18f44df0a1549ef41e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:06:12 +0000 Subject: [PATCH 19/23] Fix lint-go: use maps.Copy and drop unneeded nil check Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/codemod_bash_allowlist_unsupported_engine.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/cli/codemod_bash_allowlist_unsupported_engine.go b/pkg/cli/codemod_bash_allowlist_unsupported_engine.go index 05a2d4ab215..74e85a2f638 100644 --- a/pkg/cli/codemod_bash_allowlist_unsupported_engine.go +++ b/pkg/cli/codemod_bash_allowlist_unsupported_engine.go @@ -3,6 +3,7 @@ package cli import ( "encoding/json" "fmt" + "maps" "path/filepath" "strings" @@ -127,11 +128,7 @@ func resolveEffectiveBashTools(content string, frontmatter map[string]any, fileP // Merge external tools into the top-level tools map, line by line (each line is a JSON object). effective := make(map[string]any) - if topTools != nil { - for k, v := range topTools { - effective[k] = v - } - } + maps.Copy(effective, topTools) for line := range strings.SplitSeq(allExternalTools, "\n") { line = strings.TrimSpace(line) if line == "" || line == "{}" { From 9c43ee37d496d558ba5a6b5290349d033b2d6008 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:25:14 +0000 Subject: [PATCH 20/23] Fix filename quoting bypass in post-apply size/protection checks Co-authored-by: gh-aw-bot <4175913+gh-aw-bot@users.noreply.github.com> --- .../setup/js/push_to_pull_request_branch.cjs | 14 +++----- ...o_pull_request_branch.integration.test.cjs | 33 +++++++++++++++++++ .../js/push_to_pull_request_branch.test.cjs | 12 +++---- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 989a0f590c1..bb58d5a1247 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -114,11 +114,8 @@ function parsePositiveInteger(value) { * @returns {Promise} */ async function getBundlePreApplyFiles(exec, gitOptions, rangeBaseRef, bundleRef) { - const bundleDiffResult = await exec.getExecOutput("git", ["diff", "--name-only", "--no-renames", `${rangeBaseRef}..${bundleRef}`], gitOptions); - return bundleDiffResult.stdout - .split("\n") - .map(f => f.trim()) - .filter(Boolean); + const bundleDiffResult = await exec.getExecOutput("git", ["diff", "--name-only", "--no-renames", "-z", `${rangeBaseRef}..${bundleRef}`], gitOptions); + return bundleDiffResult.stdout.split("\0").filter(Boolean); } /** @@ -1219,11 +1216,8 @@ async function main(config = {}) { // (see github/agentic-workflows#539) let agentChangedFiles = []; { - const diffResult = await exec.getExecOutput("git", ["diff", "--name-only", "--no-renames", `${rangeBaseRef}..HEAD`], baseGitOpts); - const actualFiles = diffResult.stdout - .split("\n") - .map(f => f.trim()) - .filter(Boolean); + const diffResult = await exec.getExecOutput("git", ["diff", "--name-only", "--no-renames", "-z", `${rangeBaseRef}..HEAD`], baseGitOpts); + const actualFiles = diffResult.stdout.split("\0").filter(Boolean); agentChangedFiles = actualFiles; if (actualFiles.length > 0) { core.info(`Post-apply verification: ${actualFiles.length} file(s) actually modified`); diff --git a/actions/setup/js/push_to_pull_request_branch.integration.test.cjs b/actions/setup/js/push_to_pull_request_branch.integration.test.cjs index 5181ab8238e..f8535aa1233 100644 --- a/actions/setup/js/push_to_pull_request_branch.integration.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.integration.test.cjs @@ -139,4 +139,37 @@ describe("push_to_pull_request_branch bundle integration", () => { expect(actualFiles.sort()).toEqual(["feature.txt", "main.txt"]); }); + + it("resolves filenames that git would otherwise quote (non-ASCII characters)", async () => { + const branchName = "autoloop/quoted-path-bundle"; + const sourceRepo = createRepo("push-pr-quoted-source-"); + const targetRepo = createRepo("push-pr-quoted-target-"); + tempDirs.push(sourceRepo, targetRepo); + + writeRepoFile(sourceRepo, "README.md", "base\n"); + execGit(["add", "README.md"], { cwd: sourceRepo }); + execGit(["commit", "-m", "base"], { cwd: sourceRepo }); + execGit(["branch", "-M", "main"], { cwd: sourceRepo }); + const baseSha = execGit(["rev-parse", "HEAD"], { cwd: sourceRepo }).stdout.trim(); + + execGit(["checkout", "-b", branchName], { cwd: sourceRepo }); + const quotedFileName = "résumé.txt"; + writeRepoFile(sourceRepo, quotedFileName, "quoted path change\n"); + execGit(["add", quotedFileName], { cwd: sourceRepo }); + execGit(["commit", "-m", "quoted path change"], { cwd: sourceRepo }); + + const bundlePath = path.join(sourceRepo, "quoted.bundle"); + execGit(["bundle", "create", bundlePath, `refs/heads/${branchName}`], { cwd: sourceRepo }); + + fetchBaseCommit(targetRepo, sourceRepo, baseSha, branchName); + const bundleRef = "refs/bundles/test-quoted-bundle"; + execGit(["fetch", bundlePath, `refs/heads/${branchName}:${bundleRef}`], { cwd: targetRepo }); + + const actualFiles = await getBundlePreApplyFiles(createExecApi(targetRepo), {}, baseSha, bundleRef); + + // Without NUL-delimited (`-z`) diff output, git would quote this path (e.g. "r\303\251sum\303\251.txt"), + // causing downstream filesystem lookups keyed on the raw name to fail and silently drop the file + // from size/protection checks. + expect(actualFiles).toEqual([quotedFileName]); + }); }); diff --git a/actions/setup/js/push_to_pull_request_branch.test.cjs b/actions/setup/js/push_to_pull_request_branch.test.cjs index 30013940539..a7ffb4be824 100644 --- a/actions/setup/js/push_to_pull_request_branch.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.test.cjs @@ -2144,7 +2144,7 @@ index 0000000..abc1234 mockExec.getExecOutput.mockImplementation(async (cmd, args) => { const argList = Array.isArray(args) ? args : []; if (cmd === "git" && argList[0] === "diff" && argList[1] === "--name-only") { - return { exitCode: 0, stdout: "test.txt\n", stderr: "" }; + return { exitCode: 0, stdout: "test.txt\0", stderr: "" }; } return { exitCode: 0, stdout: "abc123\n", stderr: "" }; }); @@ -2273,7 +2273,7 @@ index 0000000..abc1234 return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" }); } if (cmd === "git" && args[0] === "diff" && args[1] === "--name-only" && args[2] === "--no-renames") { - return Promise.resolve({ exitCode: 0, stdout: `${actualFiles.join("\n")}\n`, stderr: "" }); + return Promise.resolve({ exitCode: 0, stdout: `${actualFiles.join("\0")}\0`, stderr: "" }); } if (cmd === "git" && args[0] === "rev-list") { return Promise.resolve({ exitCode: 0, stdout: "2\n", stderr: "" }); @@ -2289,8 +2289,8 @@ index 0000000..abc1234 expect(mockCore.info).toHaveBeenCalledWith("Pre-apply bundle verification: 4 file(s) detected from bundle transport"); const diffCalls = mockExec.getExecOutput.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "diff" && args[1] === "--name-only" && args[2] === "--no-renames"); - expect(diffCalls.map(([, args]) => args[3])).toContain("remote-head..refs/bundles/push-feature-branch"); - expect(diffCalls.map(([, args]) => args[3])).toContain("remote-head..HEAD"); + expect(diffCalls.map(([, args]) => args[4])).toContain("remote-head..refs/bundles/push-feature-branch"); + expect(diffCalls.map(([, args]) => args[4])).toContain("remote-head..HEAD"); } finally { pushSignedSpy.mockRestore(); } @@ -2979,7 +2979,7 @@ ${diffs} const patchPath = createPatchFile("should-accept-files-that-match-the-allowed-files-pattern", createPatchWithFiles(".changeset/my-feature-fix.md")); mockExec.getExecOutput.mockImplementation(async (cmd, args) => { if (cmd === "git" && Array.isArray(args) && args[0] === "diff" && args[1] === "--name-only" && args[2] === "--no-renames") { - return { exitCode: 0, stdout: ".changeset/my-feature-fix.md\n", stderr: "" }; + return { exitCode: 0, stdout: ".changeset/my-feature-fix.md\0", stderr: "" }; } return { exitCode: 0, stdout: "abc123\n", stderr: "" }; }); @@ -3014,7 +3014,7 @@ ${diffs} const patchPath = createPatchFile("should-allow-a-protected-file-when-both-allowed-files-matche", createPatchWithFiles("package.json")); mockExec.getExecOutput.mockImplementation(async (cmd, args) => { if (cmd === "git" && Array.isArray(args) && args[0] === "diff" && args[1] === "--name-only" && args[2] === "--no-renames") { - return { exitCode: 0, stdout: "package.json\n", stderr: "" }; + return { exitCode: 0, stdout: "package.json\0", stderr: "" }; } return { exitCode: 0, stdout: "abc123\n", stderr: "" }; }); From 0efed9c7e681cea72f6cc2459d15f5f0bab2efb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:44:12 +0000 Subject: [PATCH 21/23] Address matt-pocock review: comment_id mutation, upload targetFileName re-derivation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/safe_outputs_handlers.cjs | 27 ++++++++------- .../setup/js/safe_outputs_handlers.test.cjs | 33 +++++++++++++++++++ actions/setup/js/upload_assets.cjs | 4 ++- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index ec6d911ce93..07aeddf2d4e 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -201,30 +201,30 @@ function parseAllowedCommentIds(value) { /** * Validate an agent-supplied add_comment.comment_id against the trusted workflow allowlist. + * Does not mutate the supplied entry; the caller is responsible for applying the normalized value. * @param {Record} entry * @param {Record} addCommentConfig - * @returns {{content: Array<{type: "text", text: string}>, isError: true} | null} + * @returns {{error: {content: Array<{type: "text", text: string}>, isError: true}} | {error: null, commentId: number | undefined}} */ function validateAllowedAddCommentId(entry, addCommentConfig) { if (entry.comment_id === undefined || entry.comment_id === null || String(entry.comment_id).trim() === "") { - return null; + return { error: null, commentId: undefined }; } if (addCommentConfig.target !== "*") { - return buildIntentErrorResponse("add_comment comment_id is only allowed when safe-outputs.add-comment.target is '*' and the ID is listed in safe-outputs.add-comment.allows-comment-ids."); + return { error: buildIntentErrorResponse("add_comment comment_id is only allowed when safe-outputs.add-comment.target is '*' and the ID is listed in safe-outputs.add-comment.allows-comment-ids.") }; } const commentId = Number(entry.comment_id); if (!Number.isInteger(commentId) || commentId <= 0) { - return buildIntentErrorResponse("add_comment comment_id must be a positive integer."); + return { error: buildIntentErrorResponse("add_comment comment_id must be a positive integer.") }; } const allowedCommentIds = parseAllowedCommentIds(addCommentConfig.allows_comment_ids ?? addCommentConfig["allows-comment-ids"]); if (allowedCommentIds.size === 0) { - return buildIntentErrorResponse("add_comment comment_id requires safe-outputs.add-comment.allows-comment-ids to list trusted comment IDs."); + return { error: buildIntentErrorResponse("add_comment comment_id requires safe-outputs.add-comment.allows-comment-ids to list trusted comment IDs.") }; } if (!allowedCommentIds.has(String(commentId))) { - return buildIntentErrorResponse("add_comment comment_id is not listed in safe-outputs.add-comment.allows-comment-ids."); + return { error: buildIntentErrorResponse("add_comment comment_id is not listed in safe-outputs.add-comment.allows-comment-ids.") }; } - entry.comment_id = commentId; - return null; + return { error: null, commentId }; } /** @@ -2031,9 +2031,14 @@ function createHandlers(server, appendSafeOutput, config = {}) { // Build the entry with a temporary_id const entry = { ...(args || {}), type: "add_comment" }; - const commentIdValidationError = validateAllowedAddCommentId(entry, addCommentConfig); - if (commentIdValidationError) { - return commentIdValidationError; + const commentIdValidationResult = validateAllowedAddCommentId(entry, addCommentConfig); + if (commentIdValidationResult.error) { + return commentIdValidationResult.error; + } + if (commentIdValidationResult.commentId === undefined) { + delete entry.comment_id; + } else { + entry.comment_id = commentIdValidationResult.commentId; } const wildcardTargetValidationError = validateWildcardTargetRequirement(entry); if (wildcardTargetValidationError) { diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs index 67c97315fcf..3fbb1f2b926 100644 --- a/actions/setup/js/safe_outputs_handlers.test.cjs +++ b/actions/setup/js/safe_outputs_handlers.test.cjs @@ -2381,6 +2381,39 @@ describe("safe_outputs_handlers", () => { expect(mockAppendSafeOutput).not.toHaveBeenCalled(); }); + it("should reject comment_id when add_comment target is not '*'", () => { + const targetingHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + add_comment: { + target: "triggering", + allows_comment_ids: ["12345"], + }, + }); + + const result = targetingHandlers.addCommentHandler({ item_number: 42, body: "Update an existing status-style comment.", comment_id: "12345" }); + + expect(result.isError).toBe(true); + const responseData = JSON.parse(result.content[0].text); + expect(responseData.result).toBe("error"); + expect(responseData.error).toContain("target is '*'"); + expect(mockAppendSafeOutput).not.toHaveBeenCalled(); + }); + + it("should reject comment_id when allows-comment-ids is empty", () => { + const wildcardHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + add_comment: { + target: "*", + }, + }); + + const result = wildcardHandlers.addCommentHandler({ body: "Update an existing status-style comment.", comment_id: "12345" }); + + expect(result.isError).toBe(true); + const responseData = JSON.parse(result.content[0].text); + expect(responseData.result).toBe("error"); + expect(responseData.error).toContain("allows-comment-ids"); + expect(mockAppendSafeOutput).not.toHaveBeenCalled(); + }); + it("should refuse reply_to_id when discussions are not enabled in config", () => { // Default handlers have no discussions: true in config // Discussion check precedes context check so this error surfaces regardless of event context diff --git a/actions/setup/js/upload_assets.cjs b/actions/setup/js/upload_assets.cjs index 998f42bd613..03ba8e91a54 100644 --- a/actions/setup/js/upload_assets.cjs +++ b/actions/setup/js/upload_assets.cjs @@ -138,7 +138,9 @@ async function main() { } const generatedTargetFileName = `${computedSha}${path.extname(fileName).toLowerCase()}`; - const targetFileName = asset.targetFileName || generatedTargetFileName; + // In path mode, the source path and content are re-derived from trusted staged state, + // so always compute the target filename server-side and ignore any agent-supplied value. + const targetFileName = pathFileName ? generatedTargetFileName : asset.targetFileName || generatedTargetFileName; if (targetFileName !== path.basename(targetFileName)) { core.setFailed(`${ERR_VALIDATION}: Invalid asset target filename: ${targetFileName}`); return; From c16df32cbe611927f0f99604d3614b5fa162eb33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:45:47 +0000 Subject: [PATCH 22/23] Clarify comment_id stripping intent per code review feedback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/safe_outputs_handlers.cjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 07aeddf2d4e..73ea7006a87 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -2036,6 +2036,9 @@ function createHandlers(server, appendSafeOutput, config = {}) { return commentIdValidationResult.error; } if (commentIdValidationResult.commentId === undefined) { + // entry was spread from args, so a blank/whitespace comment_id (rather than an + // absent one) could still be sitting on entry; strip it so downstream code never + // sees an unvalidated raw value. delete entry.comment_id; } else { entry.comment_id = commentIdValidationResult.commentId; From e4d30d747b7a8a37f3dd9451b7830dd57a65e321 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 23:13:07 +0000 Subject: [PATCH 23/23] Refresh branch with main and recompile workflows Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/workflows/copilot-session-insights.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 697034c1663..d7102b3c77f 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1a205bf3354b3cf4ede0f23f8443a2f0dd553ee0a66727371426742bf0733e68","body_hash":"2a96a21304b975c51504dd556731857febd942d37b53e1fcc5b42a29855ab23e","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.223"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1a205bf3354b3cf4ede0f23f8443a2f0dd553ee0a66727371426742bf0733e68","body_hash":"2a96a21304b975c51504dd556731857febd942d37b53e1fcc5b42a29855ab23e","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.224"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md #