From 2193f249927effc1ccd63997df06ff998a43ca86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:10:37 +0000 Subject: [PATCH 1/3] Initial plan From 7916706ba81e2b843aab9882385e9c4f383a86a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:41:29 +0000 Subject: [PATCH 2/3] Add staged field support to all safe output handlers individually - Go: Add AddIfTrue("staged", c.Staged) to all handler config builders so per-handler staged flag is included in GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG JSON - JS: Update all handlers to check config.staged === true in addition to global GH_AW_SAFE_OUTPUTS_STAGED env var - JS: Add staged support to dispatch_workflow.cjs, call_workflow.cjs, and create_code_scanning_alert.cjs (which had no staged support) - JS: Update pr_review_buffer.cjs with setStaged() method; update submit_pr_review.cjs and create_pr_review_comment.cjs to propagate config.staged to the shared buffer - Tests: Extend TestHandlerConfigStagedMode with more handler type cases Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/e17356ca-3bc9-4cd4-8dfb-ef5205b19942 --- actions/setup/js/add_comment.cjs | 2 +- actions/setup/js/add_labels.cjs | 2 +- actions/setup/js/add_reviewer.cjs | 2 +- actions/setup/js/assign_milestone.cjs | 2 +- actions/setup/js/assign_to_user.cjs | 2 +- .../setup/js/autofix_code_scanning_alert.cjs | 2 +- actions/setup/js/call_workflow.cjs | 13 +++ actions/setup/js/close_discussion.cjs | 2 +- actions/setup/js/close_issue.cjs | 2 +- .../setup/js/create_code_scanning_alert.cjs | 13 +++ actions/setup/js/create_discussion.cjs | 2 +- actions/setup/js/create_issue.cjs | 2 +- actions/setup/js/create_pr_review_comment.cjs | 5 + actions/setup/js/create_project.cjs | 2 +- .../setup/js/create_project_status_update.cjs | 2 +- actions/setup/js/create_pull_request.cjs | 2 +- actions/setup/js/dispatch_workflow.cjs | 13 +++ actions/setup/js/hide_comment.cjs | 2 +- actions/setup/js/link_sub_issue.cjs | 2 +- .../mark_pull_request_as_ready_for_review.cjs | 2 +- actions/setup/js/pr_review_buffer.cjs | 18 ++- actions/setup/js/remove_labels.cjs | 2 +- .../setup/js/reply_to_pr_review_comment.cjs | 2 +- actions/setup/js/resolve_pr_review_thread.cjs | 2 +- actions/setup/js/set_issue_type.cjs | 2 +- actions/setup/js/submit_pr_review.cjs | 5 + actions/setup/js/unassign_from_user.cjs | 2 +- actions/setup/js/update_handler_factory.cjs | 2 +- actions/setup/js/update_project.cjs | 2 +- actions/setup/js/update_release.cjs | 2 +- pkg/workflow/compiler_safe_outputs_config.go | 39 ++++++- .../compiler_safe_outputs_config_test.go | 107 ++++++++++++++++++ 32 files changed, 235 insertions(+), 26 deletions(-) diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index bc9c59e2a4f..04aea3d2413 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -312,7 +312,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; // Check if append-only-comments is enabled in messages config const messagesConfig = getMessages(); diff --git a/actions/setup/js/add_labels.cjs b/actions/setup/js/add_labels.cjs index 06fab518bf6..c0b555a1086 100644 --- a/actions/setup/js/add_labels.cjs +++ b/actions/setup/js/add_labels.cjs @@ -31,7 +31,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Add labels configuration: max=${maxCount}`); if (allowedLabels.length > 0) { diff --git a/actions/setup/js/add_reviewer.cjs b/actions/setup/js/add_reviewer.cjs index 982a75ab87e..ff5c0e3b778 100644 --- a/actions/setup/js/add_reviewer.cjs +++ b/actions/setup/js/add_reviewer.cjs @@ -24,7 +24,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Add reviewer configuration: max=${maxCount}`); if (allowedReviewers.length > 0) { diff --git a/actions/setup/js/assign_milestone.cjs b/actions/setup/js/assign_milestone.cjs index f35178b07a9..fe44d1c5b27 100644 --- a/actions/setup/js/assign_milestone.cjs +++ b/actions/setup/js/assign_milestone.cjs @@ -25,7 +25,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Assign milestone configuration: max=${maxCount}`); if (allowedMilestones.length > 0) { diff --git a/actions/setup/js/assign_to_user.cjs b/actions/setup/js/assign_to_user.cjs index 55475ce6c65..0252ba09fe6 100644 --- a/actions/setup/js/assign_to_user.cjs +++ b/actions/setup/js/assign_to_user.cjs @@ -31,7 +31,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Assign to user configuration: max=${maxCount}, unassign_first=${unassignFirst}`); if (allowedAssignees.length > 0) { diff --git a/actions/setup/js/autofix_code_scanning_alert.cjs b/actions/setup/js/autofix_code_scanning_alert.cjs index 880237bc2e4..8bc2fba1d48 100644 --- a/actions/setup/js/autofix_code_scanning_alert.cjs +++ b/actions/setup/js/autofix_code_scanning_alert.cjs @@ -19,7 +19,7 @@ const HANDLER_TYPE = "autofix_code_scanning_alert"; async function main(config = {}) { // Extract configuration const maxCount = config.max || 10; - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Add code scanning autofix configuration: max=${maxCount}`); if (isStaged) logStagedPreviewInfo("no changes will be written"); diff --git a/actions/setup/js/call_workflow.cjs b/actions/setup/js/call_workflow.cjs index 3cb20a15ef4..5e809abdf7a 100644 --- a/actions/setup/js/call_workflow.cjs +++ b/actions/setup/js/call_workflow.cjs @@ -9,6 +9,7 @@ const HANDLER_TYPE = "call_workflow"; const { getErrorMessage } = require("./error_helpers.cjs"); +const { logStagedPreviewInfo } = require("./staged_preview.cjs"); /** * Main handler factory for call_workflow. @@ -31,6 +32,7 @@ async function main(config = {}) { // Track how many items we've processed for max limit let processedCount = 0; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; /** * Message handler function that processes a single call_workflow message. @@ -85,6 +87,17 @@ async function main(config = {}) { const inputs = message.inputs && typeof message.inputs === "object" ? message.inputs : {}; const payloadJson = JSON.stringify(inputs); + // If in staged mode, preview the workflow call without executing it + if (isStaged) { + logStagedPreviewInfo(`Would call workflow: ${workflowName} with payload: ${payloadJson}`); + return { + success: true, + staged: true, + workflow_name: workflowName, + payload: payloadJson, + }; + } + // Set the step outputs that the conditional `uses:` jobs check core.setOutput("call_workflow_name", workflowName); core.setOutput("call_workflow_payload", payloadJson); diff --git a/actions/setup/js/close_discussion.cjs b/actions/setup/js/close_discussion.cjs index fa83e0f39bc..c79632287be 100644 --- a/actions/setup/js/close_discussion.cjs +++ b/actions/setup/js/close_discussion.cjs @@ -163,7 +163,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Close discussion configuration: max=${maxCount}`); if (requiredLabels.length > 0) { diff --git a/actions/setup/js/close_issue.cjs b/actions/setup/js/close_issue.cjs index e400286801a..1b2604d98bb 100644 --- a/actions/setup/js/close_issue.cjs +++ b/actions/setup/js/close_issue.cjs @@ -91,7 +91,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Close issue configuration: max=${maxCount}, state_reason=${configStateReason}`); if (requiredLabels.length > 0) { diff --git a/actions/setup/js/create_code_scanning_alert.cjs b/actions/setup/js/create_code_scanning_alert.cjs index fd3e7771600..9285668c592 100644 --- a/actions/setup/js/create_code_scanning_alert.cjs +++ b/actions/setup/js/create_code_scanning_alert.cjs @@ -6,6 +6,7 @@ */ const { getErrorMessage } = require("./error_helpers.cjs"); +const { logStagedPreviewInfo } = require("./staged_preview.cjs"); const fs = require("fs"); const path = require("path"); @@ -29,6 +30,7 @@ async function main(config = {}) { // Track how many items we've processed for max limit let processedCount = 0; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; // Collect valid findings across all messages const validFindings = []; @@ -235,6 +237,17 @@ async function main(config = {}) { core.info(`Added security finding ${validFindings.length}: ${finding.severity} in ${finding.file}:${finding.line}`); + // If in staged mode, preview the finding without writing the SARIF file + if (isStaged) { + logStagedPreviewInfo(`Would create code scanning alert: ${finding.severity} in ${finding.file}:${finding.line} - ${finding.message}`); + return { + success: true, + staged: true, + finding: finding, + findingsCount: validFindings.length, + }; + } + // Generate/update SARIF file after each finding try { generateSarifFile(); diff --git a/actions/setup/js/create_discussion.cjs b/actions/setup/js/create_discussion.cjs index 9cae18d34bd..f947ad8fc63 100644 --- a/actions/setup/js/create_discussion.cjs +++ b/actions/setup/js/create_discussion.cjs @@ -318,7 +318,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; // Parse labels from config const labelsConfig = config.labels || []; diff --git a/actions/setup/js/create_issue.cjs b/actions/setup/js/create_issue.cjs index 593ba448731..9299689f9e6 100644 --- a/actions/setup/js/create_issue.cjs +++ b/actions/setup/js/create_issue.cjs @@ -218,7 +218,7 @@ async function main(config = {}) { const assignCopilot = process.env.GH_AW_ASSIGN_COPILOT === "true"; // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Default target repo: ${defaultTargetRepo}`); if (allowedRepos.size > 0) { diff --git a/actions/setup/js/create_pr_review_comment.cjs b/actions/setup/js/create_pr_review_comment.cjs index 401a058b4c2..8384ec1d7e5 100644 --- a/actions/setup/js/create_pr_review_comment.cjs +++ b/actions/setup/js/create_pr_review_comment.cjs @@ -46,6 +46,11 @@ async function main(config = {}) { core.info(`Allowed repos: ${Array.from(allowedRepos).join(", ")}`); } + // Propagate per-handler staged flag to the shared PR review buffer + if (config.staged === true) { + buffer.setStaged(true); + } + // Track how many items we've processed for max limit let processedCount = 0; diff --git a/actions/setup/js/create_project.cjs b/actions/setup/js/create_project.cjs index 749ca6243c5..2ea7cd1ca31 100644 --- a/actions/setup/js/create_project.cjs +++ b/actions/setup/js/create_project.cjs @@ -298,7 +298,7 @@ async function main(config = {}, githubClient = null) { const configuredViews = Array.isArray(config.views) ? config.views : []; // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; // Use the provided github client, or fall back to the global github object // The global github object is available when running via github-script action diff --git a/actions/setup/js/create_project_status_update.cjs b/actions/setup/js/create_project_status_update.cjs index b241020411e..1442dd3aa74 100644 --- a/actions/setup/js/create_project_status_update.cjs +++ b/actions/setup/js/create_project_status_update.cjs @@ -288,7 +288,7 @@ async function main(config = {}, githubClient = null) { const maxCount = config.max || 10; // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; // Use the provided github client, or fall back to the global github object // @ts-ignore - global.github is set by setupGlobals() from github-script context diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index 1b04c751a61..94445a2940e 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -160,7 +160,7 @@ async function main(config = {}) { const triggeringIssueNumber = typeof context !== "undefined" && context.payload?.issue?.number && !context.payload?.issue?.pull_request ? context.payload.issue.number : undefined; // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Base branch: ${configBaseBranch || "(dynamic - resolved per target repo)"}`); core.info(`Default target repo: ${defaultTargetRepo}`); diff --git a/actions/setup/js/dispatch_workflow.cjs b/actions/setup/js/dispatch_workflow.cjs index 757a257506a..949d9f50b95 100644 --- a/actions/setup/js/dispatch_workflow.cjs +++ b/actions/setup/js/dispatch_workflow.cjs @@ -11,6 +11,7 @@ const HANDLER_TYPE = "dispatch_workflow"; const { getErrorMessage } = require("./error_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { resolveTargetRepoConfig, parseRepoSlug, validateTargetRepo } = require("./repo_helpers.cjs"); +const { logStagedPreviewInfo } = require("./staged_preview.cjs"); /** * Main handler factory for dispatch_workflow @@ -73,6 +74,7 @@ async function main(config = {}) { // Track how many items we've processed for max limit let processedCount = 0; let lastDispatchTime = 0; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; // Helper function to get the default branch of the dispatch target repository const getDefaultBranchRef = async () => { @@ -197,6 +199,17 @@ async function main(config = {}) { const workflowFile = `${workflowName}${extension}`; core.info(`Dispatching workflow: ${workflowFile}`); + // If in staged mode, preview the dispatch without executing it + if (isStaged) { + logStagedPreviewInfo(`Would dispatch workflow: ${workflowFile} in ${resolvedRepoSlug} with ref: ${ref}`); + return { + success: true, + staged: true, + workflow_name: workflowName, + inputs: inputs, + }; + } + // Dispatch the workflow using the resolved file. // Request return_run_details for newer GitHub API support; fall back without it // for older GitHub Enterprise Server deployments that don't support the parameter. diff --git a/actions/setup/js/hide_comment.cjs b/actions/setup/js/hide_comment.cjs index b98edb077c9..064b91b04c9 100644 --- a/actions/setup/js/hide_comment.cjs +++ b/actions/setup/js/hide_comment.cjs @@ -52,7 +52,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Hide comment configuration: max=${maxCount}`); if (allowedReasons.length > 0) { diff --git a/actions/setup/js/link_sub_issue.cjs b/actions/setup/js/link_sub_issue.cjs index 5fd129b3a58..ecbc949cae6 100644 --- a/actions/setup/js/link_sub_issue.cjs +++ b/actions/setup/js/link_sub_issue.cjs @@ -22,7 +22,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; if (parentRequiredLabels.length > 0) { core.info(`Parent required labels: ${JSON.stringify(parentRequiredLabels)}`); diff --git a/actions/setup/js/mark_pull_request_as_ready_for_review.cjs b/actions/setup/js/mark_pull_request_as_ready_for_review.cjs index 0ac07e03c29..a848eb5b8ef 100644 --- a/actions/setup/js/mark_pull_request_as_ready_for_review.cjs +++ b/actions/setup/js/mark_pull_request_as_ready_for_review.cjs @@ -88,7 +88,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Mark pull request as ready for review configuration: max=${maxCount}`); diff --git a/actions/setup/js/pr_review_buffer.cjs b/actions/setup/js/pr_review_buffer.cjs index e208d705c63..bb5111cebea 100644 --- a/actions/setup/js/pr_review_buffer.cjs +++ b/actions/setup/js/pr_review_buffer.cjs @@ -68,6 +68,8 @@ function createReviewBuffer() { /** @type {string} Footer mode: "always" (default), "none", or "if-body" */ let footerMode = "always"; + /** @type {boolean} Staged mode: when true, preview review without submitting (set via setStaged(), reset on buffer clear) */ + let stagedMode = false; /** * Add a validated comment to the buffer. * Rejects comments targeting a different repo/PR than the first comment. @@ -157,6 +159,18 @@ function createReviewBuffer() { } } + /** + * Set staged mode for the review buffer. + * When staged, submitReview() will preview the review without actually submitting. + * @param {boolean} value - Whether staged mode is enabled + */ + function setStaged(value) { + stagedMode = value; + if (value) { + core.info("PR review buffer staged mode enabled"); + } + } + /** * Check if there are buffered comments to submit. * @returns {boolean} @@ -261,7 +275,7 @@ function createReviewBuffer() { core.info(`Submitting PR review on ${repo}#${pullRequestNumber}: event=${event}, comments=${comments.length}, bodyLength=${body.length}`); // If in staged mode, preview the review without submitting - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || stagedMode; if (isStaged) { let summaryContent = "## 🎭 Staged Mode: PR Review Preview\n\n"; summaryContent += "The following PR review would be submitted if staged mode was disabled:\n\n"; @@ -396,6 +410,7 @@ function createReviewBuffer() { reviewContext = null; footerContext = null; footerMode = "always"; + stagedMode = false; } return { @@ -406,6 +421,7 @@ function createReviewBuffer() { setFooterContext, setFooterMode, setIncludeFooter: setFooterMode, // Backward compatibility alias + setStaged, hasBufferedComments, hasReviewMetadata, getBufferedCount, diff --git a/actions/setup/js/remove_labels.cjs b/actions/setup/js/remove_labels.cjs index 6dc7cc518ab..8870dc28dfd 100644 --- a/actions/setup/js/remove_labels.cjs +++ b/actions/setup/js/remove_labels.cjs @@ -29,7 +29,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Remove labels configuration: max=${maxCount}`); if (allowedLabels.length > 0) { diff --git a/actions/setup/js/reply_to_pr_review_comment.cjs b/actions/setup/js/reply_to_pr_review_comment.cjs index ef668e34f3c..1ace552d58f 100644 --- a/actions/setup/js/reply_to_pr_review_comment.cjs +++ b/actions/setup/js/reply_to_pr_review_comment.cjs @@ -34,7 +34,7 @@ async function main(config = {}) { const maxCount = config.max || 10; const replyTarget = config.target || "triggering"; const includeFooter = parseBoolTemplatable(config.footer, true); - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); const githubClient = await createAuthenticatedGitHubClient(config); diff --git a/actions/setup/js/resolve_pr_review_thread.cjs b/actions/setup/js/resolve_pr_review_thread.cjs index de2544d40f0..c8a58bba386 100644 --- a/actions/setup/js/resolve_pr_review_thread.cjs +++ b/actions/setup/js/resolve_pr_review_thread.cjs @@ -103,7 +103,7 @@ async function main(config = {}) { const triggeringPRNumber = getPRNumber(context.payload); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Resolve PR review thread configuration: max=${maxCount}, target=${resolveTarget}, triggeringPR=${triggeringPRNumber || "none"}`); core.info(`Default target repo: ${defaultTargetRepo}`); diff --git a/actions/setup/js/set_issue_type.cjs b/actions/setup/js/set_issue_type.cjs index db0ac3ee85b..eb90b1fb90c 100644 --- a/actions/setup/js/set_issue_type.cjs +++ b/actions/setup/js/set_issue_type.cjs @@ -99,7 +99,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Set issue type configuration: max=${maxCount}`); if (allowedTypes.length > 0) { diff --git a/actions/setup/js/submit_pr_review.cjs b/actions/setup/js/submit_pr_review.cjs index 70eaa1cca32..547e632b7f9 100644 --- a/actions/setup/js/submit_pr_review.cjs +++ b/actions/setup/js/submit_pr_review.cjs @@ -46,6 +46,11 @@ async function main(config = {}) { core.info(`Allowed repos: ${Array.from(allowedRepos).join(", ")}`); } + // Propagate per-handler staged flag to the shared PR review buffer + if (config.staged === true) { + buffer.setStaged(true); + } + let processedCount = 0; /** diff --git a/actions/setup/js/unassign_from_user.cjs b/actions/setup/js/unassign_from_user.cjs index 0e92fdf83dc..99b1c934068 100644 --- a/actions/setup/js/unassign_from_user.cjs +++ b/actions/setup/js/unassign_from_user.cjs @@ -31,7 +31,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; core.info(`Unassign from user configuration: max=${maxCount}`); if (allowedAssignees.length > 0) { diff --git a/actions/setup/js/update_handler_factory.cjs b/actions/setup/js/update_handler_factory.cjs index 70065137c54..8c05d03f13d 100644 --- a/actions/setup/js/update_handler_factory.cjs +++ b/actions/setup/js/update_handler_factory.cjs @@ -113,7 +113,7 @@ function createUpdateHandlerFactory(handlerConfig) { const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; // Build configuration log message const configParts = [`max=${maxCount}`, `target=${updateTarget}`]; diff --git a/actions/setup/js/update_project.cjs b/actions/setup/js/update_project.cjs index 34a467aaf53..a6a5aaf7016 100644 --- a/actions/setup/js/update_project.cjs +++ b/actions/setup/js/update_project.cjs @@ -1237,7 +1237,7 @@ async function main(config = {}, githubClient = null) { const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; if (configuredViews.length > 0) { core.info(`Found ${configuredViews.length} configured view(s) in frontmatter`); diff --git a/actions/setup/js/update_release.cjs b/actions/setup/js/update_release.cjs index c63ccc10aff..69cbe9c07fd 100644 --- a/actions/setup/js/update_release.cjs +++ b/actions/setup/js/update_release.cjs @@ -28,7 +28,7 @@ const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); */ async function main(config = {}) { // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; const includeFooter = parseBoolTemplatable(config.footer, true); const githubClient = await createAuthenticatedGitHubClient(config); diff --git a/pkg/workflow/compiler_safe_outputs_config.go b/pkg/workflow/compiler_safe_outputs_config.go index 0cd2db0da76..2d9edc6fed9 100644 --- a/pkg/workflow/compiler_safe_outputs_config.go +++ b/pkg/workflow/compiler_safe_outputs_config.go @@ -152,6 +152,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("close_older_key", c.CloseOlderKey). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "add_comment": func(cfg *SafeOutputsConfig) map[string]any { @@ -167,6 +168,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). + AddIfTrue("staged", c.Staged). Build() }, "create_discussion": func(cfg *SafeOutputsConfig) map[string]any { @@ -189,6 +191,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "close_issue": func(cfg *SafeOutputsConfig) map[string]any { @@ -204,6 +207,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("state_reason", c.StateReason). + AddIfTrue("staged", c.Staged). Build() }, "close_discussion": func(cfg *SafeOutputsConfig) map[string]any { @@ -218,6 +222,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("required_title_prefix", c.RequiredTitlePrefix). AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). + AddIfTrue("staged", c.Staged). Build() }, "add_labels": func(cfg *SafeOutputsConfig) map[string]any { @@ -233,6 +238,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() // If config is empty, it means add_labels was explicitly configured with no options // (null config), which means "allow any labels". Return non-nil empty map to @@ -256,6 +262,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "add_reviewer": func(cfg *SafeOutputsConfig) map[string]any { @@ -270,6 +277,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "assign_milestone": func(cfg *SafeOutputsConfig) map[string]any { @@ -284,6 +292,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "mark_pull_request_as_ready_for_review": func(cfg *SafeOutputsConfig) map[string]any { @@ -299,6 +308,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "create_code_scanning_alert": func(cfg *SafeOutputsConfig) map[string]any { @@ -312,6 +322,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "create_agent_session": func(cfg *SafeOutputsConfig) map[string]any { @@ -324,6 +335,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("base", c.Base). AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). + AddIfTrue("staged", c.Staged). Build() }, "update_issue": func(cfg *SafeOutputsConfig) map[string]any { @@ -349,6 +361,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). + AddIfTrue("staged", c.Staged). Build() }, "update_discussion": func(cfg *SafeOutputsConfig) map[string]any { @@ -375,6 +388,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). + AddIfTrue("staged", c.Staged). Build() }, "link_sub_issue": func(cfg *SafeOutputsConfig) map[string]any { @@ -391,6 +405,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "update_release": func(cfg *SafeOutputsConfig) map[string]any { @@ -402,6 +417,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddTemplatableInt("max", c.Max). AddIfNotEmpty("github-token", c.GitHubToken). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). + AddIfTrue("staged", c.Staged). Build() }, "create_pull_request_review_comment": func(cfg *SafeOutputsConfig) map[string]any { @@ -416,6 +432,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "submit_pull_request_review": func(cfg *SafeOutputsConfig) map[string]any { @@ -430,6 +447,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). AddStringPtr("footer", getEffectiveFooterString(c.Footer, cfg.Footer)). + AddIfTrue("staged", c.Staged). Build() }, "reply_to_pull_request_review_comment": func(cfg *SafeOutputsConfig) map[string]any { @@ -444,6 +462,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)). + AddIfTrue("staged", c.Staged). Build() }, "resolve_pull_request_review_thread": func(cfg *SafeOutputsConfig) map[string]any { @@ -457,6 +476,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "create_pull_request": func(cfg *SafeOutputsConfig) map[string]any { @@ -490,7 +510,8 @@ var handlerRegistry = map[string]handlerBuilder{ AddStringSlice("protected_path_prefixes", getProtectedPathPrefixes()). AddStringSlice("allowed_files", c.AllowedFiles). AddStringSlice("excluded_files", c.ExcludedFiles). - AddIfTrue("preserve_branch_name", c.PreserveBranchName) + AddIfTrue("preserve_branch_name", c.PreserveBranchName). + AddIfTrue("staged", c.Staged) return builder.Build() }, "push_to_pull_request_branch": func(cfg *SafeOutputsConfig) map[string]any { @@ -536,6 +557,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "close_pull_request": func(cfg *SafeOutputsConfig) map[string]any { @@ -565,6 +587,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "dispatch_workflow": func(cfg *SafeOutputsConfig) map[string]any { @@ -584,6 +607,7 @@ var handlerRegistry = map[string]handlerBuilder{ builder.AddIfNotEmpty("target-ref", c.TargetRef) builder.AddIfNotEmpty("github-token", c.GitHubToken) + builder.AddIfTrue("staged", c.Staged) return builder.Build() }, "call_workflow": func(cfg *SafeOutputsConfig) map[string]any { @@ -600,6 +624,7 @@ var handlerRegistry = map[string]handlerBuilder{ builder.AddDefault("workflow_files", c.WorkflowFiles) } + builder.AddIfTrue("staged", c.Staged) return builder.Build() }, "missing_tool": func(cfg *SafeOutputsConfig) map[string]any { @@ -610,6 +635,7 @@ var handlerRegistry = map[string]handlerBuilder{ return newHandlerConfigBuilder(). AddTemplatableInt("max", c.Max). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "missing_data": func(cfg *SafeOutputsConfig) map[string]any { @@ -620,6 +646,7 @@ var handlerRegistry = map[string]handlerBuilder{ return newHandlerConfigBuilder(). AddTemplatableInt("max", c.Max). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "noop": func(cfg *SafeOutputsConfig) map[string]any { @@ -630,6 +657,7 @@ var handlerRegistry = map[string]handlerBuilder{ return newHandlerConfigBuilder(). AddTemplatableInt("max", c.Max). AddStringPtr("report-as-issue", c.ReportAsIssue). + AddIfTrue("staged", c.Staged). Build() }, "assign_to_agent": func(cfg *SafeOutputsConfig) map[string]any { @@ -652,6 +680,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddStringSlice("allowed-pull-request-repos", c.AllowedPullRequestRepos). AddIfNotEmpty("base-branch", c.BaseBranch). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "upload_asset": func(cfg *SafeOutputsConfig) map[string]any { @@ -665,6 +694,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfPositive("max-size", c.MaxSizeKB). AddStringSlice("allowed-exts", c.AllowedExts). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "autofix_code_scanning_alert": func(cfg *SafeOutputsConfig) map[string]any { @@ -675,6 +705,7 @@ var handlerRegistry = map[string]handlerBuilder{ return newHandlerConfigBuilder(). AddTemplatableInt("max", c.Max). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, // Note: create_project, update_project and create_project_status_update are handled by the unified handler, @@ -695,6 +726,7 @@ var handlerRegistry = map[string]handlerBuilder{ if len(c.FieldDefinitions) > 0 { builder.AddDefault("field_definitions", c.FieldDefinitions) } + builder.AddIfTrue("staged", c.Staged) return builder.Build() }, "update_project": func(cfg *SafeOutputsConfig) map[string]any { @@ -714,6 +746,7 @@ var handlerRegistry = map[string]handlerBuilder{ if len(c.FieldDefinitions) > 0 { builder.AddDefault("field_definitions", c.FieldDefinitions) } + builder.AddIfTrue("staged", c.Staged) return builder.Build() }, "assign_to_user": func(cfg *SafeOutputsConfig) map[string]any { @@ -730,6 +763,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). AddTemplatableBool("unassign_first", c.UnassignFirst). + AddIfTrue("staged", c.Staged). Build() }, "unassign_from_user": func(cfg *SafeOutputsConfig) map[string]any { @@ -745,6 +779,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() }, "create_project_status_update": func(cfg *SafeOutputsConfig) map[string]any { @@ -756,6 +791,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddTemplatableInt("max", c.Max). AddIfNotEmpty("github-token", c.GitHubToken). AddIfNotEmpty("project", c.Project). + AddIfTrue("staged", c.Staged). Build() }, "set_issue_type": func(cfg *SafeOutputsConfig) map[string]any { @@ -770,6 +806,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddIfNotEmpty("target-repo", c.TargetRepoSlug). AddStringSlice("allowed_repos", c.AllowedRepos). AddIfNotEmpty("github-token", c.GitHubToken). + AddIfTrue("staged", c.Staged). Build() // If config is empty, it means set_issue_type was explicitly configured with no options // (null config), which means "allow any type". Return non-nil empty map to diff --git a/pkg/workflow/compiler_safe_outputs_config_test.go b/pkg/workflow/compiler_safe_outputs_config_test.go index 7177035273c..ed638ccfae3 100644 --- a/pkg/workflow/compiler_safe_outputs_config_test.go +++ b/pkg/workflow/compiler_safe_outputs_config_test.go @@ -1721,6 +1721,113 @@ func TestHandlerConfigStagedMode(t *testing.T) { }, handlerKey: "close_pull_request", }, + { + name: "create_issue staged", + safeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + }, + }, + handlerKey: "create_issue", + }, + { + name: "add_comment staged", + safeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + }, + }, + handlerKey: "add_comment", + }, + { + name: "create_pull_request staged", + safeOutputs: &SafeOutputsConfig{ + CreatePullRequests: &CreatePullRequestsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + }, + }, + handlerKey: "create_pull_request", + }, + { + name: "update_issue staged", + safeOutputs: &SafeOutputsConfig{ + UpdateIssues: &UpdateIssuesConfig{ + UpdateEntityConfig: UpdateEntityConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + }, + }, + }, + handlerKey: "update_issue", + }, + { + name: "update_pull_request staged", + safeOutputs: &SafeOutputsConfig{ + UpdatePullRequests: &UpdatePullRequestsConfig{ + UpdateEntityConfig: UpdateEntityConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + }, + }, + }, + handlerKey: "update_pull_request", + }, + { + name: "update_discussion staged", + safeOutputs: &SafeOutputsConfig{ + UpdateDiscussions: &UpdateDiscussionsConfig{ + UpdateEntityConfig: UpdateEntityConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + }, + }, + }, + handlerKey: "update_discussion", + }, + { + name: "add_labels staged", + safeOutputs: &SafeOutputsConfig{ + AddLabels: &AddLabelsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + }, + }, + handlerKey: "add_labels", + }, + { + name: "dispatch_workflow staged", + safeOutputs: &SafeOutputsConfig{ + DispatchWorkflow: &DispatchWorkflowConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + Workflows: []string{"my-workflow"}, + }, + }, + handlerKey: "dispatch_workflow", + }, + { + name: "call_workflow staged", + safeOutputs: &SafeOutputsConfig{ + CallWorkflow: &CallWorkflowConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Staged: true, + }, + Workflows: []string{"my-workflow"}, + }, + }, + handlerKey: "call_workflow", + }, } for _, tt := range tests { From 0b5569f32117a13d093229af2690b2dd38baf561 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:58:54 +0000 Subject: [PATCH 3/3] Extract isStagedMode helper for consistent staged mode detection across all handlers Add isStagedMode(config) to safe_output_helpers.cjs that checks both the global GH_AW_SAFE_OUTPUTS_STAGED env var and the per-handler config.staged flag. Update all handler .cjs files to use isStagedMode(config) instead of the inline expression, ensuring consistent staged mode wiring across all safe outputs. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/0e2805dc-8d49-4cb8-a325-25a29028a8a5 --- actions/setup/js/add_comment.cjs | 4 ++-- actions/setup/js/add_labels.cjs | 3 ++- actions/setup/js/add_reviewer.cjs | 3 ++- actions/setup/js/assign_milestone.cjs | 3 ++- actions/setup/js/assign_to_user.cjs | 4 ++-- actions/setup/js/autofix_code_scanning_alert.cjs | 3 ++- actions/setup/js/call_workflow.cjs | 3 ++- actions/setup/js/close_discussion.cjs | 3 ++- actions/setup/js/close_entity_helpers.cjs | 3 ++- actions/setup/js/close_issue.cjs | 3 ++- actions/setup/js/close_pull_request.cjs | 3 ++- actions/setup/js/create_code_scanning_alert.cjs | 3 ++- actions/setup/js/create_discussion.cjs | 3 ++- actions/setup/js/create_issue.cjs | 3 ++- actions/setup/js/create_project.cjs | 3 ++- actions/setup/js/create_project_status_update.cjs | 3 ++- actions/setup/js/create_pull_request.cjs | 3 ++- actions/setup/js/dispatch_workflow.cjs | 3 ++- actions/setup/js/hide_comment.cjs | 3 ++- actions/setup/js/link_sub_issue.cjs | 3 ++- .../js/mark_pull_request_as_ready_for_review.cjs | 3 ++- actions/setup/js/noop.cjs | 3 ++- actions/setup/js/pr_review_buffer.cjs | 3 ++- actions/setup/js/push_to_pull_request_branch.cjs | 3 ++- actions/setup/js/remove_labels.cjs | 3 ++- actions/setup/js/reply_to_pr_review_comment.cjs | 3 ++- actions/setup/js/resolve_pr_review_thread.cjs | 3 ++- actions/setup/js/safe_output_handler_manager.cjs | 4 ++-- actions/setup/js/safe_output_helpers.cjs | 13 +++++++++++++ actions/setup/js/set_issue_type.cjs | 3 ++- actions/setup/js/unassign_from_user.cjs | 4 ++-- actions/setup/js/update_handler_factory.cjs | 4 ++-- actions/setup/js/update_project.cjs | 3 ++- actions/setup/js/update_release.cjs | 3 ++- 34 files changed, 79 insertions(+), 38 deletions(-) diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index 04aea3d2413..73b8f358d13 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -12,7 +12,7 @@ const { replaceTemporaryIdReferences, loadTemporaryIdMapFromResolved, resolveRep const { getTrackerID } = require("./get_tracker_id.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); -const { resolveTarget } = require("./safe_output_helpers.cjs"); +const { resolveTarget, isStagedMode } = require("./safe_output_helpers.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { getMissingInfoSections } = require("./missing_messages_helper.cjs"); @@ -312,7 +312,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); // Check if append-only-comments is enabled in messages config const messagesConfig = getMessages(); diff --git a/actions/setup/js/add_labels.cjs b/actions/setup/js/add_labels.cjs index c0b555a1086..13d65a64233 100644 --- a/actions/setup/js/add_labels.cjs +++ b/actions/setup/js/add_labels.cjs @@ -13,6 +13,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { resolveRepoIssueTarget, loadTemporaryIdMapFromResolved } = require("./temporary_id.cjs"); const { MAX_LABELS } = require("./constants.cjs"); @@ -31,7 +32,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Add labels configuration: max=${maxCount}`); if (allowedLabels.length > 0) { diff --git a/actions/setup/js/add_reviewer.cjs b/actions/setup/js/add_reviewer.cjs index ff5c0e3b778..e26c8b6d68b 100644 --- a/actions/setup/js/add_reviewer.cjs +++ b/actions/setup/js/add_reviewer.cjs @@ -9,6 +9,7 @@ const { processItems } = require("./safe_output_processor.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { getPullRequestNumber } = require("./pr_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { COPILOT_REVIEWER_BOT } = require("./constants.cjs"); @@ -24,7 +25,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Add reviewer configuration: max=${maxCount}`); if (allowedReviewers.length > 0) { diff --git a/actions/setup/js/assign_milestone.cjs b/actions/setup/js/assign_milestone.cjs index fe44d1c5b27..db6ce579772 100644 --- a/actions/setup/js/assign_milestone.cjs +++ b/actions/setup/js/assign_milestone.cjs @@ -7,6 +7,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { loadTemporaryIdMapFromResolved, resolveRepoIssueTarget } = require("./temporary_id.cjs"); @@ -25,7 +26,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Assign milestone configuration: max=${maxCount}`); if (allowedMilestones.length > 0) { diff --git a/actions/setup/js/assign_to_user.cjs b/actions/setup/js/assign_to_user.cjs index 0252ba09fe6..9ad6c89e167 100644 --- a/actions/setup/js/assign_to_user.cjs +++ b/actions/setup/js/assign_to_user.cjs @@ -8,7 +8,7 @@ const { processItems } = require("./safe_output_processor.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); -const { resolveIssueNumber, extractAssignees } = require("./safe_output_helpers.cjs"); +const { resolveIssueNumber, extractAssignees, isStagedMode } = require("./safe_output_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); @@ -31,7 +31,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Assign to user configuration: max=${maxCount}, unassign_first=${unassignFirst}`); if (allowedAssignees.length > 0) { diff --git a/actions/setup/js/autofix_code_scanning_alert.cjs b/actions/setup/js/autofix_code_scanning_alert.cjs index 8bc2fba1d48..7dd5249c92a 100644 --- a/actions/setup/js/autofix_code_scanning_alert.cjs +++ b/actions/setup/js/autofix_code_scanning_alert.cjs @@ -3,6 +3,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction @@ -19,7 +20,7 @@ const HANDLER_TYPE = "autofix_code_scanning_alert"; async function main(config = {}) { // Extract configuration const maxCount = config.max || 10; - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Add code scanning autofix configuration: max=${maxCount}`); if (isStaged) logStagedPreviewInfo("no changes will be written"); diff --git a/actions/setup/js/call_workflow.cjs b/actions/setup/js/call_workflow.cjs index 5e809abdf7a..8d550dd6ce0 100644 --- a/actions/setup/js/call_workflow.cjs +++ b/actions/setup/js/call_workflow.cjs @@ -10,6 +10,7 @@ const HANDLER_TYPE = "call_workflow"; const { getErrorMessage } = require("./error_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); /** * Main handler factory for call_workflow. @@ -32,7 +33,7 @@ async function main(config = {}) { // Track how many items we've processed for max limit let processedCount = 0; - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); /** * Message handler function that processes a single call_workflow message. diff --git a/actions/setup/js/close_discussion.cjs b/actions/setup/js/close_discussion.cjs index c79632287be..6c4664d5a7f 100644 --- a/actions/setup/js/close_discussion.cjs +++ b/actions/setup/js/close_discussion.cjs @@ -8,6 +8,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { ERR_NOT_FOUND } = require("./error_codes.cjs"); @@ -163,7 +164,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Close discussion configuration: max=${maxCount}`); if (requiredLabels.length > 0) { diff --git a/actions/setup/js/close_entity_helpers.cjs b/actions/setup/js/close_entity_helpers.cjs index 5591e1675ec..5de4a8e9184 100644 --- a/actions/setup/js/close_entity_helpers.cjs +++ b/actions/setup/js/close_entity_helpers.cjs @@ -8,6 +8,7 @@ const { getRepositoryUrl } = require("./get_repository_url.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); /** * @typedef {'issue' | 'pull_request'} EntityType @@ -212,7 +213,7 @@ function escapeMarkdownTitle(title) { */ async function processCloseEntityItems(config, callbacks, handlerConfig = {}) { // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = isStagedMode(handlerConfig); const result = loadAgentOutput(); if (!result.success) { diff --git a/actions/setup/js/close_issue.cjs b/actions/setup/js/close_issue.cjs index 1b2604d98bb..4c765d057c2 100644 --- a/actions/setup/js/close_issue.cjs +++ b/actions/setup/js/close_issue.cjs @@ -9,6 +9,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { ERR_NOT_FOUND } = require("./error_codes.cjs"); @@ -91,7 +92,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Close issue configuration: max=${maxCount}, state_reason=${configStateReason}`); if (requiredLabels.length > 0) { diff --git a/actions/setup/js/close_pull_request.cjs b/actions/setup/js/close_pull_request.cjs index 6309fe4835c..2483993cfb6 100644 --- a/actions/setup/js/close_pull_request.cjs +++ b/actions/setup/js/close_pull_request.cjs @@ -6,6 +6,7 @@ const { getTrackerID } = require("./get_tracker_id.cjs"); const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { ERR_NOT_FOUND } = require("./error_codes.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); @@ -90,7 +91,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode (either globally or per-handler config) - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Close pull request configuration: max=${maxCount}`); if (requiredLabels.length > 0) { diff --git a/actions/setup/js/create_code_scanning_alert.cjs b/actions/setup/js/create_code_scanning_alert.cjs index 9285668c592..63b52ad6a0c 100644 --- a/actions/setup/js/create_code_scanning_alert.cjs +++ b/actions/setup/js/create_code_scanning_alert.cjs @@ -7,6 +7,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const fs = require("fs"); const path = require("path"); @@ -30,7 +31,7 @@ async function main(config = {}) { // Track how many items we've processed for max limit let processedCount = 0; - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); // Collect valid findings across all messages const validFindings = []; diff --git a/actions/setup/js/create_discussion.cjs b/actions/setup/js/create_discussion.cjs index f947ad8fc63..dad7439f262 100644 --- a/actions/setup/js/create_discussion.cjs +++ b/actions/setup/js/create_discussion.cjs @@ -20,6 +20,7 @@ const { generateWorkflowIdMarker, generateWorkflowCallIdMarker, generateCloseKey const { sanitizeLabelContent } = require("./sanitize_label_content.cjs"); const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { closeOlderDiscussions: closeOlderDiscussionsFunc } = require("./close_older_discussions.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); @@ -318,7 +319,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); // Parse labels from config const labelsConfig = config.labels || []; diff --git a/actions/setup/js/create_issue.cjs b/actions/setup/js/create_issue.cjs index 9299689f9e6..5aa8862b65a 100644 --- a/actions/setup/js/create_issue.cjs +++ b/actions/setup/js/create_issue.cjs @@ -43,6 +43,7 @@ const { closeOlderIssues } = require("./close_older_issues.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); const { MAX_LABELS, MAX_ASSIGNEES } = require("./constants.cjs"); @@ -218,7 +219,7 @@ async function main(config = {}) { const assignCopilot = process.env.GH_AW_ASSIGN_COPILOT === "true"; // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Default target repo: ${defaultTargetRepo}`); if (allowedRepos.size > 0) { diff --git a/actions/setup/js/create_project.cjs b/actions/setup/js/create_project.cjs index 2ea7cd1ca31..5b3803e05be 100644 --- a/actions/setup/js/create_project.cjs +++ b/actions/setup/js/create_project.cjs @@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { normalizeTemporaryId, isTemporaryId, generateTemporaryId, getOrGenerateTemporaryId } = require("./temporary_id.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { ERR_CONFIG, ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs"); /** @@ -298,7 +299,7 @@ async function main(config = {}, githubClient = null) { const configuredViews = Array.isArray(config.views) ? config.views : []; // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); // Use the provided github client, or fall back to the global github object // The global github object is available when running via github-script action diff --git a/actions/setup/js/create_project_status_update.cjs b/actions/setup/js/create_project_status_update.cjs index 1442dd3aa74..10305296bbc 100644 --- a/actions/setup/js/create_project_status_update.cjs +++ b/actions/setup/js/create_project_status_update.cjs @@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { isTemporaryId, normalizeTemporaryId } = require("./temporary_id.cjs"); const { ERR_CONFIG, ERR_NOT_FOUND, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs"); @@ -288,7 +289,7 @@ async function main(config = {}, githubClient = null) { const maxCount = config.max || 10; // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); // Use the provided github client, or fall back to the global github object // @ts-ignore - global.github is set by setupGlobals() from github-script context diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index 94445a2940e..981db2d394b 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -27,6 +27,7 @@ const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); const { checkFileProtection } = require("./manifest_file_helpers.cjs"); const { renderTemplateFromFile } = require("./messages_core.cjs"); const { COPILOT_REVIEWER_BOT, FAQ_CREATE_PR_PERMISSIONS_URL } = require("./constants.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction @@ -160,7 +161,7 @@ async function main(config = {}) { const triggeringIssueNumber = typeof context !== "undefined" && context.payload?.issue?.number && !context.payload?.issue?.pull_request ? context.payload.issue.number : undefined; // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Base branch: ${configBaseBranch || "(dynamic - resolved per target repo)"}`); core.info(`Default target repo: ${defaultTargetRepo}`); diff --git a/actions/setup/js/dispatch_workflow.cjs b/actions/setup/js/dispatch_workflow.cjs index 949d9f50b95..4e4137c4f6b 100644 --- a/actions/setup/js/dispatch_workflow.cjs +++ b/actions/setup/js/dispatch_workflow.cjs @@ -12,6 +12,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { resolveTargetRepoConfig, parseRepoSlug, validateTargetRepo } = require("./repo_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); /** * Main handler factory for dispatch_workflow @@ -74,7 +75,7 @@ async function main(config = {}) { // Track how many items we've processed for max limit let processedCount = 0; let lastDispatchTime = 0; - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); // Helper function to get the default branch of the dispatch target repository const getDefaultBranchRef = async () => { diff --git a/actions/setup/js/hide_comment.cjs b/actions/setup/js/hide_comment.cjs index 064b91b04c9..e93e280ee21 100644 --- a/actions/setup/js/hide_comment.cjs +++ b/actions/setup/js/hide_comment.cjs @@ -7,6 +7,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); /** @@ -52,7 +53,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Hide comment configuration: max=${maxCount}`); if (allowedReasons.length > 0) { diff --git a/actions/setup/js/link_sub_issue.cjs b/actions/setup/js/link_sub_issue.cjs index ecbc949cae6..439e8bf5b7a 100644 --- a/actions/setup/js/link_sub_issue.cjs +++ b/actions/setup/js/link_sub_issue.cjs @@ -4,6 +4,7 @@ const { loadTemporaryIdMapFromResolved, resolveRepoIssueTarget } = require("./temporary_id.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); /** @@ -22,7 +23,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); if (parentRequiredLabels.length > 0) { core.info(`Parent required labels: ${JSON.stringify(parentRequiredLabels)}`); diff --git a/actions/setup/js/mark_pull_request_as_ready_for_review.cjs b/actions/setup/js/mark_pull_request_as_ready_for_review.cjs index a848eb5b8ef..74f5c3aff91 100644 --- a/actions/setup/js/mark_pull_request_as_ready_for_review.cjs +++ b/actions/setup/js/mark_pull_request_as_ready_for_review.cjs @@ -9,6 +9,7 @@ const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { ERR_NOT_FOUND } = require("./error_codes.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); @@ -88,7 +89,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Mark pull request as ready for review configuration: max=${maxCount}`); diff --git a/actions/setup/js/noop.cjs b/actions/setup/js/noop.cjs index 6bcba542d2f..25918613bc8 100644 --- a/actions/setup/js/noop.cjs +++ b/actions/setup/js/noop.cjs @@ -2,6 +2,7 @@ /// const { loadAgentOutput } = require("./load_agent_output.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); /** * Main function to handle noop safe output @@ -10,7 +11,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); */ async function main() { // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = isStagedMode(); const result = loadAgentOutput(); if (!result.success) { diff --git a/actions/setup/js/pr_review_buffer.cjs b/actions/setup/js/pr_review_buffer.cjs index bb5111cebea..a848407fc2d 100644 --- a/actions/setup/js/pr_review_buffer.cjs +++ b/actions/setup/js/pr_review_buffer.cjs @@ -21,6 +21,7 @@ const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); /** * @typedef {Object} BufferedComment @@ -275,7 +276,7 @@ function createReviewBuffer() { core.info(`Submitting PR review on ${repo}#${pullRequestNumber}: event=${event}, comments=${comments.length}, bodyLength=${body.length}`); // If in staged mode, preview the review without submitting - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || stagedMode; + const isStaged = isStagedMode({ staged: stagedMode }); if (isStaged) { let summaryContent = "## 🎭 Staged Mode: PR Review Preview\n\n"; summaryContent += "The following PR review would be submitted if staged mode was disabled:\n\n"; diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 292cea76b76..3d0a4db8932 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -4,6 +4,7 @@ /** @type {typeof import("fs")} */ const fs = require("fs"); const { generateStagedPreview } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { pushSignedCommits } = require("./push_signed_commits.cjs"); const { updateActivationCommentWithCommit, updateActivationComment } = require("./update_activation_comment.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -56,7 +57,7 @@ async function main(config = {}) { const configBaseBranch = config.base_branch || null; // Check if we're in staged mode (either globally or per-handler config) - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Target: ${target}`); if (configBaseBranch) { diff --git a/actions/setup/js/remove_labels.cjs b/actions/setup/js/remove_labels.cjs index 8870dc28dfd..e6a9a4f1259 100644 --- a/actions/setup/js/remove_labels.cjs +++ b/actions/setup/js/remove_labels.cjs @@ -12,6 +12,7 @@ const { validateLabels } = require("./safe_output_validator.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { resolveRepoIssueTarget, loadTemporaryIdMapFromResolved } = require("./temporary_id.cjs"); @@ -29,7 +30,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Remove labels configuration: max=${maxCount}`); if (allowedLabels.length > 0) { diff --git a/actions/setup/js/reply_to_pr_review_comment.cjs b/actions/setup/js/reply_to_pr_review_comment.cjs index 1ace552d58f..25daf5768fc 100644 --- a/actions/setup/js/reply_to_pr_review_comment.cjs +++ b/actions/setup/js/reply_to_pr_review_comment.cjs @@ -11,6 +11,7 @@ const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { getPRNumber } = require("./update_context_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); @@ -34,7 +35,7 @@ async function main(config = {}) { const maxCount = config.max || 10; const replyTarget = config.target || "triggering"; const includeFooter = parseBoolTemplatable(config.footer, true); - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); const githubClient = await createAuthenticatedGitHubClient(config); diff --git a/actions/setup/js/resolve_pr_review_thread.cjs b/actions/setup/js/resolve_pr_review_thread.cjs index c8a58bba386..0f388a3b2df 100644 --- a/actions/setup/js/resolve_pr_review_thread.cjs +++ b/actions/setup/js/resolve_pr_review_thread.cjs @@ -8,6 +8,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { getPRNumber } = require("./update_context_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { resolveTargetRepoConfig, validateTargetRepo } = require("./repo_helpers.cjs"); @@ -103,7 +104,7 @@ async function main(config = {}) { const triggeringPRNumber = getPRNumber(context.payload); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Resolve PR review thread configuration: max=${maxCount}, target=${resolveTarget}, triggeringPR=${triggeringPRNumber || "none"}`); core.info(`Default target repo: ${defaultTargetRepo}`); diff --git a/actions/setup/js/safe_output_handler_manager.cjs b/actions/setup/js/safe_output_handler_manager.cjs index 41e9d48a656..16684f2b42e 100644 --- a/actions/setup/js/safe_output_handler_manager.cjs +++ b/actions/setup/js/safe_output_handler_manager.cjs @@ -20,7 +20,7 @@ const { getIssuesToAssignCopilot } = require("./create_issue.cjs"); const { createReviewBuffer } = require("./pr_review_buffer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { createManifestLogger, ensureManifestExists, extractCreatedItemFromResult } = require("./safe_output_manifest.cjs"); -const { loadCustomSafeOutputJobTypes, loadCustomSafeOutputScriptHandlers, loadCustomSafeOutputActionHandlers } = require("./safe_output_helpers.cjs"); +const { loadCustomSafeOutputJobTypes, loadCustomSafeOutputScriptHandlers, loadCustomSafeOutputActionHandlers, isStagedMode } = require("./safe_output_helpers.cjs"); const { emitSafeOutputActionOutputs } = require("./safe_outputs_action_outputs.cjs"); const nodePath = require("path"); @@ -960,7 +960,7 @@ async function processSyntheticUpdates(github, context, trackedOutputs, temporar async function main() { // Detect staged mode before try/finally so it's accessible in the finally block. // In staged mode (🎭 Staged Mode Preview) no real items are created in GitHub so no manifest should be emitted. - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const isStaged = isStagedMode(); try { core.info("Safe Output Handler Manager starting..."); diff --git a/actions/setup/js/safe_output_helpers.cjs b/actions/setup/js/safe_output_helpers.cjs index 47967926ee8..42dbbb1fb94 100644 --- a/actions/setup/js/safe_output_helpers.cjs +++ b/actions/setup/js/safe_output_helpers.cjs @@ -393,6 +393,18 @@ function loadCustomSafeOutputActionHandlers() { } } +/** + * Returns true when the current execution is in staged mode. + * Staged mode is active when either the global GH_AW_SAFE_OUTPUTS_STAGED + * environment variable is "true" or when the per-handler config has staged: true. + * Use this helper in all handlers to ensure consistent staged mode detection. + * @param {Object} [config] - Handler configuration object (may have staged: true) + * @returns {boolean} + */ +function isStagedMode(config) { + return process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || (config != null && config.staged === true); +} + module.exports = { parseAllowedItems, parseMaxCount, @@ -404,4 +416,5 @@ module.exports = { extractAssignees, matchesBlockedPattern, isUsernameBlocked, + isStagedMode, }; diff --git a/actions/setup/js/set_issue_type.cjs b/actions/setup/js/set_issue_type.cjs index eb90b1fb90c..3e802f1059e 100644 --- a/actions/setup/js/set_issue_type.cjs +++ b/actions/setup/js/set_issue_type.cjs @@ -8,6 +8,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { loadTemporaryIdMapFromResolved, resolveRepoIssueTarget } = require("./temporary_id.cjs"); @@ -99,7 +100,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Set issue type configuration: max=${maxCount}`); if (allowedTypes.length > 0) { diff --git a/actions/setup/js/unassign_from_user.cjs b/actions/setup/js/unassign_from_user.cjs index 99b1c934068..de4a6c9fb2c 100644 --- a/actions/setup/js/unassign_from_user.cjs +++ b/actions/setup/js/unassign_from_user.cjs @@ -8,7 +8,7 @@ const { processItems } = require("./safe_output_processor.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); -const { resolveIssueNumber, extractAssignees } = require("./safe_output_helpers.cjs"); +const { resolveIssueNumber, extractAssignees, isStagedMode } = require("./safe_output_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); @@ -31,7 +31,7 @@ async function main(config = {}) { const githubClient = await createAuthenticatedGitHubClient(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); core.info(`Unassign from user configuration: max=${maxCount}`); if (allowedAssignees.length > 0) { diff --git a/actions/setup/js/update_handler_factory.cjs b/actions/setup/js/update_handler_factory.cjs index 8c05d03f13d..25cc21f54f7 100644 --- a/actions/setup/js/update_handler_factory.cjs +++ b/actions/setup/js/update_handler_factory.cjs @@ -6,7 +6,7 @@ */ const { getErrorMessage } = require("./error_helpers.cjs"); -const { resolveTarget } = require("./safe_output_helpers.cjs"); +const { resolveTarget, isStagedMode } = require("./safe_output_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); @@ -113,7 +113,7 @@ function createUpdateHandlerFactory(handlerConfig) { const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); // Build configuration log message const configParts = [`max=${maxCount}`, `target=${updateTarget}`]; diff --git a/actions/setup/js/update_project.cjs b/actions/setup/js/update_project.cjs index a6a5aaf7016..1bd8090e35e 100644 --- a/actions/setup/js/update_project.cjs +++ b/actions/setup/js/update_project.cjs @@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { loadTemporaryIdMapFromResolved, resolveIssueNumber, isTemporaryId, normalizeTemporaryId } = require("./temporary_id.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { ERR_API, ERR_CONFIG, ERR_NOT_FOUND, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs"); const { parseRepoSlug, resolveTargetRepoConfig, isRepoAllowed } = require("./repo_helpers.cjs"); @@ -1237,7 +1238,7 @@ async function main(config = {}, githubClient = null) { const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); if (configuredViews.length > 0) { core.info(`Found ${configuredViews.length} configured view(s) in frontmatter`); diff --git a/actions/setup/js/update_release.cjs b/actions/setup/js/update_release.cjs index 69cbe9c07fd..3e32193ff90 100644 --- a/actions/setup/js/update_release.cjs +++ b/actions/setup/js/update_release.cjs @@ -11,6 +11,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { updateBody } = require("./update_pr_description_helpers.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isStagedMode } = require("./safe_output_helpers.cjs"); const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs"); const { parseBoolTemplatable } = require("./templatable.cjs"); const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs"); @@ -28,7 +29,7 @@ const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); */ async function main(config = {}) { // Check if we're in staged mode - const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true" || config.staged === true; + const isStaged = isStagedMode(config); const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; const includeFooter = parseBoolTemplatable(config.footer, true); const githubClient = await createAuthenticatedGitHubClient(config);