diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index a5d6cfef068..4db31963085 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { getRepositoryUrl } = require("./get_repository_url.cjs"); @@ -355,7 +356,7 @@ async function main(config = {}) { itemNumber = typeof item.item_number === "number" ? item.item_number : parseInt(String(item.item_number), 10); if (isNaN(itemNumber) || itemNumber <= 0) { - core.warning(`Invalid item_number specified: ${item.item_number}`); + safeWarning(`Invalid item_number specified: ${item.item_number}`); return { success: false, error: `Invalid item_number specified: ${item.item_number}`, @@ -401,7 +402,7 @@ async function main(config = {}) { } itemNumber = targetResult.number; - core.info(`Resolved target ${targetResult.contextType} #${itemNumber} (target config: ${commentTarget})`); + safeInfo(`Resolved target ${targetResult.contextType} #${itemNumber} (target config: ${commentTarget})`); } } @@ -564,7 +565,7 @@ async function main(config = {}) { if (isDiscussion404) { // Neither issue/PR nor discussion found - truly doesn't exist - core.warning(`Target #${itemNumber} was not found as issue, PR, or discussion (may have been deleted): ${discussionErrorMessage}`); + safeWarning(`Target #${itemNumber} was not found as issue, PR, or discussion (may have been deleted): ${discussionErrorMessage}`); return { success: true, warning: `Target not found: ${discussionErrorMessage}`, @@ -573,7 +574,7 @@ async function main(config = {}) { } // Other error when trying as discussion - core.error(`Failed to add comment to discussion: ${discussionErrorMessage}`); + safeError(`Failed to add comment to discussion: ${discussionErrorMessage}`); return { success: false, error: discussionErrorMessage, @@ -583,7 +584,7 @@ async function main(config = {}) { if (is404) { // Treat 404s as warnings - the target was deleted between execution and safe output processing - core.warning(`Target was not found (may have been deleted): ${errorMessage}`); + safeWarning(`Target was not found (may have been deleted): ${errorMessage}`); return { success: true, warning: `Target not found: ${errorMessage}`, @@ -592,7 +593,7 @@ async function main(config = {}) { } // For non-404 errors, fail as before - core.error(`Failed to add comment: ${errorMessage}`); + safeError(`Failed to add comment: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/add_copilot_reviewer.cjs b/actions/setup/js/add_copilot_reviewer.cjs index 66362144543..4bfdb1077e2 100644 --- a/actions/setup/js/add_copilot_reviewer.cjs +++ b/actions/setup/js/add_copilot_reviewer.cjs @@ -13,6 +13,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * Environment variables: * - PR_NUMBER: The pull request number to add the reviewer to */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); // GitHub Copilot reviewer bot username const COPILOT_REVIEWER_BOT = "copilot-pull-request-reviewer[bot]"; @@ -54,7 +55,7 @@ Successfully added Copilot as a reviewer to PR #${prNumber}.` .write(); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to add Copilot as reviewer: ${errorMessage}`); + safeError(`Failed to add Copilot as reviewer: ${errorMessage}`); core.setFailed(`Failed to add Copilot as reviewer to PR #${prNumber}: ${errorMessage}`); } } diff --git a/actions/setup/js/add_labels.cjs b/actions/setup/js/add_labels.cjs index 6056341212b..1e1b369c989 100644 --- a/actions/setup/js/add_labels.cjs +++ b/actions/setup/js/add_labels.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "add_labels"; @@ -25,7 +26,7 @@ async function main(config = {}) { core.info(`Add labels configuration: max=${maxCount}`); if (allowedLabels.length > 0) { - core.info(`Allowed labels: ${allowedLabels.join(", ")}`); + safeInfo(`Allowed labels: ${allowedLabels.join(", ")}`); } core.info(`Default target repo: ${defaultTargetRepo}`); if (allowedRepos.size > 0) { @@ -76,7 +77,7 @@ async function main(config = {}) { const contextType = context.payload?.pull_request ? "pull request" : "issue"; const requestedLabels = message.labels ?? []; - core.info(`Requested labels: ${JSON.stringify(requestedLabels)}`); + safeInfo(`Requested labels: ${JSON.stringify(requestedLabels)}`); // If no labels provided, return a helpful message with allowed labels if configured if (requestedLabels.length === 0) { @@ -100,7 +101,7 @@ async function main(config = {}) { }; } // For other validation errors, return error - core.warning(`Label validation failed: ${labelsResult.error}`); + safeWarning(`Label validation failed: ${labelsResult.error}`); return { success: false, error: labelsResult.error ?? "Invalid labels", @@ -120,7 +121,7 @@ async function main(config = {}) { }; } - core.info(`Adding ${uniqueLabels.length} labels to ${contextType} #${itemNumber} in ${itemRepo}: ${JSON.stringify(uniqueLabels)}`); + safeInfo(`Adding ${uniqueLabels.length} labels to ${contextType} #${itemNumber} in ${itemRepo}: ${JSON.stringify(uniqueLabels)}`); try { await github.rest.issues.addLabels({ @@ -130,7 +131,7 @@ async function main(config = {}) { labels: uniqueLabels, }); - core.info(`Successfully added ${uniqueLabels.length} labels to ${contextType} #${itemNumber} in ${itemRepo}`); + safeInfo(`Successfully added ${uniqueLabels.length} labels to ${contextType} #${itemNumber} in ${itemRepo}`); return { success: true, number: itemNumber, @@ -139,7 +140,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to add labels: ${errorMessage}`); + safeError(`Failed to add labels: ${errorMessage}`); return { success: false, error: errorMessage }; } }; diff --git a/actions/setup/js/add_reaction.cjs b/actions/setup/js/add_reaction.cjs index ae34085b1c6..89f35102815 100644 --- a/actions/setup/js/add_reaction.cjs +++ b/actions/setup/js/add_reaction.cjs @@ -9,6 +9,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * This script only adds reactions - it does NOT create comments. * Use add_reaction_and_edit_comment.cjs in the activation job to create the comment with workflow link. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { // Read inputs from environment variables const reaction = process.env.GH_AW_REACTION || "eyes"; @@ -97,7 +98,7 @@ async function main() { await addReaction(reactionEndpoint, reaction); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to add reaction: ${errorMessage}`); + safeError(`Failed to add reaction: ${errorMessage}`); core.setFailed(`Failed to add reaction: ${errorMessage}`); } } diff --git a/actions/setup/js/add_reaction_and_edit_comment.cjs b/actions/setup/js/add_reaction_and_edit_comment.cjs index 830daaa5a71..143d67defcd 100644 --- a/actions/setup/js/add_reaction_and_edit_comment.cjs +++ b/actions/setup/js/add_reaction_and_edit_comment.cjs @@ -5,6 +5,7 @@ const { getRunStartedMessage } = require("./messages_run_status.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { // Read inputs from environment variables const reaction = process.env.GH_AW_REACTION || "eyes"; @@ -14,7 +15,7 @@ async function main() { const runUrl = context.payload.repository ? `${context.payload.repository.html_url}/actions/runs/${runId}` : `${githubServer}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; core.info(`Reaction type: ${reaction}`); - core.info(`Command name: ${command || "none"}`); + safeInfo(`Command name: ${command || "none"}`); core.info(`Run ID: ${runId}`); core.info(`Run URL: ${runUrl}`); @@ -150,11 +151,11 @@ async function main() { core.info(`Comment endpoint: ${commentUpdateEndpoint}`); await addCommentWithWorkflowLink(commentUpdateEndpoint, runUrl, eventName); } else { - core.info(`Skipping comment for event type: ${eventName}`); + safeInfo(`Skipping comment for event type: ${eventName}`); } } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to process reaction and comment creation: ${errorMessage}`); + safeError(`Failed to process reaction and comment creation: ${errorMessage}`); core.setFailed(`Failed to process reaction and comment creation: ${errorMessage}`); } } @@ -391,7 +392,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName) { core.info(`Successfully created discussion comment with workflow link`); core.info(`Comment ID: ${comment.id}`); core.info(`Comment URL: ${comment.url}`); - core.info(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); + safeInfo(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); core.setOutput("comment-id", comment.id); core.setOutput("comment-url", comment.url); core.setOutput("comment-repo", `${context.repo.owner}/${context.repo.repo}`); @@ -435,7 +436,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName) { core.info(`Successfully created discussion comment with workflow link`); core.info(`Comment ID: ${comment.id}`); core.info(`Comment URL: ${comment.url}`); - core.info(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); + safeInfo(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); core.setOutput("comment-id", comment.id); core.setOutput("comment-url", comment.url); core.setOutput("comment-repo", `${context.repo.owner}/${context.repo.repo}`); @@ -453,7 +454,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName) { core.info(`Successfully created comment with workflow link`); core.info(`Comment ID: ${createResponse.data.id}`); core.info(`Comment URL: ${createResponse.data.html_url}`); - core.info(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); + safeInfo(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); core.setOutput("comment-id", createResponse.data.id.toString()); core.setOutput("comment-url", createResponse.data.html_url); core.setOutput("comment-repo", `${context.repo.owner}/${context.repo.repo}`); diff --git a/actions/setup/js/add_reviewer.cjs b/actions/setup/js/add_reviewer.cjs index 2c46a82b01f..6b99a764483 100644 --- a/actions/setup/js/add_reviewer.cjs +++ b/actions/setup/js/add_reviewer.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { processItems } = require("./safe_output_processor.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -134,7 +135,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to add reviewers: ${errorMessage}`); + safeError(`Failed to add reviewers: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/add_workflow_run_comment.cjs b/actions/setup/js/add_workflow_run_comment.cjs index 21d8f16b089..28d2b741420 100644 --- a/actions/setup/js/add_workflow_run_comment.cjs +++ b/actions/setup/js/add_workflow_run_comment.cjs @@ -10,6 +10,7 @@ const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); * This script ONLY creates comments - it does NOT add reactions. * Use add_reaction.cjs in the pre-activation job to add reactions first for immediate feedback. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const runId = context.runId; const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; @@ -92,9 +93,9 @@ async function main() { await addCommentWithWorkflowLink(commentEndpoint, runUrl, eventName); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to create comment: ${errorMessage}`); + safeError(`Failed to create comment: ${errorMessage}`); // Don't fail the job - just warn since this is not critical - core.warning(`Failed to create comment with workflow link: ${errorMessage}`); + safeWarning(`Failed to create comment with workflow link: ${errorMessage}`); } } @@ -203,7 +204,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName) { core.info(`Successfully created discussion comment with workflow link`); core.info(`Comment ID: ${comment.id}`); core.info(`Comment URL: ${comment.url}`); - core.info(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); + safeInfo(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); core.setOutput("comment-id", comment.id); core.setOutput("comment-url", comment.url); core.setOutput("comment-repo", `${context.repo.owner}/${context.repo.repo}`); @@ -247,7 +248,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName) { core.info(`Successfully created discussion comment with workflow link`); core.info(`Comment ID: ${comment.id}`); core.info(`Comment URL: ${comment.url}`); - core.info(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); + safeInfo(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); core.setOutput("comment-id", comment.id); core.setOutput("comment-url", comment.url); core.setOutput("comment-repo", `${context.repo.owner}/${context.repo.repo}`); @@ -265,7 +266,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName) { core.info(`Successfully created comment with workflow link`); core.info(`Comment ID: ${createResponse.data.id}`); core.info(`Comment URL: ${createResponse.data.html_url}`); - core.info(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); + safeInfo(`Comment Repo: ${context.repo.owner}/${context.repo.repo}`); core.setOutput("comment-id", createResponse.data.id.toString()); core.setOutput("comment-url", createResponse.data.html_url); core.setOutput("comment-repo", `${context.repo.owner}/${context.repo.repo}`); diff --git a/actions/setup/js/assign_agent_helpers.cjs b/actions/setup/js/assign_agent_helpers.cjs index d5bb1fa47ef..831d6e5822e 100644 --- a/actions/setup/js/assign_agent_helpers.cjs +++ b/actions/setup/js/assign_agent_helpers.cjs @@ -11,6 +11,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * The token must be set at the step level via the `github-token` parameter in GitHub Actions. * This approach is required for compatibility with actions/github-script@v8. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Map agent names to their GitHub bot login names @@ -62,7 +63,7 @@ async function getAvailableAgentLogins(owner, repo) { return available.sort(); } catch (e) { const errorMessage = e instanceof Error ? e.message : String(e); - core.debug(`Failed to list available agent logins: ${errorMessage}`); + safeDebug(`Failed to list available agent logins: ${errorMessage}`); return []; } } @@ -97,7 +98,7 @@ async function findAgent(owner, repo, agentName) { const loginName = AGENT_LOGIN_NAMES[agentName]; if (!loginName) { - core.error(`Unknown agent: ${agentName}. Supported agents: ${Object.keys(AGENT_LOGIN_NAMES).join(", ")}`); + safeError(`Unknown agent: ${agentName}. Supported agents: ${Object.keys(AGENT_LOGIN_NAMES).join(", ")}`); return null; } @@ -109,7 +110,7 @@ async function findAgent(owner, repo, agentName) { const knownValues = Object.values(AGENT_LOGIN_NAMES); const available = actors.filter(a => a?.login && knownValues.includes(a.login)).map(a => a.login); - core.warning(`${agentName} coding agent (${loginName}) is not available as an assignee for this repository`); + safeWarning(`${agentName} coding agent (${loginName}) is not available as an assignee for this repository`); if (available.length > 0) { core.info(`Available assignable coding agents: ${available.join(", ")}`); } else { @@ -121,7 +122,7 @@ async function findAgent(owner, repo, agentName) { return null; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to find ${agentName} agent: ${errorMessage}`); + safeError(`Failed to find ${agentName} agent: ${errorMessage}`); // Re-throw authentication/permission errors so they can be handled by the caller // This allows ignore-if-missing logic to work properly @@ -183,7 +184,7 @@ async function getIssueDetails(owner, repo, issueNumber) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to get issue details: ${errorMessage}`); + safeError(`Failed to get issue details: ${errorMessage}`); // Re-throw the error to preserve the original error message for permission error detection throw error; } @@ -233,7 +234,7 @@ async function getPullRequestDetails(owner, repo, pullNumber) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to get pull request details: ${errorMessage}`); + safeError(`Failed to get pull request details: ${errorMessage}`); // Re-throw the error to preserve the original error message for permission error detection throw error; } @@ -340,7 +341,7 @@ async function assignAgentToIssue(assignableId, agentId, currentAssignees, agent // Debug: surface the raw GraphQL error structure for troubleshooting fine-grained permission issues try { - core.debug(`Raw GraphQL error message: ${errorMessage}`); + safeDebug(`Raw GraphQL error message: ${errorMessage}`); if (error && typeof error === "object") { // Common GraphQL error shapes: error.errors (array), error.data, error.response const details = { @@ -394,7 +395,7 @@ async function assignAgentToIssue(assignableId, agentId, currentAssignees, agent }, }); if (fallbackResp?.addAssigneesToAssignable) { - core.info(`Fallback succeeded: agent '${agentName}' added via addAssigneesToAssignable.`); + safeInfo(`Fallback succeeded: agent '${agentName}' added via addAssigneesToAssignable.`); return true; } core.warning("Fallback mutation returned unexpected response; proceeding with permission guidance."); @@ -404,7 +405,7 @@ async function assignAgentToIssue(assignableId, agentId, currentAssignees, agent } logPermissionError(agentName); } else { - core.error(`Failed to assign ${agentName}: ${errorMessage}`); + safeError(`Failed to assign ${agentName}: ${errorMessage}`); } return false; } @@ -415,7 +416,7 @@ async function assignAgentToIssue(assignableId, agentId, currentAssignees, agent * @param {string} agentName - Agent name for error messages */ function logPermissionError(agentName) { - core.error(`Failed to assign ${agentName}: Insufficient permissions`); + safeError(`Failed to assign ${agentName}: Insufficient permissions`); core.error(""); core.error("Assigning Copilot agents requires:"); core.error(" 1. All four workflow permissions:"); @@ -492,7 +493,7 @@ async function assignAgentToIssueByName(owner, repo, issueNumber, agentName) { try { // Find agent using the github object authenticated via step-level github-token - core.info(`Looking for ${agentName} coding agent...`); + safeInfo(`Looking for ${agentName} coding agent...`); const agentId = await findAgent(owner, repo, agentName); if (!agentId) { const error = `${agentName} coding agent is not available for this repository`; @@ -501,7 +502,7 @@ async function assignAgentToIssueByName(owner, repo, issueNumber, agentName) { const enrichedError = available.length > 0 ? `${error} (available agents: ${available.join(", ")})` : error; return { success: false, error: enrichedError }; } - core.info(`Found ${agentName} coding agent (ID: ${agentId})`); + safeInfo(`Found ${agentName} coding agent (ID: ${agentId})`); // Get issue details (ID and current assignees) via GraphQL core.info("Getting issue details..."); @@ -514,19 +515,19 @@ async function assignAgentToIssueByName(owner, repo, issueNumber, agentName) { // Check if agent is already assigned if (issueDetails.currentAssignees.some(a => a.id === agentId)) { - core.info(`${agentName} is already assigned to issue #${issueNumber}`); + safeInfo(`${agentName} is already assigned to issue #${issueNumber}`); return { success: true }; } // Assign agent using GraphQL mutation (no allowed list filtering in this helper) - core.info(`Assigning ${agentName} coding agent to issue #${issueNumber}...`); + safeInfo(`Assigning ${agentName} coding agent to issue #${issueNumber}...`); const success = await assignAgentToIssue(issueDetails.issueId, agentId, issueDetails.currentAssignees, agentName, null); if (!success) { return { success: false, error: `Failed to assign ${agentName} via GraphQL` }; } - core.info(`Successfully assigned ${agentName} coding agent to issue #${issueNumber}`); + safeInfo(`Successfully assigned ${agentName} coding agent to issue #${issueNumber}`); return { success: true }; } catch (error) { const errorMessage = getErrorMessage(error); diff --git a/actions/setup/js/assign_copilot_to_created_issues.cjs b/actions/setup/js/assign_copilot_to_created_issues.cjs index f52c38a29b9..ef636e534dd 100644 --- a/actions/setup/js/assign_copilot_to_created_issues.cjs +++ b/actions/setup/js/assign_copilot_to_created_issues.cjs @@ -10,6 +10,7 @@ const { sleep } = require("./error_recovery.cjs"); * This script reads the issues_to_assign_copilot output and assigns copilot to each issue. * It uses the agent token (GH_AW_AGENT_TOKEN) for the GraphQL mutation. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { // Prefer explicit env var (works in consolidated safe_outputs mode where the @@ -71,12 +72,12 @@ async function main() { try { // Find agent (reuse cached ID for same repo) if (!agentId) { - core.info(`Looking for ${agentName} coding agent...`); + safeInfo(`Looking for ${agentName} coding agent...`); agentId = await findAgent(owner, repo, agentName); if (!agentId) { throw new Error(`${agentName} coding agent is not available for this repository`); } - core.info(`Found ${agentName} coding agent (ID: ${agentId})`); + safeInfo(`Found ${agentName} coding agent (ID: ${agentId})`); } // Get issue details @@ -90,7 +91,7 @@ async function main() { // Check if agent is already assigned if (issueDetails.currentAssignees.some(a => a.id === agentId)) { - core.info(`${agentName} is already assigned to issue #${issueNumber}`); + safeInfo(`${agentName} is already assigned to issue #${issueNumber}`); results.push({ repo: repoSlug, issue_number: issueNumber, @@ -101,14 +102,14 @@ async function main() { } // Assign agent using GraphQL mutation (no allowed list filtering) - core.info(`Assigning ${agentName} coding agent to issue #${issueNumber}...`); + safeInfo(`Assigning ${agentName} coding agent to issue #${issueNumber}...`); const success = await assignAgentToIssue(issueDetails.issueId, agentId, issueDetails.currentAssignees, agentName, null); if (!success) { throw new Error(`Failed to assign ${agentName} via GraphQL`); } - core.info(`Successfully assigned ${agentName} coding agent to issue #${issueNumber}`); + safeInfo(`Successfully assigned ${agentName} coding agent to issue #${issueNumber}`); results.push({ repo: repoSlug, issue_number: issueNumber, @@ -116,7 +117,7 @@ async function main() { }); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to assign ${agentName} to issue #${issueNumber} in ${repoSlug}: ${errorMessage}`); + safeError(`Failed to assign ${agentName} to issue #${issueNumber} in ${repoSlug}: ${errorMessage}`); results.push({ repo: repoSlug, issue_number: issueNumber, diff --git a/actions/setup/js/assign_issue.cjs b/actions/setup/js/assign_issue.cjs index 412ab586f6c..2580f53c0fc 100644 --- a/actions/setup/js/assign_issue.cjs +++ b/actions/setup/js/assign_issue.cjs @@ -8,6 +8,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * Assign an issue to a user or bot (including copilot) * This script handles assigning issues after they are created */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { // Validate required environment variables @@ -46,7 +47,7 @@ async function main() { if (agentName) { // Use GraphQL API for agent assignment // The token is set at the step level via github-token parameter - core.info(`Detected coding agent: ${agentName}. Using GraphQL API for assignment.`); + safeInfo(`Detected coding agent: ${agentName}. Using GraphQL API for assignment.`); // Get repository owner and repo from context const { owner, repo } = context.repo; @@ -56,7 +57,7 @@ async function main() { if (!agentId) { throw new Error(`${agentName} coding agent is not available for this repository`); } - core.info(`Found ${agentName} coding agent (ID: ${agentId})`); + safeInfo(`Found ${agentName} coding agent (ID: ${agentId})`); // Get issue details const issueDetails = await getIssueDetails(owner, repo, issueNum); @@ -66,7 +67,7 @@ async function main() { // Check if agent is already assigned if (issueDetails.currentAssignees.some(a => a.id === agentId)) { - core.info(`${agentName} is already assigned to issue #${issueNum}`); + safeInfo(`${agentName} is already assigned to issue #${issueNum}`); } else { // Assign agent using GraphQL mutation - uses built-in github object authenticated via github-token (no allowed list filtering) const success = await assignAgentToIssue(issueDetails.issueId, agentId, issueDetails.currentAssignees, agentName, null); @@ -88,7 +89,7 @@ async function main() { await core.summary.addRaw(`## Issue Assignment\n\nSuccessfully assigned issue #${issueNum} to \`${trimmedAssignee}\`.\n`).write(); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to assign issue: ${errorMessage}`); + safeError(`Failed to assign issue: ${errorMessage}`); core.setFailed(`Failed to assign issue #${issueNum} to ${trimmedAssignee}: ${errorMessage}`); } } diff --git a/actions/setup/js/assign_milestone.cjs b/actions/setup/js/assign_milestone.cjs index 3eddf4054c9..fc490899215 100644 --- a/actions/setup/js/assign_milestone.cjs +++ b/actions/setup/js/assign_milestone.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -55,7 +56,7 @@ async function main(config = {}) { const milestoneNumber = Number(item.milestone_number); if (isNaN(issueNumber) || issueNumber <= 0) { - core.error(`Invalid issue_number: ${item.issue_number}`); + safeError(`Invalid issue_number: ${item.issue_number}`); return { success: false, error: `Invalid issue_number: ${item.issue_number}`, @@ -63,7 +64,7 @@ async function main(config = {}) { } if (isNaN(milestoneNumber) || milestoneNumber <= 0) { - core.error(`Invalid milestone_number: ${item.milestone_number}`); + safeError(`Invalid milestone_number: ${item.milestone_number}`); return { success: false, error: `Invalid milestone_number: ${item.milestone_number}`, @@ -83,7 +84,7 @@ async function main(config = {}) { core.info(`Fetched ${allMilestones.length} milestones from repository`); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to fetch milestones: ${errorMessage}`); + safeError(`Failed to fetch milestones: ${errorMessage}`); return { success: false, error: `Failed to fetch milestones for validation: ${errorMessage}`, @@ -106,7 +107,7 @@ async function main(config = {}) { const isAllowed = allowedMilestones.includes(milestone.title) || allowedMilestones.includes(String(milestoneNumber)); if (!isAllowed) { - core.warning(`Milestone "${milestone.title}" (#${milestoneNumber}) is not in the allowed list`); + safeWarning(`Milestone "${milestone.title}" (#${milestoneNumber}) is not in the allowed list`); return { success: false, error: `Milestone "${milestone.title}" (#${milestoneNumber}) is not in the allowed list`, @@ -131,7 +132,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to assign milestone #${milestoneNumber} to issue #${issueNumber}: ${errorMessage}`); + safeError(`Failed to assign milestone #${milestoneNumber} to issue #${issueNumber}: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs index 0aa01182f98..72db436beae 100644 --- a/actions/setup/js/assign_to_agent.cjs +++ b/actions/setup/js/assign_to_agent.cjs @@ -9,6 +9,7 @@ const { resolveTarget } = require("./safe_output_helpers.cjs"); const { loadTemporaryIdMap, resolveRepoIssueTarget } = require("./temporary_id.cjs"); const { sleep } = require("./error_recovery.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const result = loadAgentOutput(); if (!result.success) { @@ -143,7 +144,7 @@ async function main() { if (item.issue_number != null) { const resolvedTarget = resolveRepoIssueTarget(item.issue_number, temporaryIdMap, targetOwner, targetRepo); if (!resolvedTarget.resolved) { - core.error(resolvedTarget.errorMessage || `Failed to resolve issue target: ${item.issue_number}`); + safeError(resolvedTarget.errorMessage || `Failed to resolve issue target: ${item.issue_number}`); results.push({ issue_number: item.issue_number, pull_number: item.pull_number ?? null, @@ -215,7 +216,7 @@ async function main() { // Check if agent is supported if (!AGENT_LOGIN_NAMES[agentName]) { - core.warning(`Agent "${agentName}" is not supported. Supported agents: ${Object.keys(AGENT_LOGIN_NAMES).join(", ")}`); + safeWarning(`Agent "${agentName}" is not supported. Supported agents: ${Object.keys(AGENT_LOGIN_NAMES).join(", ")}`); results.push({ issue_number: issueNumber, pull_number: pullNumber, @@ -228,7 +229,7 @@ async function main() { // Check if agent is in allowed list (if configured) if (allowedAgents && !allowedAgents.includes(agentName)) { - core.error(`Agent "${agentName}" is not in the allowed list. Allowed agents: ${allowedAgents.join(", ")}`); + safeError(`Agent "${agentName}" is not in the allowed list. Allowed agents: ${allowedAgents.join(", ")}`); results.push({ issue_number: issueNumber, pull_number: pullNumber, @@ -244,13 +245,13 @@ async function main() { // Find agent (use cache if available) - uses built-in github object authenticated via github-token let agentId = agentCache[agentName]; if (!agentId) { - core.info(`Looking for ${agentName} coding agent...`); + safeInfo(`Looking for ${agentName} coding agent...`); agentId = await findAgent(effectiveOwner, effectiveRepo, agentName); if (!agentId) { throw new Error(`${agentName} coding agent is not available for this repository`); } agentCache[agentName] = agentId; - core.info(`Found ${agentName} coding agent (ID: ${agentId})`); + safeInfo(`Found ${agentName} coding agent (ID: ${agentId})`); } // Get issue or PR details (ID and current assignees) via GraphQL @@ -281,7 +282,7 @@ async function main() { // Check if agent is already assigned if (currentAssignees.some(a => a.id === agentId)) { - core.info(`${agentName} is already assigned to ${type} #${number}`); + safeInfo(`${agentName} is already assigned to ${type} #${number}`); results.push({ issue_number: issueNumber, pull_number: pullNumber, @@ -293,14 +294,14 @@ async function main() { // Assign agent using GraphQL mutation - uses built-in github object authenticated via github-token // Pass the allowed list so existing assignees are filtered before calling replaceActorsForAssignable - core.info(`Assigning ${agentName} coding agent to ${type} #${number}...`); + safeInfo(`Assigning ${agentName} coding agent to ${type} #${number}...`); const success = await assignAgentToIssue(assignableId, agentId, currentAssignees, agentName, allowedAgents); if (!success) { throw new Error(`Failed to assign ${agentName} via GraphQL`); } - core.info(`Successfully assigned ${agentName} coding agent to ${type} #${number}`); + safeInfo(`Successfully assigned ${agentName} coding agent to ${type} #${number}`); results.push({ issue_number: issueNumber, pull_number: pullNumber, @@ -320,8 +321,8 @@ async function main() { // If ignore-if-error is enabled and this is an auth error, log warning and skip if (ignoreIfError && isAuthError) { - core.warning(`Agent assignment failed for ${agentName} on ${type} #${number} due to authentication/permission error. Skipping due to ignore-if-error=true.`); - core.info(`Error details: ${errorMessage}`); + safeWarning(`Agent assignment failed for ${agentName} on ${type} #${number} due to authentication/permission error. Skipping due to ignore-if-error=true.`); + safeInfo(`Error details: ${errorMessage}`); results.push({ issue_number: issueNumber, pull_number: pullNumber, @@ -343,7 +344,7 @@ async function main() { core.debug("Failed to enrich unavailable agent message with available list"); } } - core.error(`Failed to assign agent "${agentName}" to ${type} #${number}: ${errorMessage}`); + safeError(`Failed to assign agent "${agentName}" to ${type} #${number}: ${errorMessage}`); results.push({ issue_number: issueNumber, pull_number: pullNumber, diff --git a/actions/setup/js/assign_to_user.cjs b/actions/setup/js/assign_to_user.cjs index 63bc4b96deb..6477f168b2f 100644 --- a/actions/setup/js/assign_to_user.cjs +++ b/actions/setup/js/assign_to_user.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { processItems } = require("./safe_output_processor.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -116,7 +117,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to assign users: ${errorMessage}`); + safeError(`Failed to assign users: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/autofix_code_scanning_alert.cjs b/actions/setup/js/autofix_code_scanning_alert.cjs index da96a7f01ab..d22d72562e4 100644 --- a/actions/setup/js/autofix_code_scanning_alert.cjs +++ b/actions/setup/js/autofix_code_scanning_alert.cjs @@ -6,6 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "autofix_code_scanning_alert"; @@ -63,11 +64,11 @@ async function main(config = {}) { // Parse alert number const alertNumber = parseInt(String(message.alert_number), 10); if (isNaN(alertNumber) || alertNumber <= 0) { - core.warning(`Invalid alert_number: ${message.alert_number}`); + safeWarning(`Invalid alert_number: ${message.alert_number}`); return { success: false, error: `Invalid alert_number: ${message.alert_number}` }; } - core.info(`Processing autofix_code_scanning_alert: alert_number=${alertNumber}, fix_description="${message.fix_description.substring(0, 50)}..."`); + safeInfo(`Processing autofix_code_scanning_alert: alert_number=${alertNumber}, fix_description="${message.fix_description.substring(0, 50)}..."`); // Staged mode: collect for preview if (isStaged) { @@ -83,8 +84,8 @@ async function main(config = {}) { // Create autofix via GitHub REST API try { core.info(`Creating autofix for code scanning alert ${alertNumber}`); - core.info(`Fix description: ${message.fix_description}`); - core.info(`Fix code length: ${message.fix_code.length} characters`); + safeInfo(`Fix description: ${message.fix_description}`); + safeInfo(`Fix code length: ${message.fix_code.length} characters`); // Call the GitHub REST API to create the autofix // Reference: https://docs.github.com/en/rest/code-scanning/code-scanning?apiVersion=2022-11-28#create-an-autofix-for-a-code-scanning-alert @@ -111,7 +112,7 @@ async function main(config = {}) { return { success: true, alertNumber, autofixUrl }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`✗ Failed to create autofix for alert ${alertNumber}: ${errorMessage}`); + safeError(`✗ Failed to create autofix for alert ${alertNumber}: ${errorMessage}`); // Provide helpful error messages if (errorMessage.includes("404")) { diff --git a/actions/setup/js/check_command_position.cjs b/actions/setup/js/check_command_position.cjs index 90787871aa0..c264a03137e 100644 --- a/actions/setup/js/check_command_position.cjs +++ b/actions/setup/js/check_command_position.cjs @@ -6,6 +6,7 @@ * This prevents accidental command triggers from words appearing later in content * Supports multiple command names - checks if any of them match */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const commandsJSON = process.env.GH_AW_COMMANDS; @@ -53,7 +54,7 @@ async function main() { text = context.payload.comment?.body || ""; } else { // For non-comment events, pass the check - core.info(`Event ${eventName} does not require command position check`); + safeInfo(`Event ${eventName} does not require command position check`); core.setOutput("command_position_ok", "true"); core.setOutput("matched_command", ""); return; @@ -63,7 +64,7 @@ async function main() { const trimmedText = text.trim(); const firstWord = trimmedText.split(/\s+/)[0]; - core.info(`Checking command position. First word in text: ${firstWord}`); + safeInfo(`Checking command position. First word in text: ${firstWord}`); core.info(`Looking for commands: ${commands.map(c => `/${c}`).join(", ")}`); // Check if any of the commands match @@ -78,12 +79,12 @@ async function main() { } if (matchedCommand) { - core.info(`✓ Command '/${matchedCommand}' matched at the start of the text`); + safeInfo(`✓ Command '/${matchedCommand}' matched at the start of the text`); core.setOutput("command_position_ok", "true"); core.setOutput("matched_command", matchedCommand); } else { const expectedCommands = commands.map(c => `/${c}`).join(", "); - core.warning(`⚠️ None of the commands [${expectedCommands}] matched the first word (found: '${firstWord}'). Workflow will be skipped.`); + safeWarning(`⚠️ None of the commands [${expectedCommands}] matched the first word (found: '${firstWord}'). Workflow will be skipped.`); core.setOutput("command_position_ok", "false"); core.setOutput("matched_command", ""); } diff --git a/actions/setup/js/check_membership.cjs b/actions/setup/js/check_membership.cjs index d2d67ff2dd8..cff4d649bba 100644 --- a/actions/setup/js/check_membership.cjs +++ b/actions/setup/js/check_membership.cjs @@ -3,6 +3,7 @@ const { parseRequiredPermissions, parseAllowedBots, checkRepositoryPermission, checkBotStatus } = require("./check_permissions_utils.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const { eventName } = context; const actor = context.actor; @@ -15,13 +16,13 @@ async function main() { if (eventName === "workflow_dispatch") { const hasWriteRole = requiredPermissions.includes("write"); if (hasWriteRole) { - core.info(`✅ Event ${eventName} does not require validation (write role allowed)`); + safeInfo(`✅ Event ${eventName} does not require validation (write role allowed)`); core.setOutput("is_team_member", "true"); core.setOutput("result", "safe_event"); return; } // If write is not allowed, continue with permission check - core.info(`Event ${eventName} requires validation (write role not allowed)`); + safeInfo(`Event ${eventName} requires validation (write role not allowed)`); } // skip check for other safe events @@ -35,7 +36,7 @@ async function main() { // - Validates combined state of multiple PRs before merging const safeEvents = ["schedule", "merge_group"]; if (safeEvents.includes(eventName)) { - core.info(`✅ Event ${eventName} does not require validation`); + safeInfo(`✅ Event ${eventName} does not require validation`); core.setOutput("is_team_member", "true"); core.setOutput("result", "safe_event"); return; diff --git a/actions/setup/js/check_permissions.cjs b/actions/setup/js/check_permissions.cjs index 5ba831db3f7..b1aeb09b00b 100644 --- a/actions/setup/js/check_permissions.cjs +++ b/actions/setup/js/check_permissions.cjs @@ -3,6 +3,7 @@ const { parseRequiredPermissions, checkRepositoryPermission } = require("./check_permissions_utils.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const { eventName, actor, repo } = context; const { owner, repo: repoName } = repo; @@ -18,7 +19,7 @@ async function main() { // - Validates combined state of multiple PRs before merging const safeEvents = ["workflow_dispatch", "schedule", "merge_group"]; if (safeEvents.includes(eventName)) { - core.info(`✅ Event ${eventName} does not require validation`); + safeInfo(`✅ Event ${eventName} does not require validation`); return; } diff --git a/actions/setup/js/check_permissions_utils.cjs b/actions/setup/js/check_permissions_utils.cjs index 9f10e54f222..664765616ec 100644 --- a/actions/setup/js/check_permissions_utils.cjs +++ b/actions/setup/js/check_permissions_utils.cjs @@ -7,6 +7,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * Shared utility for repository permission validation * Used by both check_permissions.cjs and check_membership.cjs */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Parse required permissions from environment variable @@ -62,12 +63,12 @@ async function checkBotStatus(actor, owner, repo) { } // For other errors, we'll treat as inactive to be safe const errorMessage = getErrorMessage(botError); - core.warning(`Failed to check bot status: ${errorMessage}`); + safeWarning(`Failed to check bot status: ${errorMessage}`); return { isBot: true, isActive: false, error: errorMessage }; } } catch (error) { const errorMessage = getErrorMessage(error); - core.warning(`Error checking bot status: ${errorMessage}`); + safeWarning(`Error checking bot status: ${errorMessage}`); return { isBot: false, isActive: false, error: errorMessage }; } } @@ -106,7 +107,7 @@ async function checkRepositoryPermission(actor, owner, repo, requiredPermissions return { authorized: false, permission }; } catch (repoError) { const errorMessage = getErrorMessage(repoError); - core.warning(`Repository permission check failed: ${errorMessage}`); + safeWarning(`Repository permission check failed: ${errorMessage}`); return { authorized: false, error: errorMessage }; } } diff --git a/actions/setup/js/check_rate_limit.cjs b/actions/setup/js/check_rate_limit.cjs index 48f4885322a..7b64128d101 100644 --- a/actions/setup/js/check_rate_limit.cjs +++ b/actions/setup/js/check_rate_limit.cjs @@ -5,6 +5,7 @@ * Rate limit check for per-user per-workflow triggers * Prevents users from triggering workflows too frequently */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const actor = context.actor; @@ -40,7 +41,7 @@ async function main() { core.info(`🔍 Checking rate limit for user '${actor}' on workflow '${workflowId}'`); core.info(` Configuration: max=${maxRuns} runs per ${windowMinutes} minutes`); - core.info(` Current event: ${eventName}`); + safeInfo(` Current event: ${eventName}`); // Check if user has an ignored role (exempt from rate limiting) const ignoredRoles = ignoredRolesList.split(",").map(r => r.trim()); @@ -77,19 +78,19 @@ async function main() { // If specific events are configured, check if current event should be limited if (limitedEvents.length > 0) { if (!limitedEvents.includes(eventName)) { - core.info(`✅ Event '${eventName}' is not subject to rate limiting`); + safeInfo(`✅ Event '${eventName}' is not subject to rate limiting`); core.info(` Rate limiting applies only to: ${limitedEvents.join(", ")}`); core.setOutput("rate_limit_ok", "true"); return; } - core.info(` Event '${eventName}' is subject to rate limiting`); + safeInfo(` Event '${eventName}' is subject to rate limiting`); } else { // When no specific events are configured, apply rate limiting only to // known programmatic triggers. Allow all other events. const programmaticEvents = ["workflow_dispatch", "repository_dispatch", "issue_comment", "pull_request_review", "pull_request_review_comment", "discussion_comment"]; if (!programmaticEvents.includes(eventName)) { - core.info(`✅ Event '${eventName}' is not a programmatic trigger; skipping rate limiting`); + safeInfo(`✅ Event '${eventName}' is not a programmatic trigger; skipping rate limiting`); core.info(` Rate limiting applies to: ${programmaticEvents.join(", ")}`); core.setOutput("rate_limit_ok", "true"); return; diff --git a/actions/setup/js/check_team_member.cjs b/actions/setup/js/check_team_member.cjs index e9fe8d138d9..2750d709e81 100644 --- a/actions/setup/js/check_team_member.cjs +++ b/actions/setup/js/check_team_member.cjs @@ -5,6 +5,7 @@ * Check if user has admin or maintainer permissions * @returns {Promise} */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const actor = context.actor; const { owner, repo } = context.repo; @@ -29,7 +30,7 @@ async function main() { } } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); - core.warning(`Repository permission check failed: ${errorMessage}`); + safeWarning(`Repository permission check failed: ${errorMessage}`); } // Fail the workflow when team membership check fails (cancellation handled by activation job's if condition) diff --git a/actions/setup/js/check_workflow_recompile_needed.cjs b/actions/setup/js/check_workflow_recompile_needed.cjs index d605527b887..593686d46b8 100644 --- a/actions/setup/js/check_workflow_recompile_needed.cjs +++ b/actions/setup/js/check_workflow_recompile_needed.cjs @@ -14,6 +14,7 @@ const fs = require("fs"); * * @returns {Promise} */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const owner = context.repo.owner; const repo = context.repo.repo; @@ -43,7 +44,7 @@ async function main() { // We need to check if there's actual diff output hasChanges = diffOutput.trim().length > 0; } catch (error) { - core.error(`Failed to check for workflow changes: ${getErrorMessage(error)}`); + safeError(`Failed to check for workflow changes: ${getErrorMessage(error)}`); throw error; } @@ -65,14 +66,14 @@ async function main() { }, }); } catch (error) { - core.warning(`Could not capture detailed diff: ${getErrorMessage(error)}`); + safeWarning(`Could not capture detailed diff: ${getErrorMessage(error)}`); } // Search for existing open issue about workflow recompilation const issueTitle = "[agentics] agentic workflows out of sync"; const searchQuery = `repo:${owner}/${repo} is:issue is:open in:title "${issueTitle}"`; - core.info(`Searching for existing issue with title: "${issueTitle}"`); + safeInfo(`Searching for existing issue with title: "${issueTitle}"`); try { const searchResult = await github.rest.search.issuesAndPullRequests({ @@ -116,7 +117,7 @@ async function main() { return; } } catch (error) { - core.error(`Failed to search for existing issues: ${getErrorMessage(error)}`); + safeError(`Failed to search for existing issues: ${getErrorMessage(error)}`); throw error; } @@ -134,7 +135,7 @@ async function main() { try { issueTemplate = fs.readFileSync(templatePath, "utf8"); } catch (error) { - core.error(`Failed to read issue template from ${templatePath}: ${getErrorMessage(error)}`); + safeError(`Failed to read issue template from ${templatePath}: ${getErrorMessage(error)}`); throw error; } @@ -173,7 +174,7 @@ async function main() { // Write to job summary await core.summary.addHeading("Workflow Recompilation Needed", 2).addRaw(`Created issue [#${newIssue.data.number}](${newIssue.data.html_url}) to track workflow recompilation.`).write(); } catch (error) { - core.error(`Failed to create issue: ${getErrorMessage(error)}`); + safeError(`Failed to create issue: ${getErrorMessage(error)}`); throw error; } } diff --git a/actions/setup/js/check_workflow_timestamp_api.cjs b/actions/setup/js/check_workflow_timestamp_api.cjs index a3cd2d3f279..03d830948dd 100644 --- a/actions/setup/js/check_workflow_timestamp_api.cjs +++ b/actions/setup/js/check_workflow_timestamp_api.cjs @@ -6,6 +6,7 @@ * This script compares the last commit time of the source .md file * with the compiled .lock.yml file and warns if recompilation is needed */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { extractHashFromLockFile, computeFrontmatterHash, createGitHubFileReader } = require("./frontmatter_hash_pure.cjs"); @@ -57,7 +58,7 @@ async function main() { return null; } catch (error) { const errorMessage = getErrorMessage(error); - core.info(`Could not fetch commit for ${path}: ${errorMessage}`); + safeInfo(`Could not fetch commit for ${path}: ${errorMessage}`); return null; } } @@ -95,7 +96,7 @@ async function main() { return { match, storedHash, recomputedHash }; } catch (error) { const errorMessage = getErrorMessage(error); - core.info(`Could not compute frontmatter hash: ${errorMessage}`); + safeInfo(`Could not compute frontmatter hash: ${errorMessage}`); return null; } } diff --git a/actions/setup/js/checkout_pr_branch.cjs b/actions/setup/js/checkout_pr_branch.cjs index db5f4404334..cd2cf94eaf0 100644 --- a/actions/setup/js/checkout_pr_branch.cjs +++ b/actions/setup/js/checkout_pr_branch.cjs @@ -19,6 +19,7 @@ * - Also run in base repository context * - Must use `gh pr checkout` to get PR branch */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { renderTemplate } = require("./messages_core.cjs"); @@ -31,7 +32,7 @@ const fs = require("fs"); function logPRContext(eventName, pullRequest) { core.startGroup("📋 PR Context Details"); - core.info(`Event type: ${eventName}`); + safeInfo(`Event type: ${eventName}`); core.info(`PR number: ${pullRequest.number}`); core.info(`PR state: ${pullRequest.state || "unknown"}`); @@ -41,7 +42,7 @@ function logPRContext(eventName, pullRequest) { core.info(`Head SHA: ${pullRequest.head.sha || "unknown"}`); if (pullRequest.head.repo) { - core.info(`Head repo: ${pullRequest.head.repo.full_name || "unknown"}`); + safeInfo(`Head repo: ${pullRequest.head.repo.full_name || "unknown"}`); core.info(`Head repo owner: ${pullRequest.head.repo.owner?.login || "unknown"}`); } else { core.warning("⚠️ Head repo information not available (repo may be deleted)"); @@ -54,7 +55,7 @@ function logPRContext(eventName, pullRequest) { core.info(`Base SHA: ${pullRequest.base.sha || "unknown"}`); if (pullRequest.base.repo) { - core.info(`Base repo: ${pullRequest.base.repo.full_name || "unknown"}`); + safeInfo(`Base repo: ${pullRequest.base.repo.full_name || "unknown"}`); core.info(`Base repo owner: ${pullRequest.base.repo.owner?.login || "unknown"}`); } } @@ -64,8 +65,8 @@ function logPRContext(eventName, pullRequest) { core.info(`Is fork PR: ${isFork} (${forkReason})`); // Log current repository context - core.info(`Current repository: ${context.repo.owner}/${context.repo.repo}`); - core.info(`GitHub SHA: ${context.sha}`); + safeInfo(`Current repository: ${context.repo.owner}/${context.repo.repo}`); + safeInfo(`GitHub SHA: ${context.sha}`); core.endGroup(); @@ -77,7 +78,7 @@ function logPRContext(eventName, pullRequest) { */ function logCheckoutStrategy(eventName, strategy, reason) { core.startGroup("🔄 Checkout Strategy"); - core.info(`Event type: ${eventName}`); + safeInfo(`Event type: ${eventName}`); core.info(`Strategy: ${strategy}`); core.info(`Reason: ${reason}`); core.endGroup(); @@ -93,7 +94,7 @@ async function main() { return; } - core.info(`Event: ${eventName}`); + safeInfo(`Event: ${eventName}`); core.info(`Pull Request #${pullRequest.number}`); // Check if PR is closed @@ -113,13 +114,13 @@ async function main() { logCheckoutStrategy(eventName, "git fetch + checkout", "pull_request event runs in merge commit context with PR branch available"); - core.info(`Fetching branch: ${branchName} from origin`); + safeInfo(`Fetching branch: ${branchName} from origin`); await exec.exec("git", ["fetch", "origin", branchName]); - core.info(`Checking out branch: ${branchName}`); + safeInfo(`Checking out branch: ${branchName}`); await exec.exec("git", ["checkout", branchName]); - core.info(`✅ Successfully checked out branch: ${branchName}`); + safeInfo(`✅ Successfully checked out branch: ${branchName}`); } else { // For pull_request_target and other PR events, we run in base repository context // IMPORTANT: For fork PRs, the head branch doesn't exist in the base repo @@ -160,7 +161,7 @@ async function main() { // Check if PR is closed - if so, treat checkout failure as a warning if (isClosed) { core.startGroup("⚠️ Closed PR Checkout Warning"); - core.warning(`Event type: ${eventName}`); + safeWarning(`Event type: ${eventName}`); core.warning(`PR number: ${pullRequest.number}`); core.warning(`PR state: closed`); core.warning(`Checkout failed (expected for closed PR): ${errorMsg}`); @@ -191,7 +192,7 @@ Pull request #${pullRequest.number} is closed. The checkout failed because the b // For open PRs, treat checkout failure as an error // Log detailed error context core.startGroup("❌ Checkout Error Details"); - core.error(`Event type: ${eventName}`); + safeError(`Event type: ${eventName}`); core.error(`PR number: ${pullRequest.number}`); core.error(`Error message: ${errorMsg}`); @@ -210,7 +211,7 @@ Pull request #${pullRequest.number} is closed. The checkout failed because the b core.info("Current branch:"); await exec.exec("git", ["branch", "--show-current"]); } catch (gitError) { - core.warning(`Could not retrieve git state: ${getErrorMessage(gitError)}`); + safeWarning(`Could not retrieve git state: ${getErrorMessage(gitError)}`); } core.endGroup(); diff --git a/actions/setup/js/close_discussion.cjs b/actions/setup/js/close_discussion.cjs index ce36b2972a2..fcd4b55d0a5 100644 --- a/actions/setup/js/close_discussion.cjs +++ b/actions/setup/js/close_discussion.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -156,10 +157,10 @@ async function main(config = {}) { core.info(`Close discussion configuration: max=${maxCount}`); if (requiredLabels.length > 0) { - core.info(`Required labels: ${requiredLabels.join(", ")}`); + safeInfo(`Required labels: ${requiredLabels.join(", ")}`); } if (requiredTitlePrefix) { - core.info(`Required title prefix: ${requiredTitlePrefix}`); + safeInfo(`Required title prefix: ${requiredTitlePrefix}`); } // Track how many items we've processed for max limit @@ -190,7 +191,7 @@ async function main(config = {}) { if (item.discussion_number !== undefined) { discussionNumber = parseInt(String(item.discussion_number), 10); if (isNaN(discussionNumber)) { - core.warning(`Invalid discussion number: ${item.discussion_number}`); + safeWarning(`Invalid discussion number: ${item.discussion_number}`); return { success: false, error: `Invalid discussion number: ${item.discussion_number}`, @@ -218,7 +219,7 @@ async function main(config = {}) { const discussionLabels = discussion.labels.nodes.map(l => l.name); const missingLabels = requiredLabels.filter(required => !discussionLabels.includes(required)); if (missingLabels.length > 0) { - core.warning(`Discussion #${discussionNumber} missing required labels: ${missingLabels.join(", ")}`); + safeWarning(`Discussion #${discussionNumber} missing required labels: ${missingLabels.join(", ")}`); return { success: false, error: `Missing required labels: ${missingLabels.join(", ")}`, @@ -228,7 +229,7 @@ async function main(config = {}) { // Validate required title prefix if configured if (requiredTitlePrefix && !discussion.title.startsWith(requiredTitlePrefix)) { - core.warning(`Discussion #${discussionNumber} title doesn't start with "${requiredTitlePrefix}"`); + safeWarning(`Discussion #${discussionNumber} title doesn't start with "${requiredTitlePrefix}"`); return { success: false, error: `Title doesn't start with "${requiredTitlePrefix}"`, @@ -257,7 +258,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to close discussion #${discussionNumber}: ${errorMessage}`); + safeError(`Failed to close discussion #${discussionNumber}: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/close_entity_helpers.cjs b/actions/setup/js/close_entity_helpers.cjs index 50e3cb127ba..e993a4196c3 100644 --- a/actions/setup/js/close_entity_helpers.cjs +++ b/actions/setup/js/close_entity_helpers.cjs @@ -10,6 +10,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); /** * @typedef {'issue' | 'pull_request'} EntityType */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * @typedef {Object} EntityConfig @@ -130,7 +131,7 @@ async function generateCloseEntityStagedPreview(config, items, requiredLabels, r // Write to step summary await core.summary.addRaw(summaryContent).write(); - core.info(`📝 ${config.displayNameCapitalized} close preview written to step summary`); + safeInfo(`📝 ${config.displayNameCapitalized} close preview written to step summary`); } /** @@ -245,7 +246,7 @@ async function processCloseEntityItems(config, callbacks, handlerConfig = {}) { const requiredTitlePrefix = handlerConfig.required_title_prefix || ""; const target = handlerConfig.target || "triggering"; - core.info(`Configuration: requiredLabels=${requiredLabels.join(",")}, requiredTitlePrefix=${requiredTitlePrefix}, target=${target}`); + safeInfo(`Configuration: requiredLabels=${requiredLabels.join(",")}, requiredTitlePrefix=${requiredTitlePrefix}, target=${target}`); // Check if we're in the correct entity context const isEntityContext = config.contextEvents.some(event => context.eventName === event); @@ -258,7 +259,7 @@ async function processCloseEntityItems(config, callbacks, handlerConfig = {}) { // Validate context based on target configuration if (target === "triggering" && !isEntityContext) { - core.info(`Target is "triggering" but not running in ${config.displayName} context, skipping ${config.displayName} close`); + safeInfo(`Target is "triggering" but not running in ${config.displayName} context, skipping ${config.displayName} close`); return; } @@ -271,7 +272,7 @@ async function processCloseEntityItems(config, callbacks, handlerConfig = {}) { // Process each item for (let i = 0; i < items.length; i++) { const item = items[i]; - core.info(`Processing ${config.itemTypeDisplay} item ${i + 1}/${items.length}: bodyLength=${item.body.length}`); + safeInfo(`Processing ${config.itemTypeDisplay} item ${i + 1}/${items.length}: bodyLength=${item.body.length}`); // Resolve entity number const resolved = resolveEntityNumber(config, target, item, isEntityContext); @@ -287,19 +288,19 @@ async function processCloseEntityItems(config, callbacks, handlerConfig = {}) { // Apply label filter if (!checkLabelFilter(entity.labels, requiredLabels)) { - core.info(`${config.displayNameCapitalized} #${entityNumber} does not have required labels: ${requiredLabels.join(", ")}`); + safeInfo(`${config.displayNameCapitalized} #${entityNumber} does not have required labels: ${requiredLabels.join(", ")}`); continue; } // Apply title prefix filter if (!checkTitlePrefixFilter(entity.title, requiredTitlePrefix)) { - core.info(`${config.displayNameCapitalized} #${entityNumber} does not have required title prefix: ${requiredTitlePrefix}`); + safeInfo(`${config.displayNameCapitalized} #${entityNumber} does not have required title prefix: ${requiredTitlePrefix}`); continue; } // Check if already closed if (entity.state === "closed") { - core.info(`${config.displayNameCapitalized} #${entityNumber} is already closed, skipping`); + safeInfo(`${config.displayNameCapitalized} #${entityNumber} is already closed, skipping`); continue; } @@ -308,11 +309,11 @@ async function processCloseEntityItems(config, callbacks, handlerConfig = {}) { // Add comment before closing const comment = await callbacks.addComment(github, context.repo.owner, context.repo.repo, entityNumber, commentBody); - core.info(`✓ Added comment to ${config.displayName} #${entityNumber}: ${comment.html_url}`); + safeInfo(`✓ Added comment to ${config.displayName} #${entityNumber}: ${comment.html_url}`); // Close the entity const closedEntity = await callbacks.closeEntity(github, context.repo.owner, context.repo.repo, entityNumber); - core.info(`✓ Closed ${config.displayName} #${entityNumber}: ${closedEntity.html_url}`); + safeInfo(`✓ Closed ${config.displayName} #${entityNumber}: ${closedEntity.html_url}`); closedEntities.push({ entity: closedEntity, @@ -328,7 +329,7 @@ async function processCloseEntityItems(config, callbacks, handlerConfig = {}) { core.setOutput("comment_url", comment.html_url); } } catch (error) { - core.error(`✗ Failed to close ${config.displayName} #${entityNumber}: ${getErrorMessage(error)}`); + safeError(`✗ Failed to close ${config.displayName} #${entityNumber}: ${getErrorMessage(error)}`); throw error; } } @@ -343,7 +344,7 @@ async function processCloseEntityItems(config, callbacks, handlerConfig = {}) { await core.summary.addRaw(summaryContent).write(); } - core.info(`Successfully closed ${closedEntities.length} ${config.displayName}(s)`); + safeInfo(`Successfully closed ${closedEntities.length} ${config.displayName}(s)`); return closedEntities; } diff --git a/actions/setup/js/close_issue.cjs b/actions/setup/js/close_issue.cjs index 9393deba3af..490bb8a94c3 100644 --- a/actions/setup/js/close_issue.cjs +++ b/actions/setup/js/close_issue.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs"); @@ -84,10 +85,10 @@ async function main(config = {}) { core.info(`Close issue configuration: max=${maxCount}`); if (requiredLabels.length > 0) { - core.info(`Required labels: ${requiredLabels.join(", ")}`); + safeInfo(`Required labels: ${requiredLabels.join(", ")}`); } if (requiredTitlePrefix) { - core.info(`Required title prefix: ${requiredTitlePrefix}`); + safeInfo(`Required title prefix: ${requiredTitlePrefix}`); } core.info(`Default target repo: ${defaultTargetRepo}`); if (allowedRepos.size > 0) { @@ -134,7 +135,7 @@ async function main(config = {}) { if (item.issue_number !== undefined) { issueNumber = parseInt(String(item.issue_number), 10); if (isNaN(issueNumber)) { - core.warning(`Invalid issue number: ${item.issue_number}`); + safeWarning(`Invalid issue number: ${item.issue_number}`); return { success: false, error: `Invalid issue number: ${item.issue_number}`, @@ -172,7 +173,7 @@ async function main(config = {}) { const issueLabels = issue.labels.map(l => (typeof l === "string" ? l : l.name || "")); const missingLabels = requiredLabels.filter(required => !issueLabels.includes(required)); if (missingLabels.length > 0) { - core.warning(`Issue #${issueNumber} missing required labels: ${missingLabels.join(", ")}`); + safeWarning(`Issue #${issueNumber} missing required labels: ${missingLabels.join(", ")}`); return { success: false, error: `Missing required labels: ${missingLabels.join(", ")}`, @@ -182,7 +183,7 @@ async function main(config = {}) { // Validate required title prefix if configured if (requiredTitlePrefix && !issue.title.startsWith(requiredTitlePrefix)) { - core.warning(`Issue #${issueNumber} title doesn't start with "${requiredTitlePrefix}"`); + safeWarning(`Issue #${issueNumber} title doesn't start with "${requiredTitlePrefix}"`); return { success: false, error: `Title doesn't start with "${requiredTitlePrefix}"`, @@ -207,7 +208,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to close issue #${issueNumber}: ${errorMessage}`); + safeError(`Failed to close issue #${issueNumber}: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/close_older_discussions.cjs b/actions/setup/js/close_older_discussions.cjs index 16e4934d7cb..5beb7f7ef93 100644 --- a/actions/setup/js/close_older_discussions.cjs +++ b/actions/setup/js/close_older_discussions.cjs @@ -8,6 +8,7 @@ const { getWorkflowIdMarkerContent } = require("./generate_footer.cjs"); /** * Maximum number of older discussions to close */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const MAX_CLOSE_COUNT = 10; /** @@ -117,7 +118,7 @@ async function searchOlderDiscussions(github, owner, repo, workflowId, categoryI } filteredCount++; - core.info(` ✓ Discussion #${d.number} matches criteria: ${d.title}`); + safeInfo(` ✓ Discussion #${d.number} matches criteria: ${d.title}`); return true; } ) @@ -202,9 +203,9 @@ async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId core.info("Starting closeOlderDiscussions operation"); core.info("=".repeat(70)); - core.info(`Search criteria: workflow ID marker: "${getWorkflowIdMarkerContent(workflowId)}"`); + safeInfo(`Search criteria: workflow ID marker: "${getWorkflowIdMarkerContent(workflowId)}"`); core.info(`New discussion reference: #${newDiscussion.number} (${newDiscussion.url})`); - core.info(`Workflow: ${workflowName}`); + safeInfo(`Workflow: ${workflowName}`); core.info(`Run URL: ${runUrl}`); core.info(""); @@ -219,7 +220,7 @@ async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId core.info(""); core.info(`Found ${olderDiscussions.length} older discussion(s) matching the criteria`); for (const discussion of olderDiscussions) { - core.info(` - Discussion #${discussion.number}: ${discussion.title}`); + safeInfo(` - Discussion #${discussion.number}: ${discussion.title}`); core.info(` URL: ${discussion.url}`); } @@ -242,7 +243,7 @@ async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId const discussion = discussionsToClose[i]; core.info("-".repeat(70)); core.info(`Processing discussion ${i + 1}/${discussionsToClose.length}: #${discussion.number}`); - core.info(` Title: ${discussion.title}`); + safeInfo(` Title: ${discussion.title}`); core.info(` URL: ${discussion.url}`); try { @@ -254,7 +255,7 @@ async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId runUrl, }); - core.info(` Message length: ${closingMessage.length} characters`); + safeInfo(` Message length: ${closingMessage.length} characters`); core.info(""); // Add comment first @@ -273,9 +274,9 @@ async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId } catch (error) { core.info(""); core.error(`✗ Failed to close discussion #${discussion.number}`); - core.error(` Error: ${getErrorMessage(error)}`); + safeError(` Error: ${getErrorMessage(error)}`); if (error instanceof Error && error.stack) { - core.error(` Stack trace: ${error.stack}`); + safeError(` Stack trace: ${error.stack}`); } // Continue with other discussions even if one fails } diff --git a/actions/setup/js/close_older_issues.cjs b/actions/setup/js/close_older_issues.cjs index eb9bdbc2e63..f3dcabfda52 100644 --- a/actions/setup/js/close_older_issues.cjs +++ b/actions/setup/js/close_older_issues.cjs @@ -7,6 +7,7 @@ const { getWorkflowIdMarkerContent } = require("./generate_footer.cjs"); /** * Maximum number of older issues to close */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const MAX_CLOSE_COUNT = 10; /** @@ -83,12 +84,12 @@ async function searchOlderIssues(github, owner, repo, workflowId, excludeNumber) // Exclude the newly created issue if (item.number === excludeNumber) { excludedCount++; - core.info(` Excluding issue #${item.number} (the newly created issue)`); + safeInfo(` Excluding issue #${item.number} (the newly created issue)`); return false; } filteredCount++; - core.info(` ✓ Issue #${item.number} matches criteria: ${item.title}`); + safeInfo(` ✓ Issue #${item.number} matches criteria: ${item.title}`); return true; }) .map(item => ({ @@ -117,7 +118,7 @@ async function searchOlderIssues(github, owner, repo, workflowId, excludeNumber) */ async function addIssueComment(github, owner, repo, issueNumber, message) { core.info(`Adding comment to issue #${issueNumber} in ${owner}/${repo}`); - core.info(` Comment length: ${message.length} characters`); + safeInfo(` Comment length: ${message.length} characters`); const result = await github.rest.issues.createComment({ owner, @@ -198,9 +199,9 @@ async function closeOlderIssues(github, owner, repo, workflowId, newIssue, workf core.info("Starting closeOlderIssues operation"); core.info("=".repeat(70)); - core.info(`Search criteria: workflow-id marker: "${getWorkflowIdMarkerContent(workflowId)}"`); + safeInfo(`Search criteria: workflow-id marker: "${getWorkflowIdMarkerContent(workflowId)}"`); core.info(`New issue reference: #${newIssue.number} (${newIssue.html_url})`); - core.info(`Workflow: ${workflowName}`); + safeInfo(`Workflow: ${workflowName}`); core.info(`Run URL: ${runUrl}`); core.info(""); @@ -215,8 +216,8 @@ async function closeOlderIssues(github, owner, repo, workflowId, newIssue, workf core.info(""); core.info(`Found ${olderIssues.length} older issue(s) matching the criteria`); for (const issue of olderIssues) { - core.info(` - Issue #${issue.number}: ${issue.title}`); - core.info(` Labels: ${issue.labels.map(l => l.name).join(", ") || "(none)"}`); + safeInfo(` - Issue #${issue.number}: ${issue.title}`); + safeInfo(` Labels: ${issue.labels.map(l => l.name).join(", ") || "(none)"}`); core.info(` URL: ${issue.html_url}`); } @@ -239,7 +240,7 @@ async function closeOlderIssues(github, owner, repo, workflowId, newIssue, workf const issue = issuesToClose[i]; core.info("-".repeat(70)); core.info(`Processing issue ${i + 1}/${issuesToClose.length}: #${issue.number}`); - core.info(` Title: ${issue.title}`); + safeInfo(` Title: ${issue.title}`); core.info(` URL: ${issue.html_url}`); try { @@ -251,7 +252,7 @@ async function closeOlderIssues(github, owner, repo, workflowId, newIssue, workf runUrl, }); - core.info(` Message length: ${closingMessage.length} characters`); + safeInfo(` Message length: ${closingMessage.length} characters`); core.info(""); // Add comment first @@ -270,9 +271,9 @@ async function closeOlderIssues(github, owner, repo, workflowId, newIssue, workf } catch (error) { core.info(""); core.error(`✗ Failed to close issue #${issue.number}`); - core.error(` Error: ${getErrorMessage(error)}`); + safeError(` Error: ${getErrorMessage(error)}`); if (error instanceof Error && error.stack) { - core.error(` Stack trace: ${error.stack}`); + safeError(` Stack trace: ${error.stack}`); } // Continue with other issues even if one fails } diff --git a/actions/setup/js/close_pull_request.cjs b/actions/setup/js/close_pull_request.cjs index e5c59a90e54..8de1303d2f4 100644 --- a/actions/setup/js/close_pull_request.cjs +++ b/actions/setup/js/close_pull_request.cjs @@ -6,6 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const HANDLER_TYPE = "close_pull_request"; @@ -83,10 +84,10 @@ async function main(config = {}) { core.info(`Close pull request configuration: max=${maxCount}`); if (requiredLabels.length > 0) { - core.info(`Required labels: ${requiredLabels.join(", ")}`); + safeInfo(`Required labels: ${requiredLabels.join(", ")}`); } if (requiredTitlePrefix) { - core.info(`Required title prefix: ${requiredTitlePrefix}`); + safeInfo(`Required title prefix: ${requiredTitlePrefix}`); } // Track how many items we've processed for max limit @@ -117,7 +118,7 @@ async function main(config = {}) { if (item.pull_request_number !== undefined) { prNumber = parseInt(String(item.pull_request_number), 10); if (isNaN(prNumber)) { - core.warning(`Invalid pull request number: ${item.pull_request_number}`); + safeWarning(`Invalid pull request number: ${item.pull_request_number}`); return { success: false, error: `Invalid pull request number: ${item.pull_request_number}`, @@ -164,7 +165,7 @@ async function main(config = {}) { // Check label filter if (!checkLabelFilter(pr.labels, requiredLabels)) { - core.info(`Skipping PR #${prNumber}: does not match label filter (required: ${requiredLabels.join(", ")})`); + safeInfo(`Skipping PR #${prNumber}: does not match label filter (required: ${requiredLabels.join(", ")})`); return { success: false, error: `PR does not match required labels`, @@ -173,7 +174,7 @@ async function main(config = {}) { // Check title prefix filter if (!checkTitlePrefixFilter(pr.title, requiredTitlePrefix)) { - core.info(`Skipping PR #${prNumber}: title does not start with '${requiredTitlePrefix}'`); + safeInfo(`Skipping PR #${prNumber}: title does not start with '${requiredTitlePrefix}'`); return { success: false, error: `PR title does not start with required prefix`, @@ -198,7 +199,7 @@ async function main(config = {}) { // Close the PR try { const closedPR = await closePullRequest(github, owner, repo, prNumber); - core.info(`✓ Closed PR #${prNumber}: ${closedPR.title}`); + safeInfo(`✓ Closed PR #${prNumber}: ${closedPR.title}`); return { success: true, pull_request_number: closedPR.number, diff --git a/actions/setup/js/collect_ndjson_output.cjs b/actions/setup/js/collect_ndjson_output.cjs index fcfca39fb56..8470d36a7e0 100644 --- a/actions/setup/js/collect_ndjson_output.cjs +++ b/actions/setup/js/collect_ndjson_output.cjs @@ -5,6 +5,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { repairJson, sanitizePrototypePollution } = require("./json_repair_helpers.cjs"); const { AGENT_OUTPUT_FILENAME, TMP_GH_AW_PATH } = require("./constants.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { try { const fs = require("fs"); @@ -24,7 +25,7 @@ async function main() { core.info(`Loaded validation config from ${validationConfigPath}`); } } catch (error) { - core.warning(`Failed to read validation config from ${validationConfigPath}: ${getErrorMessage(error)}`); + safeWarning(`Failed to read validation config from ${validationConfigPath}: ${getErrorMessage(error)}`); } // Extract mentions configuration from validation config @@ -153,14 +154,14 @@ async function main() { try { if (fs.existsSync(configPath)) { const configFileContent = fs.readFileSync(configPath, "utf8"); - core.info(`[INGESTION] Raw config content: ${configFileContent}`); + safeInfo(`[INGESTION] Raw config content: ${configFileContent}`); safeOutputsConfig = JSON.parse(configFileContent); core.info(`[INGESTION] Parsed config keys: ${JSON.stringify(Object.keys(safeOutputsConfig))}`); } else { core.info(`[INGESTION] Config file does not exist at: ${configPath}`); } } catch (error) { - core.warning(`Failed to read config file from ${configPath}: ${getErrorMessage(error)}`); + safeWarning(`Failed to read config file from ${configPath}: ${getErrorMessage(error)}`); } core.info(`[INGESTION] Output file path: ${outputFile}`); @@ -178,8 +179,8 @@ async function main() { if (outputContent.trim() === "") { core.info("Output file is empty"); } - core.info(`Raw output content length: ${outputContent.length}`); - core.info(`[INGESTION] First 500 chars of output: ${outputContent.substring(0, 500)}`); + safeInfo(`Raw output content length: ${outputContent.length}`); + safeInfo(`[INGESTION] First 500 chars of output: ${outputContent.substring(0, 500)}`); let expectedOutputTypes = {}; if (safeOutputsConfig) { try { @@ -191,7 +192,7 @@ async function main() { core.info(`[INGESTION] Expected output types full config: ${JSON.stringify(expectedOutputTypes)}`); } catch (error) { const errorMsg = getErrorMessage(error); - core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); + safeInfo(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL (JSON Lines) format: each line is a separate JSON object @@ -332,7 +333,7 @@ async function main() { const errorMsg = getErrorMessage(error); core.error(`Failed to ingest agent output: ${errorMsg}`); if (error instanceof Error && error.stack) { - core.error(`Stack trace: ${error.stack}`); + safeError(`Stack trace: ${error.stack}`); } core.setFailed(`Agent output ingestion failed: ${errorMsg}`); throw error; diff --git a/actions/setup/js/compute_text.cjs b/actions/setup/js/compute_text.cjs index 19560baaf18..b8f0faf0726 100644 --- a/actions/setup/js/compute_text.cjs +++ b/actions/setup/js/compute_text.cjs @@ -6,6 +6,7 @@ * @param {string} content - The content to sanitize * @returns {string} The sanitized content */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { sanitizeIncomingText, writeRedactedDomainsLog } = require("./sanitize_incoming_text.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -126,7 +127,7 @@ async function main() { const body = release.body || ""; text = `${name}\n\n${body}`; } catch (error) { - core.warning(`Failed to fetch release from URL: ${getErrorMessage(error)}`); + safeWarning(`Failed to fetch release from URL: ${getErrorMessage(error)}`); } } } else if (releaseId) { @@ -141,7 +142,7 @@ async function main() { const body = release.body || ""; text = `${name}\n\n${body}`; } catch (error) { - core.warning(`Failed to fetch release by ID: ${getErrorMessage(error)}`); + safeWarning(`Failed to fetch release by ID: ${getErrorMessage(error)}`); } } } @@ -159,7 +160,7 @@ async function main() { const sanitizedText = sanitizeIncomingText(text); // Display sanitized text in logs - core.info(`text: ${sanitizedText}`); + safeInfo(`text: ${sanitizedText}`); // Set the sanitized text as output core.setOutput("text", sanitizedText); diff --git a/actions/setup/js/create_agent_session.cjs b/actions/setup/js/create_agent_session.cjs index ba61152f487..665f1163f45 100644 --- a/actions/setup/js/create_agent_session.cjs +++ b/actions/setup/js/create_agent_session.cjs @@ -6,6 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const fs = require("fs"); const path = require("path"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { // Initialize outputs to empty strings to ensure they're always set core.setOutput("session_number", ""); @@ -31,7 +32,7 @@ async function main() { core.info("Agent output content is empty"); return; } - core.info(`Agent output content length: ${outputContent.length}`); + safeInfo(`Agent output content length: ${outputContent.length}`); let validatedOutput; try { @@ -134,7 +135,7 @@ async function main() { core.error(`You must configure a Personal Access Token (PAT) as COPILOT_GITHUB_TOKEN or GH_AW_GITHUB_TOKEN.`); core.error(`See documentation: https://github.github.com/gh-aw/reference/safe-outputs/#agent-task-creation-create-agent-session`); } else { - core.error(`Task ${index + 1}: Failed to create agent session: ${errorMessage}`); + safeError(`Task ${index + 1}: Failed to create agent session: ${errorMessage}`); } continue; } @@ -161,7 +162,7 @@ async function main() { createdTasks.push({ number: "", url: output }); } } catch (error) { - core.error(`Task ${index + 1}: Error creating agent session: ${getErrorMessage(error)}`); + safeError(`Task ${index + 1}: Error creating agent session: ${getErrorMessage(error)}`); } } diff --git a/actions/setup/js/create_code_scanning_alert.cjs b/actions/setup/js/create_code_scanning_alert.cjs index fd3e7771600..b1824942f62 100644 --- a/actions/setup/js/create_code_scanning_alert.cjs +++ b/actions/setup/js/create_code_scanning_alert.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const fs = require("fs"); @@ -24,8 +25,8 @@ async function main(config = {}) { const workflowFilename = config.workflow_filename || "workflow"; core.info(`Create code scanning alert configuration: max=${maxFindings === 0 ? "unlimited" : maxFindings}`); - core.info(`Driver name: ${driverName}`); - core.info(`Workflow filename for rule ID prefix: ${workflowFilename}`); + safeInfo(`Driver name: ${driverName}`); + safeInfo(`Workflow filename for rule ID prefix: ${workflowFilename}`); // Track how many items we've processed for max limit let processedCount = 0; @@ -245,7 +246,7 @@ async function main(config = {}) { core.setOutput("artifact_uploaded", "pending"); core.setOutput("codeql_uploaded", "pending"); } catch (error) { - core.error(`✗ Failed to write SARIF file: ${getErrorMessage(error)}`); + safeError(`✗ Failed to write SARIF file: ${getErrorMessage(error)}`); return { success: false, error: `Failed to write SARIF file: ${getErrorMessage(error)}`, diff --git a/actions/setup/js/create_discussion.cjs b/actions/setup/js/create_discussion.cjs index 0d6daf14244..868d99f27de 100644 --- a/actions/setup/js/create_discussion.cjs +++ b/actions/setup/js/create_discussion.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "create_discussion"; @@ -155,7 +156,7 @@ async function handleFallbackToIssue(createIssueHandler, item, qualifiedItemRepo } } catch (fallbackError) { const fallbackErrorMessage = getErrorMessage(fallbackError); - core.error(`Fallback to create-issue failed: ${fallbackErrorMessage}`); + safeError(`Fallback to create-issue failed: ${fallbackErrorMessage}`); return { success: false, error: `${contextMessage} and fallback to issue threw an error: ${fallbackErrorMessage}`, @@ -260,7 +261,7 @@ async function main(config = {}) { if (!errorMessage) { throw new Error("Internal error: repoValidation.error should not be null when valid is false"); } - core.warning(`Skipping discussion: ${errorMessage}`); + safeWarning(`Skipping discussion: ${errorMessage}`); return { success: false, error: errorMessage, @@ -302,7 +303,7 @@ async function main(config = {}) { // Check if this is a permissions error and fallback is enabled if (fallbackToIssue && createIssueHandler && isPermissionsError(errorMessage)) { - core.warning(`Failed to fetch discussion info due to permissions: ${errorMessage}`); + safeWarning(`Failed to fetch discussion info due to permissions: ${errorMessage}`); core.info(`Falling back to create-issue for ${qualifiedItemRepo}`); return await handleFallbackToIssue(createIssueHandler, item, qualifiedItemRepo, resolvedTemporaryIds, "Failed to fetch discussion info"); @@ -334,7 +335,7 @@ async function main(config = {}) { } const categoryId = resolvedCategory.id; - core.info(`Using category: ${resolvedCategory.name} (${resolvedCategory.matchType})`); + safeInfo(`Using category: ${resolvedCategory.name} (${resolvedCategory.matchType})`); // Build title let title = item.title ? item.title.trim() : ""; @@ -386,7 +387,7 @@ async function main(config = {}) { bodyLines.push(""); const body = bodyLines.join("\n").trim(); - core.info(`Creating discussion in ${qualifiedItemRepo} with title: ${title}`); + safeInfo(`Creating discussion in ${qualifiedItemRepo} with title: ${title}`); try { const createDiscussionMutation = ` @@ -437,7 +438,7 @@ async function main(config = {}) { // Check if this is a permissions error and fallback is enabled if (fallbackToIssue && createIssueHandler && isPermissionsError(errorMessage)) { - core.warning(`Discussion creation failed due to permissions: ${errorMessage}`); + safeWarning(`Discussion creation failed due to permissions: ${errorMessage}`); core.info(`Falling back to create-issue for ${qualifiedItemRepo}`); return await handleFallbackToIssue(createIssueHandler, item, qualifiedItemRepo, resolvedTemporaryIds, "Discussion creation failed"); diff --git a/actions/setup/js/create_issue.cjs b/actions/setup/js/create_issue.cjs index 9ea30bb3521..3409d727d5f 100644 --- a/actions/setup/js/create_issue.cjs +++ b/actions/setup/js/create_issue.cjs @@ -37,6 +37,7 @@ const { renderTemplate } = require("./messages_core.cjs"); const { createExpirationLine, addExpirationToFooter } = require("./ephemerals.cjs"); const { MAX_SUB_ISSUES, getSubIssueCount } = require("./sub_issue_helpers.cjs"); const { closeOlderIssues } = require("./close_older_issues.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const fs = require("fs"); /** @@ -75,7 +76,7 @@ async function searchForExistingParent(owner, repo, markerComment) { // Check each found issue to see if it can accept more sub-issues for (const issue of searchResults.data.items) { - core.info(`Found potential parent issue #${issue.number}: ${issue.title}`); + safeInfo(`Found potential parent issue #${issue.number}: ${issue.title}`); if (issue.state !== "open") { core.info(`Parent issue #${issue.number} is ${issue.state}, skipping`); @@ -97,7 +98,7 @@ async function searchForExistingParent(owner, repo, markerComment) { return null; } catch (error) { - core.warning(`Could not search for existing parent issues: ${getErrorMessage(error)}`); + safeWarning(`Could not search for existing parent issues: ${getErrorMessage(error)}`); return null; } } @@ -140,7 +141,7 @@ async function findOrCreateParentIssue({ groupId, owner, repo, titlePrefix, labe core.info(`Created new parent issue #${parentIssue.number}: ${parentIssue.html_url}`); return parentIssue.number; } catch (error) { - core.error(`Failed to create parent issue: ${getErrorMessage(error)}`); + safeError(`Failed to create parent issue: ${getErrorMessage(error)}`); return null; } } @@ -208,13 +209,13 @@ async function main(config = {}) { core.info(`Allowed repos: ${Array.from(allowedRepos).join(", ")}`); } if (envLabels.length > 0) { - core.info(`Default labels: ${envLabels.join(", ")}`); + safeInfo(`Default labels: ${envLabels.join(", ")}`); } if (envAssignees.length > 0) { core.info(`Default assignees: ${envAssignees.join(", ")}`); } if (titlePrefix) { - core.info(`Title prefix: ${titlePrefix}`); + safeInfo(`Title prefix: ${titlePrefix}`); } if (expiresHours > 0) { core.info(`Issues expire after: ${expiresHours} hours`); @@ -283,7 +284,7 @@ async function main(config = {}) { if (!errorMessage) { throw new Error("Internal error: repoValidation.error should not be null when valid is false"); } - core.warning(`Skipping issue: ${errorMessage}`); + safeWarning(`Skipping issue: ${errorMessage}`); return { success: false, error: errorMessage, @@ -306,7 +307,7 @@ async function main(config = {}) { // Get or generate the temporary ID for this issue const temporaryId = message.temporary_id ?? generateTemporaryId(); - core.info(`Processing create_issue: title=${message.title}, bodyLength=${message.body?.length ?? 0}, temporaryId=${temporaryId}, repo=${qualifiedItemRepo}`); + safeInfo(`Processing create_issue: title=${message.title}, bodyLength=${message.body?.length ?? 0}, temporaryId=${temporaryId}, repo=${qualifiedItemRepo}`); // Resolve parent: check if it's a temporary ID reference let effectiveParentIssueNumber; @@ -322,21 +323,21 @@ async function main(config = {}) { if (resolvedParent) { effectiveParentIssueNumber = resolvedParent.number; effectiveParentRepo = resolvedParent.repo; - core.info(`Resolved parent temporary ID '${message.parent}' to ${effectiveParentRepo}#${effectiveParentIssueNumber}`); + safeInfo(`Resolved parent temporary ID '${message.parent}' to ${effectiveParentRepo}#${effectiveParentIssueNumber}`); } else { - core.warning(`Parent temporary ID '${message.parent}' not found in map. Ensure parent issue is created before sub-issues.`); + safeWarning(`Parent temporary ID '${message.parent}' not found in map. Ensure parent issue is created before sub-issues.`); } } else { // Check if it looks like a malformed temporary ID if (parentWithoutHash.startsWith("aw_")) { - core.warning(`Invalid temporary ID format for parent: '${message.parent}'. Temporary IDs must be in format 'aw_' followed by exactly 12 hexadecimal characters (0-9, a-f). Example: 'aw_abc123def456'`); + safeWarning(`Invalid temporary ID format for parent: '${message.parent}'. Temporary IDs must be in format 'aw_' followed by exactly 12 hexadecimal characters (0-9, a-f). Example: 'aw_abc123def456'`); } else { // It's a real issue number const parsed = parseInt(parentWithoutHash, 10); if (!isNaN(parsed)) { effectiveParentIssueNumber = parsed; } else { - core.warning(`Invalid parent value: ${message.parent}. Expected either a valid temporary ID (format: aw_XXXXXXXXXXXX where X is a hex digit) or a numeric issue number.`); + safeWarning(`Invalid parent value: ${message.parent}. Expected either a valid temporary ID (format: aw_XXXXXXXXXXXX where X is a hex digit) or a numeric issue number.`); } } } @@ -433,12 +434,12 @@ async function main(config = {}) { bodyLines.push(""); const body = bodyLines.join("\n").trim(); - core.info(`Creating issue in ${qualifiedItemRepo} with title: ${title}`); - core.info(`Labels: ${labels.join(", ")}`); + safeInfo(`Creating issue in ${qualifiedItemRepo} with title: ${title}`); + safeInfo(`Labels: ${labels.join(", ")}`); if (assignees.length > 0) { core.info(`Assignees: ${assignees.join(", ")}`); } - core.info(`Body length: ${body.length}`); + safeInfo(`Body length: ${body.length}`); try { const { data: issue } = await github.rest.issues.create({ @@ -474,7 +475,7 @@ async function main(config = {}) { } } catch (error) { // Log error but don't fail the workflow - core.warning(`Failed to close older issues: ${getErrorMessage(error)}`); + safeWarning(`Failed to close older issues: ${getErrorMessage(error)}`); } } else { core.warning("Close older issues enabled but GH_AW_WORKFLOW_ID environment variable not set - skipping"); @@ -578,8 +579,8 @@ async function main(config = {}) { core.info("✓ Successfully linked issue #" + issue.number + " as sub-issue of #" + effectiveParentIssueNumber); } catch (error) { - core.info(`Warning: Could not link sub-issue to parent: ${getErrorMessage(error)}`); - core.info(`Error details: ${error instanceof Error ? error.stack : String(error)}`); + safeInfo(`Warning: Could not link sub-issue to parent: ${getErrorMessage(error)}`); + safeInfo(`Error details: ${error instanceof Error ? error.stack : String(error)}`); // Fallback: add a comment if sub-issue linking fails try { core.info(`Attempting fallback: adding comment to parent issue #${effectiveParentIssueNumber}...`); @@ -591,7 +592,7 @@ async function main(config = {}) { }); core.info("✓ Added comment to parent issue #" + effectiveParentIssueNumber + " (sub-issue linking not available)"); } catch (commentError) { - core.info(`Warning: Could not add comment to parent issue: ${commentError instanceof Error ? commentError.message : String(commentError)}`); + safeInfo(`Warning: Could not add comment to parent issue: ${commentError instanceof Error ? commentError.message : String(commentError)}`); } } } else if (effectiveParentIssueNumber && effectiveParentRepo !== qualifiedItemRepo) { @@ -610,14 +611,14 @@ async function main(config = {}) { } catch (error) { const errorMessage = getErrorMessage(error); if (errorMessage.includes("Issues has been disabled in this repository")) { - core.info(`⚠ Cannot create issue "${title}" in ${qualifiedItemRepo}: Issues are disabled for this repository`); + safeInfo(`⚠ Cannot create issue "${title}" in ${qualifiedItemRepo}: Issues are disabled for this repository`); core.info("Consider enabling issues in repository settings if you want to create issues automatically"); return { success: false, error: "Issues disabled for repository", }; } - core.error(`✗ Failed to create issue "${title}" in ${qualifiedItemRepo}: ${errorMessage}`); + safeError(`✗ Failed to create issue "${title}" in ${qualifiedItemRepo}: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/create_missing_data_issue.cjs b/actions/setup/js/create_missing_data_issue.cjs index 952c98cd773..f816594faa1 100644 --- a/actions/setup/js/create_missing_data_issue.cjs +++ b/actions/setup/js/create_missing_data_issue.cjs @@ -9,6 +9,7 @@ const fs = require("fs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "create_missing_data_issue"; @@ -24,9 +25,9 @@ async function main(config = {}) { const envLabels = config.labels ? (Array.isArray(config.labels) ? config.labels : config.labels.split(",")).map(label => String(label).trim()).filter(label => label) : []; const maxCount = config.max || 1; // Default to 1 to create only one issue per workflow run - core.info(`Title prefix: ${titlePrefix}`); + safeInfo(`Title prefix: ${titlePrefix}`); if (envLabels.length > 0) { - core.info(`Default labels: ${envLabels.join(", ")}`); + safeInfo(`Default labels: ${envLabels.join(", ")}`); } core.info(`Max count: ${maxCount}`); @@ -51,7 +52,7 @@ async function main(config = {}) { // Create issue title const issueTitle = `${titlePrefix} ${workflowName}`; - core.info(`Checking for existing issue with title: "${issueTitle}"`); + safeInfo(`Checking for existing issue with title: "${issueTitle}"`); // Search for existing open issue with this title const searchQuery = `repo:${owner}/${repo} is:issue is:open in:title "${issueTitle}"`; @@ -163,7 +164,7 @@ async function main(config = {}) { }; } } catch (error) { - core.warning(`Failed to create or update issue: ${getErrorMessage(error)}`); + safeWarning(`Failed to create or update issue: ${getErrorMessage(error)}`); return { success: false, error: getErrorMessage(error), diff --git a/actions/setup/js/create_missing_tool_issue.cjs b/actions/setup/js/create_missing_tool_issue.cjs index a5fcc6c3dbe..57809041736 100644 --- a/actions/setup/js/create_missing_tool_issue.cjs +++ b/actions/setup/js/create_missing_tool_issue.cjs @@ -9,6 +9,7 @@ const fs = require("fs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "create_missing_tool_issue"; @@ -24,9 +25,9 @@ async function main(config = {}) { const envLabels = config.labels ? (Array.isArray(config.labels) ? config.labels : config.labels.split(",")).map(label => String(label).trim()).filter(label => label) : []; const maxCount = config.max || 1; // Default to 1 to create only one issue per workflow run - core.info(`Title prefix: ${titlePrefix}`); + safeInfo(`Title prefix: ${titlePrefix}`); if (envLabels.length > 0) { - core.info(`Default labels: ${envLabels.join(", ")}`); + safeInfo(`Default labels: ${envLabels.join(", ")}`); } core.info(`Max count: ${maxCount}`); @@ -51,7 +52,7 @@ async function main(config = {}) { // Create issue title const issueTitle = `${titlePrefix} ${workflowName}`; - core.info(`Checking for existing issue with title: "${issueTitle}"`); + safeInfo(`Checking for existing issue with title: "${issueTitle}"`); // Search for existing open issue with this title const searchQuery = `repo:${owner}/${repo} is:issue is:open in:title "${issueTitle}"`; @@ -157,7 +158,7 @@ async function main(config = {}) { }; } } catch (error) { - core.warning(`Failed to create or update issue: ${getErrorMessage(error)}`); + safeWarning(`Failed to create or update issue: ${getErrorMessage(error)}`); return { success: false, error: getErrorMessage(error), diff --git a/actions/setup/js/create_pr_review_comment.cjs b/actions/setup/js/create_pr_review_comment.cjs index 06e9cb3522c..c4c8f096638 100644 --- a/actions/setup/js/create_pr_review_comment.cjs +++ b/actions/setup/js/create_pr_review_comment.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { generateFooter } = require("./generate_footer.cjs"); const { getRepositoryUrl } = require("./get_repository_url.cjs"); @@ -64,7 +65,7 @@ async function main(config = {}) { const commentItem = message; - core.info(`Processing create_pull_request_review_comment: path=${commentItem.path}, line=${commentItem.line}, bodyLength=${commentItem.body?.length || 0}`); + safeInfo(`Processing create_pull_request_review_comment: path=${commentItem.path}, line=${commentItem.line}, bodyLength=${commentItem.body?.length || 0}`); // Resolve and validate target repository const repoResult = resolveAndValidateRepo(commentItem, defaultTargetRepo, allowedRepos, "PR review comment"); @@ -186,7 +187,7 @@ async function main(config = {}) { pullRequest = fullPR; core.info(`Fetched full pull request details for PR #${pullRequestNumber} in ${itemRepo}`); } catch (error) { - core.warning(`Failed to fetch pull request details for PR #${pullRequestNumber}: ${getErrorMessage(error)}`); + safeWarning(`Failed to fetch pull request details for PR #${pullRequestNumber}: ${getErrorMessage(error)}`); return { success: false, error: `Failed to fetch pull request details: ${getErrorMessage(error)}`, @@ -250,7 +251,7 @@ async function main(config = {}) { body += generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber); core.info(`Creating review comment on PR #${pullRequestNumber} in ${itemRepo} at ${commentItem.path}:${line}${startLine ? ` (lines ${startLine}-${line})` : ""} [${side}]`); - core.info(`Comment content length: ${body.length}`); + safeInfo(`Comment content length: ${body.length}`); try { // Prepare the request parameters @@ -286,7 +287,7 @@ async function main(config = {}) { repo: itemRepo, }; } catch (error) { - core.error(`✗ Failed to create review comment: ${getErrorMessage(error)}`); + safeError(`✗ Failed to create review comment: ${getErrorMessage(error)}`); return { success: false, error: getErrorMessage(error), diff --git a/actions/setup/js/create_project.cjs b/actions/setup/js/create_project.cjs index dcf283a51ae..81c37db2d98 100644 --- a/actions/setup/js/create_project.cjs +++ b/actions/setup/js/create_project.cjs @@ -10,9 +10,10 @@ const { normalizeTemporaryId, isTemporaryId } = require("./temporary_id.cjs"); * @param {Error & { errors?: Array<{ type?: string, message: string, path?: unknown, locations?: unknown }>, request?: unknown, data?: unknown }} error - GraphQL error * @param {string} operation - Operation description */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); function logGraphQLError(error, operation) { core.info(`GraphQL Error during: ${operation}`); - core.info(`Message: ${getErrorMessage(error)}`); + safeInfo(`Message: ${getErrorMessage(error)}`); const errorList = Array.isArray(error.errors) ? error.errors : []; const hasInsufficientScopes = errorList.some(e => e?.type === "INSUFFICIENT_SCOPES"); @@ -27,17 +28,17 @@ function logGraphQLError(error, operation) { } if (error.errors) { - core.info(`Errors array (${error.errors.length} error(s)):`); + safeInfo(`Errors array (${error.errors.length} error(s)):`); error.errors.forEach((err, idx) => { - core.info(` [${idx + 1}] ${err.message}`); - if (err.type) core.info(` Type: ${err.type}`); - if (err.path) core.info(` Path: ${JSON.stringify(err.path)}`); - if (err.locations) core.info(` Locations: ${JSON.stringify(err.locations)}`); + safeInfo(` [${idx + 1}] ${err.message}`); + if (err.type) safeInfo(` Type: ${err.type}`); + if (err.path) safeInfo(` Path: ${JSON.stringify(err.path)}`); + if (err.locations) safeInfo(` Locations: ${JSON.stringify(err.locations)}`); }); } - if (error.request) core.info(`Request: ${JSON.stringify(error.request, null, 2)}`); - if (error.data) core.info(`Response data: ${JSON.stringify(error.data, null, 2)}`); + if (error.request) safeInfo(`Request: ${JSON.stringify(error.request, null, 2)}`); + if (error.data) safeInfo(`Response data: ${JSON.stringify(error.data, null, 2)}`); } /** @@ -77,7 +78,7 @@ async function getOwnerId(ownerType, ownerLogin) { * @returns {Promise<{ projectId: string, projectNumber: number, projectTitle: string, projectUrl: string, itemId?: string }>} Created project info */ async function createProjectV2(ownerId, title) { - core.info(`Creating project with title: "${title}"`); + safeInfo(`Creating project with title: "${title}"`); const result = await github.graphql( `mutation($ownerId: ID!, $title: String!) { @@ -94,7 +95,7 @@ async function createProjectV2(ownerId, title) { ); const project = result.createProjectV2.projectV2; - core.info(`✓ Created project #${project.number}: ${project.title}`); + safeInfo(`✓ Created project #${project.number}: ${project.title}`); core.info(` URL: ${project.url}`); return { @@ -271,14 +272,14 @@ async function createProjectView(projectUrl, viewConfig) { ...(visibleFields ? { visible_fields: visibleFields } : {}), }; - core.info(`Creating project view: ${name} (${layout})...`); + safeInfo(`Creating project view: ${name} (${layout})...`); const response = await github.request(route, params); const created = response?.data; if (created?.id) { - core.info(`✓ Created view: ${name} (ID: ${created.id})`); + safeInfo(`✓ Created view: ${name} (ID: ${created.id})`); } else { - core.info(`✓ Created view: ${name}`); + safeInfo(`✓ Created view: ${name}`); } } @@ -309,7 +310,7 @@ async function main(config = {}, githubClient = null) { } core.info(`Max count: ${maxCount}`); if (config.title_prefix) { - core.info(`Title prefix: ${titlePrefix}`); + safeInfo(`Title prefix: ${titlePrefix}`); } if (configuredViews.length > 0) { core.info(`Found ${configuredViews.length} configured view(s) in frontmatter`); @@ -377,11 +378,11 @@ async function main(config = {}, githubClient = null) { if (issueTitle) { // Use the issue title with the configured prefix title = `${titlePrefix}: ${issueTitle}`; - core.info(`Generated title from issue: "${title}"`); + safeInfo(`Generated title from issue: "${title}"`); } else if (issueNumber) { // Fallback to issue number if no title is available title = `${titlePrefix} #${issueNumber}`; - core.info(`Generated title from issue number: "${title}"`); + safeInfo(`Generated title from issue number: "${title}"`); } else { throw new Error("Missing required field 'title' in create_project call and unable to generate from context"); } @@ -396,7 +397,7 @@ async function main(config = {}, githubClient = null) { // Determine owner type (org or user) const ownerType = owner_type || "org"; // Default to org if not specified - core.info(`Creating project "${title}" for ${ownerType}/${targetOwner}`); + safeInfo(`Creating project "${title}" for ${ownerType}/${targetOwner}`); // Get owner ID const ownerId = await getOwnerId(ownerType, targetOwner); @@ -435,11 +436,11 @@ async function main(config = {}, githubClient = null) { const viewConfig = configuredViews[i]; try { await createProjectView(projectInfo.projectUrl, viewConfig); - core.info(`✓ Created view ${i + 1}/${configuredViews.length}: ${viewConfig.name} (${viewConfig.layout})`); + safeInfo(`✓ Created view ${i + 1}/${configuredViews.length}: ${viewConfig.name} (${viewConfig.layout})`); } catch (err) { // prettier-ignore const error = /** @type {Error & { errors?: Array<{ type?: string, message: string, path?: unknown, locations?: unknown }>, request?: unknown, data?: unknown }} */ (err); - core.error(`Failed to create configured view ${i + 1}: ${viewConfig.name}`); + safeError(`Failed to create configured view ${i + 1}: ${viewConfig.name}`); logGraphQLError(error, `Creating configured view: ${viewConfig.name}`); } } diff --git a/actions/setup/js/create_project_status_update.cjs b/actions/setup/js/create_project_status_update.cjs index b0a4e10479a..7d5a4aecf57 100644 --- a/actions/setup/js/create_project_status_update.cjs +++ b/actions/setup/js/create_project_status_update.cjs @@ -7,6 +7,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "create_project_status_update"; @@ -18,7 +19,7 @@ const HANDLER_TYPE = "create_project_status_update"; */ function logGraphQLError(error, operation) { core.info(`GraphQL Error during: ${operation}`); - core.info(`Message: ${getErrorMessage(error)}`); + safeInfo(`Message: ${getErrorMessage(error)}`); const errorList = Array.isArray(error.errors) ? error.errors : []; const hasInsufficientScopes = errorList.some(e => e?.type === "INSUFFICIENT_SCOPES"); @@ -35,17 +36,17 @@ function logGraphQLError(error, operation) { } if (error.errors) { - core.info(`Errors array (${error.errors.length} error(s)):`); + safeInfo(`Errors array (${error.errors.length} error(s)):`); error.errors.forEach((err, idx) => { - core.info(` [${idx + 1}] ${err.message}`); - if (err.type) core.info(` Type: ${err.type}`); - if (err.path) core.info(` Path: ${JSON.stringify(err.path)}`); - if (err.locations) core.info(` Locations: ${JSON.stringify(err.locations)}`); + safeInfo(` [${idx + 1}] ${err.message}`); + if (err.type) safeInfo(` Type: ${err.type}`); + if (err.path) safeInfo(` Path: ${JSON.stringify(err.path)}`); + if (err.locations) safeInfo(` Locations: ${JSON.stringify(err.locations)}`); }); } - if (error.request) core.info(`Request: ${JSON.stringify(error.request, null, 2)}`); - if (error.data) core.info(`Response data: ${JSON.stringify(error.data, null, 2)}`); + if (error.request) safeInfo(`Request: ${JSON.stringify(error.request, null, 2)}`); + if (error.data) safeInfo(`Response data: ${JSON.stringify(error.data, null, 2)}`); } /** @@ -197,7 +198,7 @@ async function resolveProjectV2(projectInfo, projectNumberInt) { // If the query succeeded but returned null, fall back to list search core.warning(`Direct projectV2(number) query returned null; falling back to projectsV2 list search`); } catch (error) { - core.warning(`Direct projectV2(number) query failed; falling back to projectsV2 list search: ${getErrorMessage(error)}`); + safeWarning(`Direct projectV2(number) query failed; falling back to projectsV2 list search: ${getErrorMessage(error)}`); } // Wrap fallback query in try-catch to handle transient API errors gracefully @@ -361,7 +362,7 @@ async function main(config = {}, githubClient = null) { const body = String(output.body); core.info(`Creating status update: ${status} (${startDate} → ${targetDate})`); - core.info(`Body preview: ${body.substring(0, 100)}${body.length > 100 ? "..." : ""}`); + safeInfo(`Body preview: ${body.substring(0, 100)}${body.length > 100 ? "..." : ""}`); // Create the status update using GraphQL mutation const mutation = ` @@ -428,7 +429,7 @@ async function main(config = {}, githubClient = null) { } catch (err) { // prettier-ignore const error = /** @type {Error & { errors?: Array<{ type?: string, message: string, path?: unknown, locations?: unknown }>, request?: unknown, data?: unknown }} */ (err); - core.error(`Failed to create project status update: ${getErrorMessage(error)}`); + safeError(`Failed to create project status update: ${getErrorMessage(error)}`); logGraphQLError(error, "Creating project status update"); return { diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index b3e2e47d3df..517f079484d 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -19,6 +19,7 @@ const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "create_pull_request"; @@ -95,10 +96,10 @@ async function main(config = {}) { core.info(`Allowed repos: ${Array.from(allowedRepos).join(", ")}`); } if (envLabels.length > 0) { - core.info(`Default labels: ${envLabels.join(", ")}`); + safeInfo(`Default labels: ${envLabels.join(", ")}`); } if (titlePrefix) { - core.info(`Title prefix: ${titlePrefix}`); + safeInfo(`Title prefix: ${titlePrefix}`); } core.info(`Draft default: ${draftDefault}`); core.info(`If no changes: ${ifNoChanges}`); @@ -133,7 +134,7 @@ async function main(config = {}) { const pullRequestItem = message; - core.info(`Processing create_pull_request: title=${pullRequestItem.title || "No title"}, bodyLength=${pullRequestItem.body?.length || 0}`); + safeInfo(`Processing create_pull_request: title=${pullRequestItem.title || "No title"}, bodyLength=${pullRequestItem.body?.length || 0}`); // Resolve and validate target repository const repoResult = resolveAndValidateRepo(pullRequestItem, defaultTargetRepo, allowedRepos, "pull request"); @@ -407,10 +408,10 @@ async function main(config = {}) { // Use draft setting from message if provided, otherwise use config default const draft = pullRequestItem.draft !== undefined ? pullRequestItem.draft : draftDefault; - core.info(`Creating pull request with title: ${title}`); - core.info(`Labels: ${JSON.stringify(labels)}`); + safeInfo(`Creating pull request with title: ${title}`); + safeInfo(`Labels: ${JSON.stringify(labels)}`); core.info(`Draft: ${draft}`); - core.info(`Body length: ${body.length}`); + safeInfo(`Body length: ${body.length}`); const randomHex = crypto.randomBytes(8).toString("hex"); // Use branch name from JSONL if provided, otherwise generate unique branch name @@ -420,10 +421,10 @@ async function main(config = {}) { branchName = `${workflowId}-${randomHex}`; } else { branchName = `${branchName}-${randomHex}`; - core.info(`Using branch name from JSONL with added salt: ${branchName}`); + safeInfo(`Using branch name from JSONL with added salt: ${branchName}`); } - core.info(`Generated branch name: ${branchName}`); + safeInfo(`Generated branch name: ${branchName}`); core.info(`Base branch: ${baseBranch}`); // Create a new branch using git CLI, ensuring it's based on the correct base branch @@ -445,9 +446,9 @@ async function main(config = {}) { } // Handle branch creation/checkout - core.info(`Branch should not exist locally, creating new branch from base: ${branchName}`); + safeInfo(`Branch should not exist locally, creating new branch from base: ${branchName}`); await exec.exec(`git checkout -b ${branchName}`); - core.info(`Created new branch from base: ${branchName}`); + safeInfo(`Created new branch from base: ${branchName}`); // Apply the patch using git CLI (skip if empty) if (!isEmpty) { @@ -466,7 +467,7 @@ async function main(config = {}) { await exec.exec("git am /tmp/gh-aw/aw.patch"); core.info("Patch applied successfully"); } catch (patchError) { - core.error(`Failed to apply patch: ${patchError instanceof Error ? patchError.message : String(patchError)}`); + safeError(`Failed to apply patch: ${patchError instanceof Error ? patchError.message : String(patchError)}`); // Investigate why the patch failed by logging git status and the failed patch try { @@ -482,7 +483,7 @@ async function main(config = {}) { core.info("Failed patch content:"); core.info(patchResult.stdout); } catch (investigateError) { - core.warning(`Failed to investigate patch failure: ${investigateError instanceof Error ? investigateError.message : String(investigateError)}`); + safeWarning(`Failed to investigate patch failure: ${investigateError instanceof Error ? investigateError.message : String(investigateError)}`); } return { success: false, error: "Failed to apply patch" }; @@ -498,24 +499,24 @@ async function main(config = {}) { remoteBranchExists = true; } } catch (checkError) { - core.info(`Remote branch check failed (non-fatal): ${checkError instanceof Error ? checkError.message : String(checkError)}`); + safeInfo(`Remote branch check failed (non-fatal): ${checkError instanceof Error ? checkError.message : String(checkError)}`); } if (remoteBranchExists) { - core.warning(`Remote branch ${branchName} already exists - appending random suffix`); + safeWarning(`Remote branch ${branchName} already exists - appending random suffix`); const extraHex = crypto.randomBytes(4).toString("hex"); const oldBranch = branchName; branchName = `${branchName}-${extraHex}`; // Rename local branch await exec.exec(`git branch -m ${oldBranch} ${branchName}`); - core.info(`Renamed branch to ${branchName}`); + safeInfo(`Renamed branch to ${branchName}`); } await exec.exec(`git push origin ${branchName}`); core.info("Changes pushed to branch"); } catch (pushError) { // Push failed - create fallback issue instead of PR - core.error(`Git push failed: ${pushError instanceof Error ? pushError.message : String(pushError)}`); + safeError(`Git push failed: ${pushError instanceof Error ? pushError.message : String(pushError)}`); core.warning("Git push operation failed - creating fallback issue instead of pull request"); const runId = context.runId; @@ -619,17 +620,17 @@ ${patchPreview}`; remoteBranchExists = true; } } catch (checkError) { - core.info(`Remote branch check failed (non-fatal): ${checkError instanceof Error ? checkError.message : String(checkError)}`); + safeInfo(`Remote branch check failed (non-fatal): ${checkError instanceof Error ? checkError.message : String(checkError)}`); } if (remoteBranchExists) { - core.warning(`Remote branch ${branchName} already exists - appending random suffix`); + safeWarning(`Remote branch ${branchName} already exists - appending random suffix`); const extraHex = crypto.randomBytes(4).toString("hex"); const oldBranch = branchName; branchName = `${branchName}-${extraHex}`; // Rename local branch await exec.exec(`git branch -m ${oldBranch} ${branchName}`); - core.info(`Renamed branch to ${branchName}`); + safeInfo(`Renamed branch to ${branchName}`); } await exec.exec(`git push origin ${branchName}`); @@ -684,7 +685,7 @@ ${patchPreview}`; issue_number: pullRequest.number, labels: labels, }); - core.info(`Added labels to pull request: ${JSON.stringify(labels)}`); + safeInfo(`Added labels to pull request: ${JSON.stringify(labels)}`); } // Enable auto-merge if configured @@ -704,7 +705,7 @@ ${patchPreview}`; ); core.info(`Enabled auto-merge for pull request #${pullRequest.number}`); } catch (autoMergeError) { - core.warning(`Failed to enable auto-merge for PR #${pullRequest.number}: ${autoMergeError instanceof Error ? autoMergeError.message : String(autoMergeError)}`); + safeWarning(`Failed to enable auto-merge for PR #${pullRequest.number}: ${autoMergeError instanceof Error ? autoMergeError.message : String(autoMergeError)}`); } } @@ -735,7 +736,7 @@ ${patchPreview}`; }; } catch (prError) { const errorMessage = prError instanceof Error ? prError.message : String(prError); - core.warning(`Failed to create pull request: ${errorMessage}`); + safeWarning(`Failed to create pull request: ${errorMessage}`); // Check if the error is the specific "GitHub actions is not permitted to create or approve pull requests" error if (errorMessage.includes("GitHub Actions is not permitted to create or approve pull requests")) { diff --git a/actions/setup/js/determine_automatic_lockdown.cjs b/actions/setup/js/determine_automatic_lockdown.cjs index c75f34c1474..4aa3f048db7 100644 --- a/actions/setup/js/determine_automatic_lockdown.cjs +++ b/actions/setup/js/determine_automatic_lockdown.cjs @@ -19,6 +19,7 @@ * @param {any} core - GitHub Actions core library * @returns {Promise} */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function determineAutomaticLockdown(github, context, core) { try { core.info("Determining automatic lockdown mode for GitHub MCP server"); @@ -54,7 +55,7 @@ async function determineAutomaticLockdown(github, context, core) { } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - core.error(`Failed to determine automatic lockdown mode: ${errorMessage}`); + safeError(`Failed to determine automatic lockdown mode: ${errorMessage}`); // Default to lockdown mode for safety core.setOutput("lockdown", "true"); core.setOutput("visibility", "unknown"); diff --git a/actions/setup/js/dispatch_workflow.cjs b/actions/setup/js/dispatch_workflow.cjs index 625df9469be..52d24e1b972 100644 --- a/actions/setup/js/dispatch_workflow.cjs +++ b/actions/setup/js/dispatch_workflow.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "dispatch_workflow"; @@ -51,7 +52,7 @@ async function main(config = {}) { }); return `refs/heads/${repoData.default_branch}`; } catch (error) { - core.warning(`Failed to fetch default branch: ${getErrorMessage(error)}`); + safeWarning(`Failed to fetch default branch: ${getErrorMessage(error)}`); return "refs/heads/main"; } }; @@ -123,7 +124,7 @@ async function main(config = {}) { } } - core.info(`Dispatching workflow: ${workflowName}`); + safeInfo(`Dispatching workflow: ${workflowName}`); // Prepare inputs - convert all values to strings as required by workflow_dispatch /** @type {Record} */ @@ -174,7 +175,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to dispatch workflow "${workflowName}": ${errorMessage}`); + safeError(`Failed to dispatch workflow "${workflowName}": ${errorMessage}`); return { success: false, diff --git a/actions/setup/js/display_file_helpers.cjs b/actions/setup/js/display_file_helpers.cjs index cf96906de89..37cf1329afc 100644 --- a/actions/setup/js/display_file_helpers.cjs +++ b/actions/setup/js/display_file_helpers.cjs @@ -7,6 +7,7 @@ * This module provides helper functions for displaying file contents * in GitHub Actions logs with collapsible groups and proper formatting. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const fs = require("fs"); @@ -21,19 +22,19 @@ function displayFileContent(filePath, fileName, maxBytes = 64 * 1024) { const stats = fs.statSync(filePath); if (stats.isDirectory()) { - core.info(` ${fileName}/ (directory)`); + safeInfo(` ${fileName}/ (directory)`); return; } // Handle empty files if (stats.size === 0) { - core.info(` ${fileName} (empty file)`); + safeInfo(` ${fileName} (empty file)`); return; } // Handle files too large to read if (stats.size >= 1024 * 1024) { - core.info(` ${fileName} (file too large to display, ${stats.size} bytes)`); + safeInfo(` ${fileName} (file too large to display, ${stats.size} bytes)`); return; } @@ -43,7 +44,7 @@ function displayFileContent(filePath, fileName, maxBytes = 64 * 1024) { const shouldDisplayContent = displayExtensions.includes(fileExtension); if (!shouldDisplayContent) { - core.info(` ${fileName} (content not displayed for ${fileExtension} files)`); + safeInfo(` ${fileName} (content not displayed for ${fileExtension} files)`); return; } @@ -56,16 +57,16 @@ function displayFileContent(filePath, fileName, maxBytes = 64 * 1024) { core.startGroup(`${fileName} (${stats.size} bytes)`); const lines = contentToDisplay.split("\n"); for (const line of lines) { - core.info(line); + safeInfo(line); } if (wasTruncated) { core.info(`...`); - core.info(`(truncated, showing first ${maxBytes} bytes of ${content.length} total)`); + safeInfo(`(truncated, showing first ${maxBytes} bytes of ${content.length} total)`); } core.endGroup(); } catch (/** @type {unknown} */ error) { const errorMessage = error instanceof Error ? error.message : String(error); - core.warning(`Could not display file ${fileName}: ${errorMessage}`); + safeWarning(`Could not display file ${fileName}: ${errorMessage}`); } } @@ -98,7 +99,7 @@ function displayDirectory(dirPath, maxBytes = 64 * 1024) { } } catch (/** @type {unknown} */ error) { const errorMessage = error instanceof Error ? error.message : String(error); - core.error(`Error reading directory ${dirPath}: ${errorMessage}`); + safeError(`Error reading directory ${dirPath}: ${errorMessage}`); } core.endGroup(); diff --git a/actions/setup/js/display_file_helpers.test.cjs b/actions/setup/js/display_file_helpers.test.cjs index b869bd1f18b..af61d9ff71d 100644 --- a/actions/setup/js/display_file_helpers.test.cjs +++ b/actions/setup/js/display_file_helpers.test.cjs @@ -324,6 +324,62 @@ describe("display_file_helpers", () => { fs.rmSync(tmpDir, { recursive: true, force: true }); } }); + + test("neutralizes workflow commands in file content", () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "display-test-")); + const filePath = path.join(tmpDir, "malicious.log"); + // File content with workflow commands at line start + fs.writeFileSync(filePath, "Normal log entry\n::set-output name=hack::value\nAnother line\n::warning::injected"); + + const mockCore = { info: vi.fn(), startGroup: vi.fn(), endGroup: vi.fn(), warning: vi.fn() }; + global.core = mockCore; + + try { + displayFileContent(filePath, "malicious.log"); + + // Collect all logged lines + const loggedLines = mockCore.info.mock.calls.map(call => call[0]); + + // Verify workflow commands were neutralized + // Line starting with :: should have zero-width space inserted + expect(loggedLines).toContain("Normal log entry"); + expect(loggedLines).toContain(":\u200B:set-output name=hack::value"); + expect(loggedLines).toContain("Another line"); + expect(loggedLines).toContain(":\u200B:warning::injected"); + + // Verify original commands are NOT present (would be security issue) + expect(loggedLines).not.toContain("::set-output name=hack::value"); + expect(loggedLines).not.toContain("::warning::injected"); + } finally { + delete global.core; + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + }); + + test("preserves :: in middle of lines when displaying file content", () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "display-test-")); + const filePath = path.join(tmpDir, "code.log"); + // File content with :: in middle of lines (not workflow commands) + fs.writeFileSync(filePath, "IPv6 address ::1\nC++ std::vector\nTimestamp 12:30:45"); + + const mockCore = { info: vi.fn(), startGroup: vi.fn(), endGroup: vi.fn(), warning: vi.fn() }; + global.core = mockCore; + + try { + displayFileContent(filePath, "code.log"); + + // Collect all logged lines + const loggedLines = mockCore.info.mock.calls.map(call => call[0]); + + // Verify :: in middle of line is preserved + expect(loggedLines).toContain("IPv6 address ::1"); + expect(loggedLines).toContain("C++ std::vector"); + expect(loggedLines).toContain("Timestamp 12:30:45"); + } finally { + delete global.core; + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + }); }); describe("displayDirectory", () => { diff --git a/actions/setup/js/error_recovery.cjs b/actions/setup/js/error_recovery.cjs index 14da05572e1..db9f825caed 100644 --- a/actions/setup/js/error_recovery.cjs +++ b/actions/setup/js/error_recovery.cjs @@ -5,6 +5,7 @@ * Error recovery utilities for safe output operations * Provides retry logic with exponential backoff for transient failures */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -86,14 +87,14 @@ async function withRetry(operation, config = {}, operationName = "operation") { for (let attempt = 0; attempt <= fullConfig.maxRetries; attempt++) { try { if (attempt > 0) { - core.info(`Retry attempt ${attempt}/${fullConfig.maxRetries} for ${operationName} after ${delay}ms delay`); + safeInfo(`Retry attempt ${attempt}/${fullConfig.maxRetries} for ${operationName} after ${delay}ms delay`); await sleep(delay); } const result = await operation(); if (attempt > 0) { - core.info(`✓ ${operationName} succeeded on retry attempt ${attempt}`); + safeInfo(`✓ ${operationName} succeeded on retry attempt ${attempt}`); } return result; @@ -103,7 +104,7 @@ async function withRetry(operation, config = {}, operationName = "operation") { // Check if this error should be retried if (!fullConfig.shouldRetry(error)) { - core.debug(`${operationName} failed with non-retryable error: ${errorMsg}`); + safeDebug(`${operationName} failed with non-retryable error: ${errorMsg}`); throw enhanceError(error, { operation: operationName, attempt: attempt + 1, @@ -114,7 +115,7 @@ async function withRetry(operation, config = {}, operationName = "operation") { // If this was the last attempt, throw the enhanced error if (attempt === fullConfig.maxRetries) { - core.warning(`${operationName} failed after ${fullConfig.maxRetries} retry attempts: ${errorMsg}`); + safeWarning(`${operationName} failed after ${fullConfig.maxRetries} retry attempts: ${errorMsg}`); throw enhanceError(error, { operation: operationName, attempt: attempt + 1, @@ -125,7 +126,7 @@ async function withRetry(operation, config = {}, operationName = "operation") { } // Log the retry attempt - core.warning(`${operationName} failed (attempt ${attempt + 1}/${fullConfig.maxRetries + 1}): ${errorMsg}`); + safeWarning(`${operationName} failed (attempt ${attempt + 1}/${fullConfig.maxRetries + 1}): ${errorMsg}`); // Calculate next delay with exponential backoff delay = Math.min(delay * fullConfig.backoffMultiplier, fullConfig.maxDelayMs); diff --git a/actions/setup/js/expired_entity_cleanup_helpers.cjs b/actions/setup/js/expired_entity_cleanup_helpers.cjs index 1cd0b468037..2f70fcfe382 100644 --- a/actions/setup/js/expired_entity_cleanup_helpers.cjs +++ b/actions/setup/js/expired_entity_cleanup_helpers.cjs @@ -4,6 +4,7 @@ const { extractExpirationDate } = require("./ephemerals.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const DEFAULT_MAX_UPDATES_PER_RUN = 100; const DEFAULT_GRAPHQL_DELAY_MS = 500; @@ -40,17 +41,17 @@ function categorizeByExpiration(entities, { entityLabel }) { const notExpired = []; for (const entity of entities) { - core.info(`Processing ${entityLabel} #${entity.number}: ${entity.title}`); + safeInfo(`Processing ${entityLabel} #${entity.number}: ${entity.title}`); if (!validateCreationDate(entity.createdAt)) { - core.warning(` ${entityLabel} #${entity.number} has invalid creation date: ${entity.createdAt}, skipping`); + safeWarning(` ${entityLabel} #${entity.number} has invalid creation date: ${entity.createdAt}, skipping`); continue; } core.info(` Creation date: ${entity.createdAt}`); const expirationDate = extractExpirationDate(entity.body); if (!expirationDate) { - core.warning(` ${entityLabel} #${entity.number} has invalid expiration date format, skipping`); + safeWarning(` ${entityLabel} #${entity.number} has invalid expiration date format, skipping`); continue; } core.info(` Expiration date: ${expirationDate.toISOString()}`); @@ -63,13 +64,13 @@ function categorizeByExpiration(entities, { entityLabel }) { if (isExpired) { const daysSinceExpiration = Math.abs(daysUntilExpiration); const hoursSinceExpiration = Math.abs(hoursUntilExpiration); - core.info(` ✓ ${entityLabel} #${entity.number} is EXPIRED (expired ${daysSinceExpiration} days, ${hoursSinceExpiration % 24} hours ago)`); + safeInfo(` ✓ ${entityLabel} #${entity.number} is EXPIRED (expired ${daysSinceExpiration} days, ${hoursSinceExpiration % 24} hours ago)`); expired.push({ ...entity, expirationDate, }); } else { - core.info(` ✗ ${entityLabel} #${entity.number} is NOT expired (expires in ${daysUntilExpiration} days, ${hoursUntilExpiration % 24} hours)`); + safeInfo(` ✗ ${entityLabel} #${entity.number} is NOT expired (expires in ${daysUntilExpiration} days, ${hoursUntilExpiration % 24} hours)`); notExpired.push({ ...entity, expirationDate, @@ -96,11 +97,11 @@ async function processExpiredEntities(expiredEntities, { entityLabel, maxPerRun const entitiesToProcess = expiredEntities.slice(0, maxPerRun); if (expiredEntities.length > maxPerRun) { - core.warning(`Found ${expiredEntities.length} expired ${entityLabel.toLowerCase()}s, but only closing the first ${maxPerRun}`); - core.info(`Remaining ${expiredEntities.length - maxPerRun} expired ${entityLabel.toLowerCase()}s will be closed in the next run`); + safeWarning(`Found ${expiredEntities.length} expired ${entityLabel.toLowerCase()}s, but only closing the first ${maxPerRun}`); + safeInfo(`Remaining ${expiredEntities.length - maxPerRun} expired ${entityLabel.toLowerCase()}s will be closed in the next run`); } - core.info(`Preparing to close ${entitiesToProcess.length} ${entityLabel.toLowerCase()}(s)`); + safeInfo(`Preparing to close ${entitiesToProcess.length} ${entityLabel.toLowerCase()}(s)`); const closed = []; const failed = []; @@ -108,7 +109,7 @@ async function processExpiredEntities(expiredEntities, { entityLabel, maxPerRun for (let i = 0; i < entitiesToProcess.length; i++) { const entity = entitiesToProcess[i]; - core.info(`[${i + 1}/${entitiesToProcess.length}] Processing ${entityLabel.toLowerCase()} #${entity.number}: ${entity.url}`); + safeInfo(`[${i + 1}/${entitiesToProcess.length}] Processing ${entityLabel.toLowerCase()} #${entity.number}: ${entity.url}`); try { const result = await processEntity(entity); @@ -119,9 +120,9 @@ async function processExpiredEntities(expiredEntities, { entityLabel, maxPerRun closed.push(result.record); } - core.info(`✓ Successfully processed ${entityLabel.toLowerCase()} #${entity.number}: ${entity.url}`); + safeInfo(`✓ Successfully processed ${entityLabel.toLowerCase()} #${entity.number}: ${entity.url}`); } catch (error) { - core.error(`✗ Failed to close ${entityLabel.toLowerCase()} #${entity.number}: ${getErrorMessage(error)}`); + safeError(`✗ Failed to close ${entityLabel.toLowerCase()} #${entity.number}: ${getErrorMessage(error)}`); core.error(` Error details: ${JSON.stringify(error, null, 2)}`); failed.push({ number: entity.number, diff --git a/actions/setup/js/expired_entity_search_helpers.cjs b/actions/setup/js/expired_entity_search_helpers.cjs index 0fab75cb4d8..d56fd57b176 100644 --- a/actions/setup/js/expired_entity_search_helpers.cjs +++ b/actions/setup/js/expired_entity_search_helpers.cjs @@ -11,6 +11,7 @@ const { EXPIRATION_PATTERN, LEGACY_EXPIRATION_PATTERN } = require("./ephemerals. * @property {string} resultKey - Key to use in return object (e.g., "issues", "pullRequests", "discussions") * @property {boolean} [enableDedupe] - Enable duplicate ID tracking (default: false) */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Search statistics @@ -132,7 +133,7 @@ async function searchEntitiesWithExpiration(github, owner, repo, config) { const expirationValue = match ? match[1] : legacyMatch ? legacyMatch[1] : "unknown"; const format = match ? "new" : "legacy"; - core.info(` Found ${config.entityType.slice(0, -1)} #${entity.number} with expiration marker (${format} format): "${expirationValue}" - ${entity.title}`); + safeInfo(` Found ${config.entityType.slice(0, -1)} #${entity.number} with expiration marker (${format} format): "${expirationValue}" - ${entity.title}`); items.push(entity); } } diff --git a/actions/setup/js/git_helpers.cjs b/actions/setup/js/git_helpers.cjs index cff8eea2390..838631cc645 100644 --- a/actions/setup/js/git_helpers.cjs +++ b/actions/setup/js/git_helpers.cjs @@ -10,6 +10,7 @@ const { spawnSync } = require("child_process"); * @returns {string} Command output * @throws {Error} If command fails */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); function execGitSync(args, options = {}) { // Log the git command being executed for debugging (but redact credentials) const gitCommand = `git ${args @@ -32,8 +33,8 @@ function execGitSync(args, options = {}) { }); if (result.error) { - if (typeof core !== "undefined" && core.error) { - core.error(`Git command failed with error: ${result.error.message}`); + if (typeof core !== "undefined" && typeof core.error === "function") { + safeError(`Git command failed with error: ${result.error.message}`); } throw result.error; } diff --git a/actions/setup/js/github_api_helpers.cjs b/actions/setup/js/github_api_helpers.cjs index bba862507b7..8e818d21371 100644 --- a/actions/setup/js/github_api_helpers.cjs +++ b/actions/setup/js/github_api_helpers.cjs @@ -5,6 +5,7 @@ * GitHub API helper functions * Provides common GitHub API operations with consistent error handling */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -46,7 +47,7 @@ async function getFileContent(github, owner, repo, path, ref) { return response.data.content || null; } catch (error) { const errorMessage = getErrorMessage(error); - core.info(`Could not fetch content for ${path}: ${errorMessage}`); + safeInfo(`Could not fetch content for ${path}: ${errorMessage}`); return null; } } diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs index 9361024157e..9b1f00b7e02 100644 --- a/actions/setup/js/handle_agent_failure.cjs +++ b/actions/setup/js/handle_agent_failure.cjs @@ -15,6 +15,7 @@ const fs = require("fs"); * Attempt to find a pull request for the current branch * @returns {Promise<{number: number, html_url: string} | null>} PR info or null if not found */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function findPullRequestForCurrentBranch() { try { const { owner, repo } = context.repo; @@ -42,7 +43,7 @@ async function findPullRequestForCurrentBranch() { core.info(`No pull request found for branch: ${currentBranch}`); return null; } catch (error) { - core.warning(`Failed to find pull request for current branch: ${getErrorMessage(error)}`); + safeWarning(`Failed to find pull request for current branch: ${getErrorMessage(error)}`); return null; } } @@ -57,7 +58,7 @@ async function ensureParentIssue(previousParentNumber = null) { const parentTitle = "[agentics] Failed runs"; const parentLabel = "agentic-workflows"; - core.info(`Searching for parent issue: "${parentTitle}"`); + safeInfo(`Searching for parent issue: "${parentTitle}"`); // Search for existing parent issue const searchQuery = `repo:${owner}/${repo} is:issue is:open label:${parentLabel} in:title "${parentTitle}"`; @@ -93,7 +94,7 @@ async function ensureParentIssue(previousParentNumber = null) { } } } catch (error) { - core.warning(`Error searching for parent issue: ${getErrorMessage(error)}`); + safeWarning(`Error searching for parent issue: ${getErrorMessage(error)}`); } // Create parent issue if it doesn't exist or if previous one is full @@ -190,7 +191,7 @@ gh aw audit node_id: newIssue.data.node_id, }; } catch (error) { - core.error(`Failed to create parent issue: ${getErrorMessage(error)}`); + safeError(`Failed to create parent issue: ${getErrorMessage(error)}`); throw error; } } @@ -232,7 +233,7 @@ async function linkSubIssue(parentNodeId, subIssueNodeId, parentNumber, subIssue if (errorMessage.includes("Field 'addSubIssue' doesn't exist") || errorMessage.includes("not yet available")) { core.warning(`Sub-issue API not available. Issue #${subIssueNumber} created but not linked to parent.`); } else { - core.warning(`Failed to link sub-issue: ${errorMessage}`); + safeWarning(`Failed to link sub-issue: ${errorMessage}`); } } } @@ -294,7 +295,7 @@ function loadMissingDataMessages() { return missingDataMessages; } catch (error) { - core.warning(`Failed to load missing_data messages: ${getErrorMessage(error)}`); + safeWarning(`Failed to load missing_data messages: ${getErrorMessage(error)}`); return []; } } @@ -310,7 +311,7 @@ function buildMissingDataContext() { return ""; } - core.info(`Found ${missingDataMessages.length} missing_data message(s)`); + safeInfo(`Found ${missingDataMessages.length} missing_data message(s)`); // Format the missing data using the existing formatter const formattedList = formatMissingData(missingDataMessages); @@ -344,7 +345,7 @@ async function main() { const checkoutPRSuccess = process.env.GH_AW_CHECKOUT_PR_SUCCESS || ""; core.info(`Agent conclusion: ${agentConclusion}`); - core.info(`Workflow name: ${workflowName}`); + safeInfo(`Workflow name: ${workflowName}`); core.info(`Workflow ID: ${workflowID}`); core.info(`Secret verification result: ${secretVerificationResult}`); core.info(`Assignment error count: ${assignmentErrorCount}`); @@ -407,7 +408,7 @@ async function main() { try { parentIssue = await ensureParentIssue(); } catch (error) { - core.warning(`Could not create parent issue, proceeding without parent: ${getErrorMessage(error)}`); + safeWarning(`Could not create parent issue, proceeding without parent: ${getErrorMessage(error)}`); // Continue without parent issue } @@ -415,7 +416,7 @@ async function main() { const sanitizedWorkflowName = sanitizeContent(workflowName, { maxLength: 100 }); const issueTitle = `[agentics] ${sanitizedWorkflowName} failed`; - core.info(`Checking for existing issue with title: "${issueTitle}"`); + safeInfo(`Checking for existing issue with title: "${issueTitle}"`); // Search for existing open issue with this title and label const searchQuery = `repo:${owner}/${repo} is:issue is:open label:agentic-workflows in:title "${issueTitle}"`; @@ -617,17 +618,17 @@ async function main() { try { await linkSubIssue(parentIssue.node_id, newIssue.data.node_id, parentIssue.number, newIssue.data.number); } catch (error) { - core.warning(`Could not link issue as sub-issue: ${getErrorMessage(error)}`); + safeWarning(`Could not link issue as sub-issue: ${getErrorMessage(error)}`); // Continue even if linking fails } } } } catch (error) { - core.warning(`Failed to create or update failure tracking issue: ${getErrorMessage(error)}`); + safeWarning(`Failed to create or update failure tracking issue: ${getErrorMessage(error)}`); // Don't fail the workflow if we can't create the issue } } catch (error) { - core.warning(`Error in handle_agent_failure: ${getErrorMessage(error)}`); + safeWarning(`Error in handle_agent_failure: ${getErrorMessage(error)}`); // Don't fail the workflow } } diff --git a/actions/setup/js/handle_noop_message.cjs b/actions/setup/js/handle_noop_message.cjs index 9b369e88c88..e50a4e122cf 100644 --- a/actions/setup/js/handle_noop_message.cjs +++ b/actions/setup/js/handle_noop_message.cjs @@ -6,6 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { generateFooterWithExpiration } = require("./ephemerals.cjs"); const { renderTemplate } = require("./messages_core.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Search for or create the parent issue for all agentic workflow no-op runs @@ -16,7 +17,7 @@ async function ensureAgentRunsIssue() { const parentTitle = "[agentics] No-Op Runs"; const parentLabel = "agentic-workflows"; - core.info(`Searching for no-op runs issue: "${parentTitle}"`); + safeInfo(`Searching for no-op runs issue: "${parentTitle}"`); // Search for existing no-op runs issue const searchQuery = `repo:${owner}/${repo} is:issue is:open label:${parentLabel} in:title "${parentTitle}"`; @@ -37,7 +38,7 @@ async function ensureAgentRunsIssue() { }; } } catch (error) { - core.warning(`Error searching for no-op runs issue: ${getErrorMessage(error)}`); + safeWarning(`Error searching for no-op runs issue: ${getErrorMessage(error)}`); } // Create no-op runs issue if it doesn't exist @@ -69,7 +70,7 @@ async function ensureAgentRunsIssue() { node_id: newIssue.data.node_id, }; } catch (error) { - core.error(`Failed to create no-op runs issue: ${getErrorMessage(error)}`); + safeError(`Failed to create no-op runs issue: ${getErrorMessage(error)}`); throw error; } } @@ -90,9 +91,9 @@ async function main() { const agentConclusion = process.env.GH_AW_AGENT_CONCLUSION || ""; const reportAsIssue = process.env.GH_AW_NOOP_REPORT_AS_ISSUE !== "false"; // Default to true - core.info(`Workflow name: ${workflowName}`); + safeInfo(`Workflow name: ${workflowName}`); core.info(`Run URL: ${runUrl}`); - core.info(`No-op message: ${noopMessage}`); + safeInfo(`No-op message: ${noopMessage}`); core.info(`Agent conclusion: ${agentConclusion}`); core.info(`Report as issue: ${reportAsIssue}`); @@ -138,7 +139,7 @@ async function main() { try { noopRunsIssue = await ensureAgentRunsIssue(); } catch (error) { - core.warning(`Could not create no-op runs issue: ${getErrorMessage(error)}`); + safeWarning(`Could not create no-op runs issue: ${getErrorMessage(error)}`); // Don't fail the workflow if we can't create the issue return; } @@ -167,11 +168,11 @@ async function main() { core.info(`✓ Posted no-op message to no-op runs issue #${noopRunsIssue.number}`); } catch (error) { - core.warning(`Failed to post comment to no-op runs issue: ${getErrorMessage(error)}`); + safeWarning(`Failed to post comment to no-op runs issue: ${getErrorMessage(error)}`); // Don't fail the workflow } } catch (error) { - core.warning(`Error in handle_noop_message: ${getErrorMessage(error)}`); + safeWarning(`Error in handle_noop_message: ${getErrorMessage(error)}`); // Don't fail the workflow } } diff --git a/actions/setup/js/hide_comment.cjs b/actions/setup/js/hide_comment.cjs index 30af9e37f74..a842bae6940 100644 --- a/actions/setup/js/hide_comment.cjs +++ b/actions/setup/js/hide_comment.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -123,7 +124,7 @@ async function main(config = {}) { } } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to hide comment: ${errorMessage}`); + safeError(`Failed to hide comment: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/interpolate_prompt.cjs b/actions/setup/js/interpolate_prompt.cjs index 1484c8beef9..70adecd235b 100644 --- a/actions/setup/js/interpolate_prompt.cjs +++ b/actions/setup/js/interpolate_prompt.cjs @@ -16,9 +16,10 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * @param {Record} variables - Map of variable names to their values * @returns {string} - The interpolated content */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); function interpolateVariables(content, variables) { core.info(`[interpolateVariables] Starting interpolation with ${Object.keys(variables).length} variables`); - core.info(`[interpolateVariables] Content length: ${content.length} characters`); + safeInfo(`[interpolateVariables] Content length: ${content.length} characters`); let result = content; let totalReplacements = 0; @@ -29,12 +30,12 @@ function interpolateVariables(content, variables) { const matches = (content.match(pattern) || []).length; if (matches > 0) { - core.info(`[interpolateVariables] Replacing ${varName} (${matches} occurrence(s))`); + safeInfo(`[interpolateVariables] Replacing ${varName} (${matches} occurrence(s))`); core.info(`[interpolateVariables] Value: ${value.substring(0, 100)}${value.length > 100 ? "..." : ""}`); result = result.replace(pattern, value); totalReplacements += matches; } else { - core.info(`[interpolateVariables] Variable ${varName} not found in content (unused)`); + safeInfo(`[interpolateVariables] Variable ${varName} not found in content (unused)`); } } @@ -74,7 +75,7 @@ function renderMarkdownTemplate(markdown) { const bodyPreview = body.substring(0, 60).replace(/\n/g, "\\n"); core.info(`[renderMarkdownTemplate] Block ${blockCount}: condition="${condTrimmed}" -> ${truthyResult ? "KEEP" : "REMOVE"}`); - core.info(`[renderMarkdownTemplate] Body preview: "${bodyPreview}${body.length > 60 ? "..." : ""}"`); + safeInfo(`[renderMarkdownTemplate] Body preview: "${bodyPreview}${body.length > 60 ? "..." : ""}"`); if (truthyResult) { // Keep body with leading newline if there was one before the opening tag @@ -103,7 +104,7 @@ function renderMarkdownTemplate(markdown) { const bodyPreview = body.substring(0, 40).replace(/\n/g, "\\n"); core.info(`[renderMarkdownTemplate] Inline ${inlineCount}: condition="${condTrimmed}" -> ${truthyResult ? "KEEP" : "REMOVE"}`); - core.info(`[renderMarkdownTemplate] Body preview: "${bodyPreview}${body.length > 40 ? "..." : ""}"`); + safeInfo(`[renderMarkdownTemplate] Body preview: "${bodyPreview}${body.length > 40 ? "..." : ""}"`); if (truthyResult) { keptInline++; @@ -159,7 +160,7 @@ async function main() { let content = fs.readFileSync(promptPath, "utf8"); const originalLength = content.length; core.info(`[main] Original content length: ${originalLength} characters`); - core.info(`[main] First 200 characters: ${content.substring(0, 200).replace(/\n/g, "\\n")}`); + safeInfo(`[main] First 200 characters: ${content.substring(0, 200).replace(/\n/g, "\\n")}`); // Step 1: Process runtime imports (files and URLs) core.info("\n========================================"); @@ -237,12 +238,12 @@ async function main() { core.info("[main] STEP 4: Writing Output"); core.info("========================================"); core.info(`Writing processed content back to: ${promptPath}`); - core.info(`Final content length: ${content.length} characters`); - core.info(`Total length change: ${originalLength} -> ${content.length} (${content.length > originalLength ? "+" : ""}${content.length - originalLength})`); + safeInfo(`Final content length: ${content.length} characters`); + safeInfo(`Total length change: ${originalLength} -> ${content.length} (${content.length > originalLength ? "+" : ""}${content.length - originalLength})`); fs.writeFileSync(promptPath, content, "utf8"); - core.info(`Last 200 characters: ${content.substring(Math.max(0, content.length - 200)).replace(/\n/g, "\\n")}`); + safeInfo(`Last 200 characters: ${content.substring(Math.max(0, content.length - 200)).replace(/\n/g, "\\n")}`); core.info("========================================"); core.info("[main] Processing complete - SUCCESS"); core.info("========================================"); @@ -251,10 +252,10 @@ async function main() { core.info("[main] Processing failed - ERROR"); core.info("========================================"); const err = error instanceof Error ? error : new Error(String(error)); - core.info(`[main] Error type: ${err.constructor.name}`); - core.info(`[main] Error message: ${err.message}`); + safeInfo(`[main] Error type: ${err.constructor.name}`); + safeInfo(`[main] Error message: ${err.message}`); if (err.stack) { - core.info(`[main] Stack trace:\n${err.stack}`); + safeInfo(`[main] Stack trace:\n${err.stack}`); } core.setFailed(getErrorMessage(error)); } diff --git a/actions/setup/js/interpolate_prompt_additional.test.cjs b/actions/setup/js/interpolate_prompt_additional.test.cjs index 8e39517db28..5efaa98d0d6 100644 --- a/actions/setup/js/interpolate_prompt_additional.test.cjs +++ b/actions/setup/js/interpolate_prompt_additional.test.cjs @@ -4,8 +4,16 @@ import path from "path"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url), __dirname = path.dirname(__filename), - core = { info: vi.fn(), setFailed: vi.fn() }; + core = { info: vi.fn(), setFailed: vi.fn() }, + safeInfo = vi.fn(), + safeDebug = vi.fn(), + safeWarning = vi.fn(), + safeError = vi.fn(); global.core = core; +global.safeInfo = safeInfo; +global.safeDebug = safeDebug; +global.safeWarning = safeWarning; +global.safeError = safeError; const { isTruthy } = require("./is_truthy.cjs"), interpolatePromptScript = fs.readFileSync(path.join(__dirname, "interpolate_prompt.cjs"), "utf8"), renderMarkdownTemplateMatch = interpolatePromptScript.match(/function renderMarkdownTemplate\(markdown\)\s*{[\s\S]*?return result;[\s\S]*?}/); diff --git a/actions/setup/js/link_sub_issue.cjs b/actions/setup/js/link_sub_issue.cjs index ab8cf704074..9e3769e1b5b 100644 --- a/actions/setup/js/link_sub_issue.cjs +++ b/actions/setup/js/link_sub_issue.cjs @@ -10,6 +10,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * @param {Object} config - Handler configuration from GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG * @returns {Promise} Message handler function (message, resolvedTemporaryIds) => result */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main(config = {}) { // Extract configuration from config object const parentRequiredLabels = config.parent_required_labels || []; @@ -19,16 +20,16 @@ async function main(config = {}) { const maxCount = config.max || 5; if (parentRequiredLabels.length > 0) { - core.info(`Parent required labels: ${JSON.stringify(parentRequiredLabels)}`); + safeInfo(`Parent required labels: ${JSON.stringify(parentRequiredLabels)}`); } if (parentTitlePrefix) { - core.info(`Parent title prefix: ${parentTitlePrefix}`); + safeInfo(`Parent title prefix: ${parentTitlePrefix}`); } if (subRequiredLabels.length > 0) { - core.info(`Sub-issue required labels: ${JSON.stringify(subRequiredLabels)}`); + safeInfo(`Sub-issue required labels: ${JSON.stringify(subRequiredLabels)}`); } if (subTitlePrefix) { - core.info(`Sub-issue title prefix: ${subTitlePrefix}`); + safeInfo(`Sub-issue title prefix: ${subTitlePrefix}`); } core.info(`Max count: ${maxCount}`); @@ -89,7 +90,7 @@ async function main(config = {}) { // Check for other resolution errors (non-temporary ID issues) if (parentResolved.errorMessage) { - core.warning(`Failed to resolve parent issue: ${parentResolved.errorMessage}`); + safeWarning(`Failed to resolve parent issue: ${parentResolved.errorMessage}`); return { parent_issue_number: item.parent_issue_number, sub_issue_number: item.sub_issue_number, @@ -99,7 +100,7 @@ async function main(config = {}) { } if (subResolved.errorMessage) { - core.warning(`Failed to resolve sub-issue: ${subResolved.errorMessage}`); + safeWarning(`Failed to resolve sub-issue: ${subResolved.errorMessage}`); return { parent_issue_number: item.parent_issue_number, sub_issue_number: item.sub_issue_number, @@ -122,10 +123,10 @@ async function main(config = {}) { } if (parentResolved.wasTemporaryId && parentResolved.resolved) { - core.info(`Resolved parent temporary ID '${item.parent_issue_number}' to ${parentResolved.resolved.owner}/${parentResolved.resolved.repo}#${parentIssueNumber}`); + safeInfo(`Resolved parent temporary ID '${item.parent_issue_number}' to ${parentResolved.resolved.owner}/${parentResolved.resolved.repo}#${parentIssueNumber}`); } if (subResolved.wasTemporaryId && subResolved.resolved) { - core.info(`Resolved sub-issue temporary ID '${item.sub_issue_number}' to ${subResolved.resolved.owner}/${subResolved.resolved.repo}#${subIssueNumber}`); + safeInfo(`Resolved sub-issue temporary ID '${item.sub_issue_number}' to ${subResolved.resolved.owner}/${subResolved.resolved.repo}#${subIssueNumber}`); } // Sub-issue linking is only supported within the same repository. @@ -158,7 +159,7 @@ async function main(config = {}) { parentIssue = parentResponse.data; } catch (error) { const errorMessage = getErrorMessage(error); - core.warning(`Failed to fetch parent issue #${parentIssueNumber}: ${errorMessage}`); + safeWarning(`Failed to fetch parent issue #${parentIssueNumber}: ${errorMessage}`); return { parent_issue_number: parentIssueNumber, sub_issue_number: subIssueNumber, @@ -172,7 +173,7 @@ async function main(config = {}) { const parentLabels = parentIssue.labels.map(l => (typeof l === "string" ? l : l.name || "")); const missingLabels = parentRequiredLabels.filter(required => !parentLabels.includes(required)); if (missingLabels.length > 0) { - core.warning(`Parent issue #${parentIssueNumber} is missing required labels: ${missingLabels.join(", ")}. Skipping.`); + safeWarning(`Parent issue #${parentIssueNumber} is missing required labels: ${missingLabels.join(", ")}. Skipping.`); return { parent_issue_number: parentIssueNumber, sub_issue_number: subIssueNumber, @@ -183,7 +184,7 @@ async function main(config = {}) { } if (parentTitlePrefix && !parentIssue.title.startsWith(parentTitlePrefix)) { - core.warning(`Parent issue #${parentIssueNumber} title does not start with "${parentTitlePrefix}". Skipping.`); + safeWarning(`Parent issue #${parentIssueNumber} title does not start with "${parentTitlePrefix}". Skipping.`); return { parent_issue_number: parentIssueNumber, sub_issue_number: subIssueNumber, @@ -203,7 +204,7 @@ async function main(config = {}) { subIssue = subResponse.data; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to fetch sub-issue #${subIssueNumber}: ${errorMessage}`); + safeError(`Failed to fetch sub-issue #${subIssueNumber}: ${errorMessage}`); return { parent_issue_number: parentIssueNumber, sub_issue_number: subIssueNumber, @@ -234,7 +235,7 @@ async function main(config = {}) { const existingParent = parentCheckResult?.repository?.issue?.parent; if (existingParent) { - core.warning(`Sub-issue #${subIssueNumber} is already a sub-issue of #${existingParent.number} ("${existingParent.title}"). Skipping.`); + safeWarning(`Sub-issue #${subIssueNumber} is already a sub-issue of #${existingParent.number} ("${existingParent.title}"). Skipping.`); return { parent_issue_number: parentIssueNumber, sub_issue_number: subIssueNumber, @@ -245,7 +246,7 @@ async function main(config = {}) { } catch (error) { // If the GraphQL query fails (e.g., parent field not available), log warning but continue const errorMessage = getErrorMessage(error); - core.warning(`Could not check if sub-issue #${subIssueNumber} has a parent: ${errorMessage}. Proceeding with link attempt.`); + safeWarning(`Could not check if sub-issue #${subIssueNumber} has a parent: ${errorMessage}. Proceeding with link attempt.`); } // Validate sub-issue filters @@ -253,7 +254,7 @@ async function main(config = {}) { const subLabels = subIssue.labels.map(l => (typeof l === "string" ? l : l.name || "")); const missingLabels = subRequiredLabels.filter(required => !subLabels.includes(required)); if (missingLabels.length > 0) { - core.warning(`Sub-issue #${subIssueNumber} is missing required labels: ${missingLabels.join(", ")}. Skipping.`); + safeWarning(`Sub-issue #${subIssueNumber} is missing required labels: ${missingLabels.join(", ")}. Skipping.`); return { parent_issue_number: parentIssueNumber, sub_issue_number: subIssueNumber, @@ -264,7 +265,7 @@ async function main(config = {}) { } if (subTitlePrefix && !subIssue.title.startsWith(subTitlePrefix)) { - core.warning(`Sub-issue #${subIssueNumber} title does not start with "${subTitlePrefix}". Skipping.`); + safeWarning(`Sub-issue #${subIssueNumber} title does not start with "${subTitlePrefix}". Skipping.`); return { parent_issue_number: parentIssueNumber, sub_issue_number: subIssueNumber, @@ -309,7 +310,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.warning(`Failed to link issue #${subIssueNumber} as sub-issue of #${parentIssueNumber}: ${errorMessage}`); + safeWarning(`Failed to link issue #${subIssueNumber} as sub-issue of #${parentIssueNumber}: ${errorMessage}`); return { parent_issue_number: parentIssueNumber, sub_issue_number: subIssueNumber, diff --git a/actions/setup/js/load_agent_output.cjs b/actions/setup/js/load_agent_output.cjs index ab4d962157d..de2fd9e7f97 100644 --- a/actions/setup/js/load_agent_output.cjs +++ b/actions/setup/js/load_agent_output.cjs @@ -2,7 +2,7 @@ /// const { getErrorMessage } = require("./error_helpers.cjs"); - +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const fs = require("fs"); /** @@ -68,7 +68,7 @@ function loadAgentOutput() { return { success: false }; } - core.info(`Agent output content length: ${outputContent.length}`); + safeInfo(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; @@ -77,7 +77,7 @@ function loadAgentOutput() { } catch (error) { const errorMessage = `Error parsing agent output JSON: ${getErrorMessage(error)}`; core.error(errorMessage); - core.info(`Failed to parse content:\n${truncateForLogging(outputContent)}`); + safeInfo(`Failed to parse content:\n${truncateForLogging(outputContent)}`); return { success: false, error: errorMessage }; } diff --git a/actions/setup/js/lock-issue.cjs b/actions/setup/js/lock-issue.cjs index dae96e33959..9adbf18f93d 100644 --- a/actions/setup/js/lock-issue.cjs +++ b/actions/setup/js/lock-issue.cjs @@ -6,12 +6,13 @@ * This script is used in the activation job when lock-for-agent is enabled * to prevent concurrent modifications during agent workflow execution */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); async function main() { // Log actor and event information for debugging - core.info(`Lock-issue debug: actor=${context.actor}, eventName=${context.eventName}`); + safeInfo(`Lock-issue debug: actor=${context.actor}, eventName=${context.eventName}`); // Get issue number from context const issueNumber = context.issue.number; @@ -62,7 +63,7 @@ async function main() { core.setOutput("locked", "true"); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to lock issue: ${errorMessage}`); + safeError(`Failed to lock issue: ${errorMessage}`); core.setFailed(`Failed to lock issue #${issueNumber}: ${errorMessage}`); core.setOutput("locked", "false"); } diff --git a/actions/setup/js/log_parser_bootstrap.cjs b/actions/setup/js/log_parser_bootstrap.cjs index 47753b8051f..c7ec3096b63 100644 --- a/actions/setup/js/log_parser_bootstrap.cjs +++ b/actions/setup/js/log_parser_bootstrap.cjs @@ -15,6 +15,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * @param {boolean} [options.supportsDirectories=false] - Whether the parser supports reading from directories * @returns {Promise} */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function runLogParser(options) { const fs = require("fs"); const path = require("path"); @@ -38,7 +39,7 @@ async function runLogParser(options) { const stat = fs.statSync(logPath); if (stat.isDirectory()) { if (!supportsDirectories) { - core.info(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`); + safeInfo(`Log path is a directory but ${parserName} parser does not support directories: ${logPath}`); return; } @@ -131,7 +132,7 @@ async function runLogParser(options) { try { safeOutputsContent = fs.readFileSync(safeOutputsPath, "utf8"); } catch (error) { - core.warning(`Failed to read safe outputs file: ${getErrorMessage(error)}`); + safeWarning(`Failed to read safe outputs file: ${getErrorMessage(error)}`); } } @@ -179,7 +180,7 @@ async function runLogParser(options) { core.summary.addRaw(fullMarkdown).write(); } else { // Fallback: just log success message for parsers without log entries - core.info(`${parserName} log parsed successfully`); + safeInfo(`${parserName} log parsed successfully`); // Add safe outputs preview to core.info (fallback path) if (safeOutputsContent) { @@ -206,7 +207,7 @@ async function runLogParser(options) { core.summary.addRaw(fullMarkdown).write(); } } else { - core.error(`Failed to parse ${parserName} log`); + safeError(`Failed to parse ${parserName} log`); } // Handle MCP server failures if present 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 d19bb6fd9bb..a940aed8505 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 @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { generateFooter } = require("./generate_footer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); @@ -112,7 +113,7 @@ async function main(config = {}) { if (item.pull_request_number !== undefined) { prNumber = parseInt(String(item.pull_request_number), 10); if (isNaN(prNumber)) { - core.warning(`Invalid pull_request_number: ${item.pull_request_number}`); + safeWarning(`Invalid pull_request_number: ${item.pull_request_number}`); return { success: false, error: `Invalid pull_request_number: ${item.pull_request_number}`, @@ -186,7 +187,7 @@ async function main(config = {}) { }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to mark PR #${prNumber} as ready for review: ${errorMessage}`); + safeError(`Failed to mark PR #${prNumber} as ready for review: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/messages_core.cjs b/actions/setup/js/messages_core.cjs index 7db11d43ccc..85eaac8b594 100644 --- a/actions/setup/js/messages_core.cjs +++ b/actions/setup/js/messages_core.cjs @@ -20,6 +20,7 @@ * * Both camelCase and snake_case placeholder formats are supported. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * @typedef {Object} SafeOutputMessages @@ -55,7 +56,7 @@ function getMessages() { // Parse JSON with camelCase keys from Go struct (using json struct tags) return JSON.parse(messagesEnv); } catch (error) { - core.warning(`Failed to parse GH_AW_SAFE_OUTPUT_MESSAGES: ${getErrorMessage(error)}`); + safeWarning(`Failed to parse GH_AW_SAFE_OUTPUT_MESSAGES: ${getErrorMessage(error)}`); return null; } } diff --git a/actions/setup/js/missing_data.cjs b/actions/setup/js/missing_data.cjs index 107797194f9..a3e0a0b136a 100644 --- a/actions/setup/js/missing_data.cjs +++ b/actions/setup/js/missing_data.cjs @@ -6,6 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "missing_data"; @@ -56,7 +57,7 @@ async function main(config = {}) { core.info(` Reason: ${missingData.reason}`); } if (missingData.context) { - core.info(` Context: ${missingData.context}`); + safeInfo(` Context: ${missingData.context}`); } if (missingData.alternatives) { core.info(` Alternatives: ${missingData.alternatives}`); diff --git a/actions/setup/js/missing_tool.cjs b/actions/setup/js/missing_tool.cjs index cd1cb058b79..e859967739a 100644 --- a/actions/setup/js/missing_tool.cjs +++ b/actions/setup/js/missing_tool.cjs @@ -6,6 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "missing_tool"; @@ -42,7 +43,7 @@ async function main(config = {}) { // Validate required fields (only reason is required now) if (!message.reason) { - core.warning(`missing_tool message missing 'reason' field: ${JSON.stringify(message)}`); + safeWarning(`missing_tool message missing 'reason' field: ${JSON.stringify(message)}`); return { success: false, error: "Missing required field: reason", diff --git a/actions/setup/js/noop.cjs b/actions/setup/js/noop.cjs index 6bcba542d2f..3b39db2e510 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 { safeInfo } = require("./sanitized_logging.cjs"); /** * Main function to handle noop safe output @@ -49,7 +50,7 @@ async function main() { for (let i = 0; i < noopItems.length; i++) { const item = noopItems[i]; - core.info(`No-op message ${i + 1}: ${item.message}`); + safeInfo(`No-op message ${i + 1}: ${item.message}`); summaryContent += `- ${item.message}\n`; } diff --git a/actions/setup/js/noop_handler.cjs b/actions/setup/js/noop_handler.cjs index 54cf13b33b6..50be1ecad6e 100644 --- a/actions/setup/js/noop_handler.cjs +++ b/actions/setup/js/noop_handler.cjs @@ -6,6 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "noop"; @@ -43,7 +44,7 @@ async function main(config = {}) { // Validate required fields const { message: messageText } = message; if (!messageText || typeof messageText !== "string" || !messageText.trim()) { - core.warning(`noop message missing or invalid 'message' field: ${JSON.stringify(message)}`); + safeWarning(`noop message missing or invalid 'message' field: ${JSON.stringify(message)}`); return { success: false, error: "Missing required field: message", @@ -54,7 +55,7 @@ async function main(config = {}) { const timestamp = new Date().toISOString(); - core.info(`✓ Recorded noop message: ${messageText}`); + safeInfo(`✓ Recorded noop message: ${messageText}`); return { success: true, diff --git a/actions/setup/js/notify_comment_error.cjs b/actions/setup/js/notify_comment_error.cjs index 7d5b7c66ef4..6212d3137ae 100644 --- a/actions/setup/js/notify_comment_error.cjs +++ b/actions/setup/js/notify_comment_error.cjs @@ -14,6 +14,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * Collect generated asset URLs from safe output jobs * @returns {Array} Array of generated asset URLs */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); function collectGeneratedAssets() { const assets = []; @@ -27,7 +28,7 @@ function collectGeneratedAssets() { try { jobOutputMapping = JSON.parse(safeOutputJobsEnv); } catch (error) { - core.warning(`Failed to parse GH_AW_SAFE_OUTPUT_JOBS: ${getErrorMessage(error)}`); + safeWarning(`Failed to parse GH_AW_SAFE_OUTPUT_JOBS: ${getErrorMessage(error)}`); return assets; } @@ -61,7 +62,7 @@ async function main() { core.info(`Comment ID: ${commentId}`); core.info(`Comment Repo: ${commentRepo}`); core.info(`Run URL: ${runUrl}`); - core.info(`Workflow Name: ${workflowName}`); + safeInfo(`Workflow Name: ${workflowName}`); core.info(`Agent Conclusion: ${agentConclusion}`); if (detectionConclusion) { core.info(`Detection Conclusion: ${detectionConclusion}`); @@ -93,7 +94,7 @@ async function main() { } await core.summary.addRaw(summaryContent).write(); - core.info(`Successfully wrote ${noopMessages.length} noop message(s) to step summary`); + safeInfo(`Successfully wrote ${noopMessages.length} noop message(s) to step summary`); return; } @@ -112,7 +113,7 @@ async function main() { const repoOwner = commentRepo ? commentRepo.split("/")[0] : context.repo.owner; const repoName = commentRepo ? commentRepo.split("/")[1] : context.repo.repo; - core.info(`Updating comment in ${repoOwner}/${repoName}`); + safeInfo(`Updating comment in ${repoOwner}/${repoName}`); // Determine the message based on agent conclusion using custom messages if configured let message; @@ -244,7 +245,7 @@ async function main() { return; } catch (error) { // Don't fail the workflow if we can't create the comment - core.warning(`Failed to create append-only comment: ${getErrorMessage(error)}`); + safeWarning(`Failed to create append-only comment: ${getErrorMessage(error)}`); return; } } @@ -296,7 +297,7 @@ async function main() { } } catch (error) { // Don't fail the workflow if we can't update the comment - core.warning(`Failed to update comment: ${getErrorMessage(error)}`); + safeWarning(`Failed to update comment: ${getErrorMessage(error)}`); } } diff --git a/actions/setup/js/parse_mcp_gateway_log.cjs b/actions/setup/js/parse_mcp_gateway_log.cjs index 7247c414653..e9e34eed405 100644 --- a/actions/setup/js/parse_mcp_gateway_log.cjs +++ b/actions/setup/js/parse_mcp_gateway_log.cjs @@ -12,6 +12,7 @@ const { displayDirectories } = require("./display_file_helpers.cjs"); * - /tmp/gh-aw/mcp-logs/gateway.log (main gateway log, fallback) * - /tmp/gh-aw/mcp-logs/stderr.log (stderr output, fallback) */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Prints all gateway-related files to core.info for debugging @@ -37,7 +38,7 @@ async function main() { if (fs.existsSync(gatewayMdPath)) { const gatewayMdContent = fs.readFileSync(gatewayMdPath, "utf8"); if (gatewayMdContent && gatewayMdContent.trim().length > 0) { - core.info(`Found gateway.md (${gatewayMdContent.length} bytes)`); + safeInfo(`Found gateway.md (${gatewayMdContent.length} bytes)`); // Write the markdown directly to the step summary core.summary.addRaw(gatewayMdContent).write(); @@ -54,7 +55,7 @@ async function main() { // Read gateway.log if it exists if (fs.existsSync(gatewayLogPath)) { gatewayLogContent = fs.readFileSync(gatewayLogPath, "utf8"); - core.info(`Found gateway.log (${gatewayLogContent.length} bytes)`); + safeInfo(`Found gateway.log (${gatewayLogContent.length} bytes)`); } else { core.info(`No gateway.log found at: ${gatewayLogPath}`); } @@ -62,7 +63,7 @@ async function main() { // Read stderr.log if it exists if (fs.existsSync(stderrLogPath)) { stderrLogContent = fs.readFileSync(stderrLogPath, "utf8"); - core.info(`Found stderr.log (${stderrLogContent.length} bytes)`); + safeInfo(`Found stderr.log (${stderrLogContent.length} bytes)`); } else { core.info(`No stderr.log found at: ${stderrLogPath}`); } diff --git a/actions/setup/js/push_repo_memory.cjs b/actions/setup/js/push_repo_memory.cjs index 1699f126c8c..061f67dfd95 100644 --- a/actions/setup/js/push_repo_memory.cjs +++ b/actions/setup/js/push_repo_memory.cjs @@ -34,6 +34,7 @@ const { execGitSync } = require("./git_helpers.cjs"); * GH_TOKEN: GitHub token for authentication * GITHUB_RUN_ID: Workflow run ID for commit messages */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function main() { const artifactDir = process.env.ARTIFACT_DIR; @@ -103,7 +104,7 @@ async function main() { } // Checkout or create the memory branch - core.info(`Checking out branch: ${branchName}...`); + safeInfo(`Checking out branch: ${branchName}...`); try { const repoUrl = `https://x-access-token:${ghToken}@github.com/${targetRepo}.git`; @@ -111,14 +112,14 @@ async function main() { try { execGitSync(["fetch", repoUrl, `${branchName}:${branchName}`], { stdio: "pipe" }); execGitSync(["checkout", branchName], { stdio: "inherit" }); - core.info(`Checked out existing branch: ${branchName}`); + safeInfo(`Checked out existing branch: ${branchName}`); } catch (fetchError) { // Branch doesn't exist, create orphan branch - core.info(`Branch ${branchName} does not exist, creating orphan branch...`); + safeInfo(`Branch ${branchName} does not exist, creating orphan branch...`); execGitSync(["checkout", "--orphan", branchName], { stdio: "inherit" }); // Use --ignore-unmatch to avoid failure when directory is empty execGitSync(["rm", "-r", "-f", "--ignore-unmatch", "."], { stdio: "pipe" }); - core.info(`Created orphan branch: ${branchName}`); + safeInfo(`Created orphan branch: ${branchName}`); } } catch (error) { core.setFailed(`Failed to checkout branch: ${getErrorMessage(error)}`); @@ -303,21 +304,21 @@ async function main() { } // Pull with merge strategy (ours wins on conflicts) - core.info(`Pulling latest changes from ${branchName}...`); + safeInfo(`Pulling latest changes from ${branchName}...`); try { const repoUrl = `https://x-access-token:${ghToken}@github.com/${targetRepo}.git`; execGitSync(["pull", "--no-rebase", "-X", "ours", repoUrl, branchName], { stdio: "inherit" }); } catch (error) { // Pull might fail if branch doesn't exist yet or on conflicts - this is acceptable - core.warning(`Pull failed (this may be expected): ${getErrorMessage(error)}`); + safeWarning(`Pull failed (this may be expected): ${getErrorMessage(error)}`); } // Push changes - core.info(`Pushing changes to ${branchName}...`); + safeInfo(`Pushing changes to ${branchName}...`); try { const repoUrl = `https://x-access-token:${ghToken}@github.com/${targetRepo}.git`; execGitSync(["push", repoUrl, `HEAD:${branchName}`], { stdio: "inherit" }); - core.info(`Successfully pushed changes to ${branchName} branch`); + safeInfo(`Successfully pushed changes to ${branchName} branch`); } catch (error) { core.setFailed(`Failed to push changes: ${getErrorMessage(error)}`); return; diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index fb2734d8474..bd363d477f3 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -11,6 +11,7 @@ const { replaceTemporaryIdReferences } = require("./temporary_id.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "push_to_pull_request_branch"; @@ -39,14 +40,14 @@ async function main(config = {}) { core.info(`Base branch: ${baseBranch}`); } if (titlePrefix) { - core.info(`Title prefix: ${titlePrefix}`); + safeInfo(`Title prefix: ${titlePrefix}`); } if (envLabels.length > 0) { - core.info(`Required labels: ${envLabels.join(", ")}`); + safeInfo(`Required labels: ${envLabels.join(", ")}`); } core.info(`If no changes: ${ifNoChanges}`); if (commitTitleSuffix) { - core.info(`Commit title suffix: ${commitTitleSuffix}`); + safeInfo(`Commit title suffix: ${commitTitleSuffix}`); } core.info(`Max patch size: ${maxSizeKb} KB`); core.info(`Max count: ${maxCount || "unlimited"}`); @@ -92,7 +93,7 @@ async function main(config = {}) { const msg = "Patch file contains error message - cannot push without changes"; core.error("Patch file generation failed"); core.error(`Patch file location: /tmp/gh-aw/aw.patch`); - core.error(`Patch file size: ${Buffer.byteLength(patchContent, "utf8")} bytes`); + safeError(`Patch file size: ${Buffer.byteLength(patchContent, "utf8")} bytes`); const previewLength = Math.min(500, patchContent.length); core.error(`Patch file preview (first ${previewLength} characters):`); core.error(patchContent.substring(0, previewLength)); @@ -204,13 +205,13 @@ async function main(config = {}) { prTitle = pullRequest.title || ""; prLabels = pullRequest.labels.map(label => label.name); } catch (error) { - core.info(`Warning: Could not fetch PR ${pullNumber} details: ${getErrorMessage(error)}`); + safeInfo(`Warning: Could not fetch PR ${pullNumber} details: ${getErrorMessage(error)}`); return { success: false, error: `Failed to determine branch name for PR ${pullNumber}` }; } - core.info(`Target branch: ${branchName}`); - core.info(`PR title: ${prTitle}`); - core.info(`PR labels: ${prLabels.join(", ")}`); + safeInfo(`Target branch: ${branchName}`); + safeInfo(`PR title: ${prTitle}`); + safeInfo(`PR labels: ${prLabels.join(", ")}`); // Validate title prefix if specified if (titlePrefix && !prTitle.startsWith(titlePrefix)) { @@ -226,20 +227,20 @@ async function main(config = {}) { } if (titlePrefix) { - core.info(`✓ Title prefix validation passed: "${titlePrefix}"`); + safeInfo(`✓ Title prefix validation passed: "${titlePrefix}"`); } if (envLabels.length > 0) { - core.info(`✓ Labels validation passed: ${envLabels.join(", ")}`); + safeInfo(`✓ Labels validation passed: ${envLabels.join(", ")}`); } const hasChanges = !isEmpty; // Switch to or create the target branch - core.info(`Switching to branch: ${branchName}`); + safeInfo(`Switching to branch: ${branchName}`); // Fetch the specific target branch from origin try { - core.info(`Fetching branch: ${branchName}`); + safeInfo(`Fetching branch: ${branchName}`); await exec.exec(`git fetch origin ${branchName}:refs/remotes/origin/${branchName}`); } catch (fetchError) { return { success: false, error: `Failed to fetch branch ${branchName}: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}` }; @@ -255,7 +256,7 @@ async function main(config = {}) { // Checkout the branch from origin try { await exec.exec(`git checkout -B ${branchName} origin/${branchName}`); - core.info(`Checked out existing branch from origin: ${branchName}`); + safeInfo(`Checked out existing branch from origin: ${branchName}`); } catch (checkoutError) { return { success: false, error: `Failed to checkout branch ${branchName}: ${checkoutError instanceof Error ? checkoutError.message : String(checkoutError)}` }; } @@ -265,7 +266,7 @@ async function main(config = {}) { core.info("Applying patch..."); try { if (commitTitleSuffix) { - core.info(`Appending commit title suffix: "${commitTitleSuffix}"`); + safeInfo(`Appending commit title suffix: "${commitTitleSuffix}"`); // Read the patch file let patchContent = fs.readFileSync("/tmp/gh-aw/aw.patch", "utf8"); @@ -275,7 +276,7 @@ async function main(config = {}) { // Write the modified patch back fs.writeFileSync("/tmp/gh-aw/aw.patch", patchContent, "utf8"); - core.info(`Patch modified with commit title suffix: "${commitTitleSuffix}"`); + safeInfo(`Patch modified with commit title suffix: "${commitTitleSuffix}"`); } // Log first 100 lines of patch for debugging @@ -293,9 +294,9 @@ async function main(config = {}) { // Push the applied commits to the branch await exec.exec(`git push origin ${branchName}`); - core.info(`Changes committed and pushed to branch: ${branchName}`); + safeInfo(`Changes committed and pushed to branch: ${branchName}`); } catch (error) { - core.error(`Failed to apply patch: ${getErrorMessage(error)}`); + safeError(`Failed to apply patch: ${getErrorMessage(error)}`); // Investigate patch failure try { @@ -321,7 +322,7 @@ async function main(config = {}) { core.info("Failed patch (full):"); core.info(patchFullResult.stdout); } catch (investigateError) { - core.warning(`Failed to investigate patch failure: ${investigateError instanceof Error ? investigateError.message : String(investigateError)}`); + safeWarning(`Failed to investigate patch failure: ${investigateError instanceof Error ? investigateError.message : String(investigateError)}`); } return { success: false, error: "Failed to apply patch" }; diff --git a/actions/setup/js/redact_secrets.cjs b/actions/setup/js/redact_secrets.cjs index 882a90d6f49..c2514c01376 100644 --- a/actions/setup/js/redact_secrets.cjs +++ b/actions/setup/js/redact_secrets.cjs @@ -6,6 +6,7 @@ * This script processes all .txt, .json, .log, .md, .mdx, .yml, .jsonl files under /tmp/gh-aw and /opt/gh-aw * and redacts any strings matching the actual secret values provided via environment variables. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const fs = require("fs"); const path = require("path"); /** @@ -35,7 +36,7 @@ function findFiles(dir, extensions) { } } } catch (error) { - core.warning(`Failed to scan directory ${dir}: ${getErrorMessage(error)}`); + safeWarning(`Failed to scan directory ${dir}: ${getErrorMessage(error)}`); } return results; } @@ -94,7 +95,7 @@ function redactBuiltInPatterns(content) { } redactionCount += matches.length; detectedPatterns.push(name); - core.info(`Redacted ${matches.length} occurrence(s) of ${name}`); + safeInfo(`Redacted ${matches.length} occurrence(s) of ${name}`); } } @@ -161,7 +162,7 @@ function processFile(filePath, secretValues) { } return totalRedactions; } catch (error) { - core.warning(`Failed to process file ${filePath}: ${getErrorMessage(error)}`); + safeWarning(`Failed to process file ${filePath}: ${getErrorMessage(error)}`); return 0; } } diff --git a/actions/setup/js/remove_labels.cjs b/actions/setup/js/remove_labels.cjs index 294970b8a6e..4a994127cc5 100644 --- a/actions/setup/js/remove_labels.cjs +++ b/actions/setup/js/remove_labels.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "remove_labels"; @@ -23,7 +24,7 @@ async function main(config = {}) { core.info(`Remove labels configuration: max=${maxCount}`); if (allowedLabels.length > 0) { - core.info(`Allowed labels to remove: ${allowedLabels.join(", ")}`); + safeInfo(`Allowed labels to remove: ${allowedLabels.join(", ")}`); } // Track how many items we've processed for max limit @@ -61,7 +62,7 @@ async function main(config = {}) { const contextType = context.payload?.pull_request ? "pull request" : "issue"; const requestedLabels = message.labels ?? []; - core.info(`Requested labels to remove: ${JSON.stringify(requestedLabels)}`); + safeInfo(`Requested labels to remove: ${JSON.stringify(requestedLabels)}`); // If no labels provided, return a helpful message with allowed labels if configured if (!requestedLabels || requestedLabels.length === 0) { @@ -92,7 +93,7 @@ async function main(config = {}) { }; } // For other validation errors, return error - core.warning(`Label validation failed: ${labelsResult.error}`); + safeWarning(`Label validation failed: ${labelsResult.error}`); return { success: false, error: labelsResult.error ?? "Invalid labels", @@ -111,7 +112,7 @@ async function main(config = {}) { }; } - core.info(`Removing ${uniqueLabels.length} labels from ${contextType} #${itemNumber}: ${JSON.stringify(uniqueLabels)}`); + safeInfo(`Removing ${uniqueLabels.length} labels from ${contextType} #${itemNumber}: ${JSON.stringify(uniqueLabels)}`); // Track successfully removed labels const removedLabels = []; @@ -126,21 +127,21 @@ async function main(config = {}) { name: label, }); removedLabels.push(label); - core.info(`Removed label "${label}" from ${contextType} #${itemNumber}`); + safeInfo(`Removed label "${label}" from ${contextType} #${itemNumber}`); } catch (error) { // Label might not exist on the issue/PR - this is not a failure const errorMessage = getErrorMessage(error); if (errorMessage.includes("Label does not exist") || errorMessage.includes("404")) { - core.info(`Label "${label}" was not present on ${contextType} #${itemNumber}, skipping`); + safeInfo(`Label "${label}" was not present on ${contextType} #${itemNumber}, skipping`); } else { - core.warning(`Failed to remove label "${label}": ${errorMessage}`); + safeWarning(`Failed to remove label "${label}": ${errorMessage}`); failedLabels.push({ label, error: errorMessage }); } } } if (removedLabels.length > 0) { - core.info(`Successfully removed ${removedLabels.length} labels from ${contextType} #${itemNumber}`); + safeInfo(`Successfully removed ${removedLabels.length} labels from ${contextType} #${itemNumber}`); } return { diff --git a/actions/setup/js/resolve_mentions.cjs b/actions/setup/js/resolve_mentions.cjs index 3b201d8bdf2..ed9b4e7b0fc 100644 --- a/actions/setup/js/resolve_mentions.cjs +++ b/actions/setup/js/resolve_mentions.cjs @@ -10,6 +10,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * @property {number} resolvedCount - Number of mentions resolved via API * @property {boolean} limitExceeded - Whether the 50 mention limit was exceeded */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Extract all @mentions from text @@ -77,7 +78,7 @@ async function getRecentCollaborators(owner, repo, github, core) { return allowedMap; } catch (error) { - core.warning(`Failed to fetch recent collaborators: ${getErrorMessage(error)}`); + safeWarning(`Failed to fetch recent collaborators: ${getErrorMessage(error)}`); return new Map(); } } diff --git a/actions/setup/js/resolve_mentions_from_payload.cjs b/actions/setup/js/resolve_mentions_from_payload.cjs index 63b4a13a81d..e3c40958908 100644 --- a/actions/setup/js/resolve_mentions_from_payload.cjs +++ b/actions/setup/js/resolve_mentions_from_payload.cjs @@ -4,6 +4,7 @@ /** * Helper module for resolving allowed mentions from GitHub event payloads */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { resolveMentionsLazily, isPayloadUserBot } = require("./resolve_mentions.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -188,7 +189,7 @@ async function resolveAllowedMentionsFromPayload(context, github, core, mentions return allowedMentions; } catch (error) { - core.warning(`Failed to resolve mentions for output collector: ${getErrorMessage(error)}`); + safeWarning(`Failed to resolve mentions for output collector: ${getErrorMessage(error)}`); // Return empty array on error return []; } diff --git a/actions/setup/js/runtime_import.cjs b/actions/setup/js/runtime_import.cjs index 0a1eb5aa182..95c188769d9 100644 --- a/actions/setup/js/runtime_import.cjs +++ b/actions/setup/js/runtime_import.cjs @@ -18,6 +18,7 @@ const http = require("http"); * @param {string} content - The file content to check * @returns {boolean} - True if content starts with front matter */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); function hasFrontMatter(content) { return content.trimStart().startsWith("---\n") || content.trimStart().startsWith("---\r\n"); } @@ -355,7 +356,7 @@ function evaluateExpression(expr) { } catch (error) { // If evaluation fails, log but don't throw const errorMessage = error instanceof Error ? error.message : String(error); - core.warning(`Failed to evaluate expression "${trimmed}": ${errorMessage}`); + safeWarning(`Failed to evaluate expression "${trimmed}": ${errorMessage}`); } } @@ -522,7 +523,7 @@ async function processUrlImport(url, optional, startLine, endLine) { } catch (error) { if (optional) { const errorMessage = getErrorMessage(error); - core.warning(`Optional runtime import URL failed: ${url}: ${errorMessage}`); + safeWarning(`Optional runtime import URL failed: ${url}: ${errorMessage}`); return ""; } throw error; diff --git a/actions/setup/js/safe_output_handler_manager.cjs b/actions/setup/js/safe_output_handler_manager.cjs index de615d7f8dc..3660a88643c 100644 --- a/actions/setup/js/safe_output_handler_manager.cjs +++ b/actions/setup/js/safe_output_handler_manager.cjs @@ -8,6 +8,7 @@ * It reads configuration, loads the appropriate handlers for enabled safe output types, * and processes messages from the agent output file while maintaining a shared temporary ID map. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -111,7 +112,7 @@ async function loadHandlers(config) { // This is a fatal error - the handler is misconfigured // Re-throw to fail the step rather than continuing const error = new Error(`Handler ${type} main() did not return a function - expected a message handler function but got ${typeof messageHandler}`); - core.error(`✗ Fatal error loading handler ${type}: ${error.message}`); + safeError(`✗ Fatal error loading handler ${type}: ${error.message}`); throw error; } @@ -127,14 +128,14 @@ async function loadHandlers(config) { throw error; } // For other errors (e.g., module not found), log warning and continue - core.warning(`Failed to load handler for ${type}: ${errorMessage}`); + safeWarning(`Failed to load handler for ${type}: ${errorMessage}`); } } else { core.debug(`Handler not enabled: ${type}`); } } - core.info(`Loaded ${messageHandlers.size} handler(s)`); + safeInfo(`Loaded ${messageHandlers.size} handler(s)`); return messageHandlers; } @@ -178,7 +179,7 @@ function collectMissingMessages(messages) { } } - core.info(`Collected ${missingTools.length} missing tool(s), ${missingData.length} missing data item(s), and ${noopMessages.length} noop message(s)`); + safeInfo(`Collected ${missingTools.length} missing tool(s), ${missingData.length} missing data item(s), and ${noopMessages.length} noop message(s)`); return { missingTools, missingData, noopMessages }; } @@ -212,7 +213,7 @@ async function processMessages(messageHandlers, messages) { /** @type {Array<{type: string, message: any, messageIndex: number, handler: Function}>} */ const deferredMessages = []; - core.info(`Processing ${messages.length} message(s) in order of appearance...`); + safeInfo(`Processing ${messages.length} message(s) in order of appearance...`); // Process messages in order of appearance for (let i = 0; i < messages.length; i++) { @@ -230,7 +231,7 @@ async function processMessages(messageHandlers, messages) { // Check if this message type is handled by a standalone step if (STANDALONE_STEP_TYPES.has(messageType)) { // Silently skip - this is handled by a dedicated step - core.debug(`Message ${i + 1} (${messageType}) will be handled by standalone step`); + safeDebug(`Message ${i + 1} (${messageType}) will be handled by standalone step`); results.push({ type: messageType, messageIndex: i, @@ -255,7 +256,7 @@ async function processMessages(messageHandlers, messages) { } try { - core.info(`Processing message ${i + 1}/${messages.length}: ${messageType}`); + safeInfo(`Processing message ${i + 1}/${messages.length}: ${messageType}`); // Convert Map to plain object for handler const resolvedTemporaryIds = Object.fromEntries(temporaryIdMap); @@ -269,7 +270,7 @@ async function processMessages(messageHandlers, messages) { // Check if the handler explicitly returned a failure if (result && result.success === false && !result.deferred) { const errorMsg = result.error || "Handler returned success: false"; - core.error(`✗ Message ${i + 1} (${messageType}) failed: ${errorMsg}`); + safeError(`✗ Message ${i + 1} (${messageType}) failed: ${errorMsg}`); results.push({ type: messageType, messageIndex: i, @@ -281,7 +282,7 @@ async function processMessages(messageHandlers, messages) { // Check if the operation was deferred due to unresolved temporary IDs if (result && result.deferred === true) { - core.info(`⏸ Message ${i + 1} (${messageType}) deferred - will retry after first pass`); + safeInfo(`⏸ Message ${i + 1} (${messageType}) deferred - will retry after first pass`); deferredMessages.push({ type: messageType, message: message, @@ -354,9 +355,9 @@ async function processMessages(messageHandlers, messages) { result, }); - core.info(`✓ Message ${i + 1} (${messageType}) completed successfully`); + safeInfo(`✓ Message ${i + 1} (${messageType}) completed successfully`); } catch (error) { - core.error(`✗ Message ${i + 1} (${messageType}) failed: ${getErrorMessage(error)}`); + safeError(`✗ Message ${i + 1} (${messageType}) failed: ${getErrorMessage(error)}`); results.push({ type: messageType, messageIndex: i, @@ -373,11 +374,11 @@ async function processMessages(messageHandlers, messages) { // with unresolved IDs to enable full synthetic update resolution. if (deferredMessages.length > 0) { core.info(`\n=== Retrying Deferred Messages ===`); - core.info(`Found ${deferredMessages.length} deferred message(s) to retry`); + safeInfo(`Found ${deferredMessages.length} deferred message(s) to retry`); for (const deferred of deferredMessages) { try { - core.info(`Retrying message ${deferred.messageIndex + 1}/${messages.length}: ${deferred.type}`); + safeInfo(`Retrying message ${deferred.messageIndex + 1}/${messages.length}: ${deferred.type}`); // Convert Map to plain object for handler const resolvedTemporaryIds = Object.fromEntries(temporaryIdMap); @@ -391,7 +392,7 @@ async function processMessages(messageHandlers, messages) { // Check if the handler explicitly returned a failure if (result && result.success === false && !result.deferred) { const errorMsg = result.error || "Handler returned success: false"; - core.error(`✗ Retry of message ${deferred.messageIndex + 1} (${deferred.type}) failed: ${errorMsg}`); + safeError(`✗ Retry of message ${deferred.messageIndex + 1} (${deferred.type}) failed: ${errorMsg}`); // Update the result to error const resultIndex = results.findIndex(r => r.messageIndex === deferred.messageIndex); if (resultIndex >= 0) { @@ -403,14 +404,14 @@ async function processMessages(messageHandlers, messages) { // Check if still deferred if (result && result.deferred === true) { - core.warning(`⏸ Message ${deferred.messageIndex + 1} (${deferred.type}) still deferred - some temporary IDs remain unresolved`); + safeWarning(`⏸ Message ${deferred.messageIndex + 1} (${deferred.type}) still deferred - some temporary IDs remain unresolved`); // Update the existing result entry const resultIndex = results.findIndex(r => r.messageIndex === deferred.messageIndex); if (resultIndex >= 0) { results[resultIndex].result = result; } } else { - core.info(`✓ Message ${deferred.messageIndex + 1} (${deferred.type}) completed on retry`); + safeInfo(`✓ Message ${deferred.messageIndex + 1} (${deferred.type}) completed on retry`); // If handler returned a temp ID mapping, add it to our map // This ensures that sub-issues created during deferred retry have their temporary IDs @@ -449,7 +450,7 @@ async function processMessages(messageHandlers, messages) { } } } catch (error) { - core.error(`✗ Retry of message ${deferred.messageIndex + 1} (${deferred.type}) failed: ${getErrorMessage(error)}`); + safeError(`✗ Retry of message ${deferred.messageIndex + 1} (${deferred.type}) failed: ${getErrorMessage(error)}`); // Update the result to error const resultIndex = results.findIndex(r => r.messageIndex === deferred.messageIndex); if (resultIndex >= 0) { @@ -672,7 +673,7 @@ async function processSyntheticUpdates(github, context, trackedOutputs, temporar core.debug(`Unknown output type: ${tracked.type}`); } } catch (error) { - core.warning(`✗ Failed to update ${tracked.type} ${tracked.result.repo}#${tracked.result.number}: ${getErrorMessage(error)}`); + safeWarning(`✗ Failed to update ${tracked.type} ${tracked.result.repo}#${tracked.result.number}: ${getErrorMessage(error)}`); } } else { core.debug(`Output ${tracked.result.repo}#${tracked.result.number} still has unresolved temporary IDs`); diff --git a/actions/setup/js/safe_output_helpers.cjs b/actions/setup/js/safe_output_helpers.cjs index 2a925df0202..bb84f230e6e 100644 --- a/actions/setup/js/safe_output_helpers.cjs +++ b/actions/setup/js/safe_output_helpers.cjs @@ -5,6 +5,7 @@ * Shared helper functions for safe-output scripts * Provides common validation and target resolution logic */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Parse a comma-separated list of allowed items from environment variable @@ -251,7 +252,7 @@ function loadCustomSafeOutputJobTypes() { } catch (error) { if (typeof core !== "undefined") { const { getErrorMessage } = require("./error_helpers.cjs"); - core.warning(`Failed to parse GH_AW_SAFE_OUTPUT_JOBS: ${getErrorMessage(error)}`); + safeWarning(`Failed to parse GH_AW_SAFE_OUTPUT_JOBS: ${getErrorMessage(error)}`); } return new Set(); } diff --git a/actions/setup/js/safe_output_processor.cjs b/actions/setup/js/safe_output_processor.cjs index 8ddcf398901..1fed33fc66f 100644 --- a/actions/setup/js/safe_output_processor.cjs +++ b/actions/setup/js/safe_output_processor.cjs @@ -5,6 +5,7 @@ * Shared processor for safe-output scripts * Provides common pipeline: load agent output, handle staged mode, parse config, resolve target */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { loadAgentOutput } = require("./load_agent_output.cjs"); const { generateStagedPreview } = require("./staged_preview.cjs"); @@ -111,12 +112,12 @@ async function processSafeOutput(config, stagedPreviewOptions, handlerConfig = n // Parse allowed items from handlerConfig allowed = handlerConfig.allowed || handlerConfig.allowed_labels || handlerConfig.allowed_repos; if (Array.isArray(allowed)) { - core.info(`Allowed ${itemTypeName}s: ${JSON.stringify(allowed)}`); + safeInfo(`Allowed ${itemTypeName}s: ${JSON.stringify(allowed)}`); } else if (typeof allowed === "string") { allowed = parseAllowedItems(allowed); - core.info(`Allowed ${itemTypeName}s: ${JSON.stringify(allowed)}`); + safeInfo(`Allowed ${itemTypeName}s: ${JSON.stringify(allowed)}`); } else { - core.info(`No ${itemTypeName} restrictions - any ${itemTypeName}s are allowed`); + safeInfo(`No ${itemTypeName} restrictions - any ${itemTypeName}s are allowed`); } // Get max count from handlerConfig @@ -125,7 +126,7 @@ async function processSafeOutput(config, stagedPreviewOptions, handlerConfig = n // Get target from handlerConfig target = handlerConfig.target || "triggering"; - core.info(`${displayName} target configuration: ${target}`); + safeInfo(`${displayName} target configuration: ${target}`); } else { // Fall back to reading from config file + env vars (backward compatibility) const safeOutputConfig = getSafeOutputConfig(configKey); @@ -134,9 +135,9 @@ async function processSafeOutput(config, stagedPreviewOptions, handlerConfig = n const allowedEnvValue = envVars.allowed ? process.env[envVars.allowed] : undefined; allowed = parseAllowedItems(allowedEnvValue) || safeOutputConfig.allowed; if (allowed) { - core.info(`Allowed ${itemTypeName}s: ${JSON.stringify(allowed)}`); + safeInfo(`Allowed ${itemTypeName}s: ${JSON.stringify(allowed)}`); } else { - core.info(`No ${itemTypeName} restrictions - any ${itemTypeName}s are allowed`); + safeInfo(`No ${itemTypeName} restrictions - any ${itemTypeName}s are allowed`); } // Parse max count (env takes priority, then config) @@ -151,7 +152,7 @@ async function processSafeOutput(config, stagedPreviewOptions, handlerConfig = n // Get target configuration target = envVars.target ? process.env[envVars.target] || "triggering" : "triggering"; - core.info(`${displayName} target configuration: ${target}`); + safeInfo(`${displayName} target configuration: ${target}`); } // For multiple items, return early without target resolution diff --git a/actions/setup/js/safe_output_summary.cjs b/actions/setup/js/safe_output_summary.cjs index 7e3055379be..30e5c6e5060 100644 --- a/actions/setup/js/safe_output_summary.cjs +++ b/actions/setup/js/safe_output_summary.cjs @@ -7,6 +7,7 @@ * This module provides functionality to generate step summaries for safe-output messages. * Each processed safe-output generates a summary enclosed in a
section. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { displayFileContent } = require("./display_file_helpers.cjs"); @@ -109,7 +110,7 @@ async function writeSafeOutputSummaries(results, messages) { displayFileContent(safeOutputsFile, "safe-outputs.jsonl", 5000); } } catch (error) { - core.debug(`Could not read raw safe-output file: ${error instanceof Error ? error.message : String(error)}`); + safeDebug(`Could not read raw safe-output file: ${error instanceof Error ? error.message : String(error)}`); } } } @@ -141,7 +142,7 @@ async function writeSafeOutputSummaries(results, messages) { await core.summary.addRaw(summaryContent).write(); core.info(`📝 Safe output summaries written to step summary`); } catch (error) { - core.warning(`Failed to write safe output summaries: ${error instanceof Error ? error.message : String(error)}`); + safeWarning(`Failed to write safe output summaries: ${error instanceof Error ? error.message : String(error)}`); } } diff --git a/actions/setup/js/safe_output_topological_sort.cjs b/actions/setup/js/safe_output_topological_sort.cjs index 1198d516fdd..ad2e5447f15 100644 --- a/actions/setup/js/safe_output_topological_sort.cjs +++ b/actions/setup/js/safe_output_topological_sort.cjs @@ -11,6 +11,7 @@ * This enables resolution of all temporary IDs in a single pass for acyclic * dependency graphs (graphs without loops). */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { extractTemporaryIdReferences, getCreatedTemporaryId } = require("./temporary_id.cjs"); @@ -216,7 +217,7 @@ function topologicalSort(messages, dependencies) { } if (typeof core !== "undefined") { - core.warning(`Topological sort incomplete: ${sorted.length}/${messages.length} messages sorted. ` + `Messages ${unsorted.join(", ")} may be part of a dependency cycle.`); + safeWarning(`Topological sort incomplete: ${sorted.length}/${messages.length} messages sorted. ` + `Messages ${unsorted.join(", ")} may be part of a dependency cycle.`); } } @@ -243,7 +244,7 @@ function sortSafeOutputMessages(messages) { if (typeof core !== "undefined") { const messagesWithDeps = Array.from(dependencies.entries()).filter(([_, deps]) => deps.size > 0); - core.info(`Dependency analysis: ${providers.size} message(s) create temporary IDs, ` + `${messagesWithDeps.length} message(s) have dependencies`); + safeInfo(`Dependency analysis: ${providers.size} message(s) create temporary IDs, ` + `${messagesWithDeps.length} message(s) have dependencies`); } // Check for cycles @@ -255,7 +256,7 @@ function sortSafeOutputMessages(messages) { const tempId = getCreatedTemporaryId(msg); return `${i} (${msg.type}${tempId ? `, creates ${tempId}` : ""})`; }); - core.warning(`Dependency cycle detected in safe output messages: ${cycleMessages.join(" -> ")}. ` + `Temporary IDs may not resolve correctly. Messages will be processed in original order.`); + safeWarning(`Dependency cycle detected in safe output messages: ${cycleMessages.join(" -> ")}. ` + `Temporary IDs may not resolve correctly. Messages will be processed in original order.`); } // Return original order if there's a cycle return messages; @@ -271,7 +272,7 @@ function sortSafeOutputMessages(messages) { // Check if order changed const orderChanged = sortedIndices.some((idx, i) => idx !== i); if (orderChanged) { - core.info(`Topological sort reordered ${messages.length} message(s) to resolve temporary ID dependencies. ` + `New order: [${sortedIndices.join(", ")}]`); + safeInfo(`Topological sort reordered ${messages.length} message(s) to resolve temporary ID dependencies. ` + `New order: [${sortedIndices.join(", ")}]`); } else { core.info(`Topological sort: Messages already in optimal order (no reordering needed)`); } diff --git a/actions/setup/js/safe_output_unified_handler_manager.cjs b/actions/setup/js/safe_output_unified_handler_manager.cjs index 1ac542fb413..67aab23f672 100644 --- a/actions/setup/js/safe_output_unified_handler_manager.cjs +++ b/actions/setup/js/safe_output_unified_handler_manager.cjs @@ -13,6 +13,7 @@ * * The @actions/github package is installed at runtime via setup.sh to enable Octokit instantiation. */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -199,7 +200,7 @@ async function loadHandlers(configs, projectOctokit = null) { if (typeof messageHandler !== "function") { const error = new Error(`Handler ${type} main() did not return a function - expected a message handler function but got ${typeof messageHandler}`); - core.error(`✗ Fatal error loading handler ${type}: ${error.message}`); + safeError(`✗ Fatal error loading handler ${type}: ${error.message}`); throw error; } @@ -213,7 +214,7 @@ async function loadHandlers(configs, projectOctokit = null) { if (errorMessage.includes("did not return a function")) { throw error; } - core.warning(`Failed to load regular handler for ${type}: ${errorMessage}`); + safeWarning(`Failed to load regular handler for ${type}: ${errorMessage}`); } } } @@ -236,7 +237,7 @@ async function loadHandlers(configs, projectOctokit = null) { if (typeof messageHandler !== "function") { const error = new Error(`Handler ${type} main() did not return a function - expected a message handler function but got ${typeof messageHandler}`); - core.error(`✗ Fatal error loading handler ${type}: ${error.message}`); + safeError(`✗ Fatal error loading handler ${type}: ${error.message}`); throw error; } @@ -250,12 +251,12 @@ async function loadHandlers(configs, projectOctokit = null) { if (errorMessage.includes("did not return a function")) { throw error; } - core.warning(`Failed to load project handler for ${type}: ${errorMessage}`); + safeWarning(`Failed to load project handler for ${type}: ${errorMessage}`); } } } - core.info(`Loaded ${messageHandlers.size} handler(s) total`); + safeInfo(`Loaded ${messageHandlers.size} handler(s) total`); return messageHandlers; } @@ -299,7 +300,7 @@ function collectMissingMessages(messages) { } } - core.info(`Collected ${missingTools.length} missing tool(s), ${missingData.length} missing data item(s), and ${noopMessages.length} noop message(s)`); + safeInfo(`Collected ${missingTools.length} missing tool(s), ${missingData.length} missing data item(s), and ${noopMessages.length} noop message(s)`); return { missingTools, missingData, noopMessages }; } @@ -360,7 +361,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) /** @type {Array<{type: string, message: any, messageIndex: number, handler: Function}>} */ const deferredMessages = []; - core.info(`Processing ${sortedMessages.length} message(s) in topologically sorted order...`); + safeInfo(`Processing ${sortedMessages.length} message(s) in topologically sorted order...`); // Process messages in topologically sorted order for (let i = 0; i < sortedMessages.length; i++) { @@ -378,7 +379,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) // Check if this message type is handled by a standalone step if (STANDALONE_STEP_TYPES.has(messageType)) { // Silently skip - this is handled by a dedicated step - core.debug(`Message ${i + 1} (${messageType}) will be handled by standalone step`); + safeDebug(`Message ${i + 1} (${messageType}) will be handled by standalone step`); results.push({ type: messageType, messageIndex: i, @@ -392,7 +393,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) // Check if this message type is a custom safe output job if (customSafeOutputJobTypes.has(messageType)) { // Silently skip - this is handled by a custom safe output job - core.debug(`Message ${i + 1} (${messageType}) will be handled by custom safe output job`); + safeDebug(`Message ${i + 1} (${messageType}) will be handled by custom safe output job`); results.push({ type: messageType, messageIndex: i, @@ -417,7 +418,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) } try { - core.info(`Processing message ${i + 1}/${messages.length}: ${messageType}`); + safeInfo(`Processing message ${i + 1}/${messages.length}: ${messageType}`); // Record the temp ID map size before processing to detect new IDs const tempIdMapSizeBefore = temporaryIdMap.size; @@ -441,7 +442,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) // Check if the handler explicitly returned a failure if (result && result.success === false && !result.deferred) { const errorMsg = result.error || "Handler returned success: false"; - core.error(`✗ Message ${i + 1} (${messageType}) failed: ${errorMsg}`); + safeError(`✗ Message ${i + 1} (${messageType}) failed: ${errorMsg}`); results.push({ type: messageType, messageIndex: i, @@ -453,7 +454,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) // Check if the operation was deferred due to unresolved temporary IDs if (result && result.deferred === true) { - core.info(`⏸ Message ${i + 1} (${messageType}) deferred - will retry after first pass`); + safeInfo(`⏸ Message ${i + 1} (${messageType}) deferred - will retry after first pass`); deferredMessages.push({ type: messageType, message: message, @@ -486,7 +487,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) temporaryIdMap.set(normalizedTempId, { projectUrl: result.projectUrl, }); - core.info(`✓ Stored project mapping: ${message.temporary_id} -> ${result.projectUrl}`); + safeInfo(`✓ Stored project mapping: ${message.temporary_id} -> ${result.projectUrl}`); } // If this was an update_project that created a draft issue, store the draft item mapping @@ -544,9 +545,9 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) result, }); - core.info(`✓ Message ${i + 1} (${messageType}) completed successfully`); + safeInfo(`✓ Message ${i + 1} (${messageType}) completed successfully`); } catch (error) { - core.error(`✗ Message ${i + 1} (${messageType}) failed: ${getErrorMessage(error)}`); + safeError(`✗ Message ${i + 1} (${messageType}) failed: ${getErrorMessage(error)}`); results.push({ type: messageType, messageIndex: i, @@ -563,11 +564,11 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) // with unresolved IDs to enable full synthetic update resolution. if (deferredMessages.length > 0) { core.info(`\n=== Retrying Deferred Messages ===`); - core.info(`Found ${deferredMessages.length} deferred message(s) to retry`); + safeInfo(`Found ${deferredMessages.length} deferred message(s) to retry`); for (const deferred of deferredMessages) { try { - core.info(`Retrying message ${deferred.messageIndex + 1}/${messages.length}: ${deferred.type}`); + safeInfo(`Retrying message ${deferred.messageIndex + 1}/${messages.length}: ${deferred.type}`); // Convert Map to plain object for handler const resolvedTemporaryIds = Object.fromEntries(temporaryIdMap); @@ -581,7 +582,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) // Check if the handler explicitly returned a failure if (result && result.success === false && !result.deferred) { const errorMsg = result.error || "Handler returned success: false"; - core.error(`✗ Retry of message ${deferred.messageIndex + 1} (${deferred.type}) failed: ${errorMsg}`); + safeError(`✗ Retry of message ${deferred.messageIndex + 1} (${deferred.type}) failed: ${errorMsg}`); // Update the result to error const resultIndex = results.findIndex(r => r.messageIndex === deferred.messageIndex); if (resultIndex >= 0) { @@ -593,14 +594,14 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) // Check if still deferred if (result && result.deferred === true) { - core.warning(`⏸ Message ${deferred.messageIndex + 1} (${deferred.type}) still deferred - some temporary IDs remain unresolved`); + safeWarning(`⏸ Message ${deferred.messageIndex + 1} (${deferred.type}) still deferred - some temporary IDs remain unresolved`); // Update the existing result entry const resultIndex = results.findIndex(r => r.messageIndex === deferred.messageIndex); if (resultIndex >= 0) { results[resultIndex].result = result; } } else { - core.info(`✓ Message ${deferred.messageIndex + 1} (${deferred.type}) completed on retry`); + safeInfo(`✓ Message ${deferred.messageIndex + 1} (${deferred.type}) completed on retry`); // If handler returned a temp ID mapping, add it to our map // This ensures that sub-issues created during deferred retry have their temporary IDs @@ -639,7 +640,7 @@ async function processMessages(messageHandlers, messages, projectOctokit = null) } } } catch (error) { - core.error(`✗ Retry of message ${deferred.messageIndex + 1} (${deferred.type}) failed: ${getErrorMessage(error)}`); + safeError(`✗ Retry of message ${deferred.messageIndex + 1} (${deferred.type}) failed: ${getErrorMessage(error)}`); // Update the result to error const resultIndex = results.findIndex(r => r.messageIndex === deferred.messageIndex); if (resultIndex >= 0) { @@ -862,7 +863,7 @@ async function processSyntheticUpdates(github, context, trackedOutputs, temporar core.debug(`Unknown output type: ${tracked.type}`); } } catch (error) { - core.warning(`✗ Failed to update ${tracked.type} ${tracked.result.repo}#${tracked.result.number}: ${getErrorMessage(error)}`); + safeWarning(`✗ Failed to update ${tracked.type} ${tracked.result.repo}#${tracked.result.number}: ${getErrorMessage(error)}`); } } else { core.debug(`Output ${tracked.result.repo}#${tracked.result.number} still has unresolved temporary IDs`); diff --git a/actions/setup/js/safe_output_validator.cjs b/actions/setup/js/safe_output_validator.cjs index 3294a80169d..ceb8831451c 100644 --- a/actions/setup/js/safe_output_validator.cjs +++ b/actions/setup/js/safe_output_validator.cjs @@ -9,6 +9,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); * Load and parse the safe outputs configuration from config.json * @returns {object} The parsed configuration object */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); function loadSafeOutputsConfig() { const configPath = "/opt/gh-aw/safeoutputs/config.json"; try { @@ -19,7 +20,7 @@ function loadSafeOutputsConfig() { const configContent = fs.readFileSync(configPath, "utf8"); return JSON.parse(configContent); } catch (error) { - core.warning(`Failed to load config: ${getErrorMessage(error)}`); + safeWarning(`Failed to load config: ${getErrorMessage(error)}`); return {}; } } @@ -116,7 +117,7 @@ function validateLabels(labels, allowedLabels = undefined, maxCount = 3) { // Apply max count limit if (uniqueLabels.length > maxCount) { - core.info(`Too many labels (${uniqueLabels.length}), limiting to ${maxCount}`); + safeInfo(`Too many labels (${uniqueLabels.length}), limiting to ${maxCount}`); return { valid: true, value: uniqueLabels.slice(0, maxCount) }; } diff --git a/actions/setup/js/sanitize_content_core.cjs b/actions/setup/js/sanitize_content_core.cjs index 372a2bb7565..56ca5c0976a 100644 --- a/actions/setup/js/sanitize_content_core.cjs +++ b/actions/setup/js/sanitize_content_core.cjs @@ -5,6 +5,7 @@ * mention resolution or filtering. It's designed to be imported by both * sanitize_content.cjs (full version) and sanitize_incoming_text.cjs (minimal version). */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Module-level set to collect redacted URL domains across sanitization calls. @@ -305,8 +306,8 @@ function neutralizeAllMentions(s) { // This prevents bypass patterns like "test_@user" from escaping sanitization return s.replace(/(^|[^A-Za-z0-9`])@([A-Za-z0-9](?:[A-Za-z0-9_-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { // Log when a mention is escaped to help debug issues - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + if (typeof core !== "undefined" && typeof core.info === "function") { + safeInfo(`Escaped mention: @${p2} (not in allowed list)`); } return `${p1}\`@${p2}\``; }); @@ -535,8 +536,8 @@ function neutralizeGitHubReferences(s, allowedRepos) { const refText = owner && repo ? `${owner}/${repo}#${issueNum}` : `#${issueNum}`; // Log when a reference is escaped - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped GitHub reference: ${refText} (not in allowed list)`); + if (typeof core !== "undefined" && typeof core.info === "function") { + safeInfo(`Escaped GitHub reference: ${refText} (not in allowed list)`); } return `${prefix}\`${refText}\``; diff --git a/actions/setup/js/sanitize_output.cjs b/actions/setup/js/sanitize_output.cjs index 4b07e46f65a..457f61e4687 100644 --- a/actions/setup/js/sanitize_output.cjs +++ b/actions/setup/js/sanitize_output.cjs @@ -6,6 +6,7 @@ * @param {string} content - The content to sanitize * @returns {string} The sanitized content */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { sanitizeContent, writeRedactedDomainsLog } = require("./sanitize_content.cjs"); async function main() { @@ -29,7 +30,7 @@ async function main() { core.setOutput("output", ""); } else { const sanitizedContent = sanitizeContent(outputContent); - core.info(`Collected agentic output (sanitized): ${sanitizedContent.substring(0, 200)}${sanitizedContent.length > 200 ? "..." : ""}`); + safeInfo(`Collected agentic output (sanitized): ${sanitizedContent.substring(0, 200)}${sanitizedContent.length > 200 ? "..." : ""}`); core.setOutput("output", sanitizedContent); } diff --git a/actions/setup/js/sanitized_logging.cjs b/actions/setup/js/sanitized_logging.cjs new file mode 100644 index 00000000000..3e480c42ee5 --- /dev/null +++ b/actions/setup/js/sanitized_logging.cjs @@ -0,0 +1,81 @@ +// @ts-check + +/** + * Neutralize GitHub Actions workflow commands in text by escaping double colons. + * This prevents injection of commands like ::set-output::, ::warning::, ::error::, etc. + * + * GitHub Actions workflow commands have the format: + * ::command parameter1={data},parameter2={data}::{command value} + * + * IMPORTANT: Workflow commands are only recognized when :: appears at the start of a line. + * This function only neutralizes :: at line boundaries, preserving :: in normal text + * (e.g., "12:30", "C++::function", "IPv6 ::1"). + * + * By replacing :: with :\u200B: (zero-width space) only at line starts, we prevent + * command injection while maintaining readability and not affecting legitimate uses. + * + * @param {string} text - The text to neutralize + * @returns {string} The neutralized text + */ +function neutralizeWorkflowCommands(text) { + if (typeof text !== "string") { + return String(text); + } + // Replace :: only at the start of a line (after start or newline) + // The ^ anchor with 'm' flag matches at the start of the string AND after each newline + // This matches GitHub Actions' actual command parsing behavior + // Preserves :: in normal text like "12:30", "C++::function", etc. + return text.replace(/^::/gm, ":\u200B:"); +} + +/** + * Sanitized wrapper for core.info that neutralizes workflow commands in user-generated content. + * Use this instead of core.info() when logging user-generated text that might contain + * malicious workflow commands. + * + * @param {string} message - The message to log (will be sanitized) + */ +function safeInfo(message) { + core.info(neutralizeWorkflowCommands(message)); +} + +/** + * Sanitized wrapper for core.debug that neutralizes workflow commands in user-generated content. + * Use this instead of core.debug() when logging user-generated text that might contain + * malicious workflow commands. + * + * @param {string} message - The message to log (will be sanitized) + */ +function safeDebug(message) { + core.debug(neutralizeWorkflowCommands(message)); +} + +/** + * Sanitized wrapper for core.warning that neutralizes workflow commands in user-generated content. + * Use this instead of core.warning() when logging user-generated text that might contain + * malicious workflow commands. + * + * @param {string} message - The message to log (will be sanitized) + */ +function safeWarning(message) { + core.warning(neutralizeWorkflowCommands(message)); +} + +/** + * Sanitized wrapper for core.error that neutralizes workflow commands in user-generated content. + * Use this instead of core.error() when logging user-generated text that might contain + * malicious workflow commands. + * + * @param {string} message - The message to log (will be sanitized) + */ +function safeError(message) { + core.error(neutralizeWorkflowCommands(message)); +} + +module.exports = { + neutralizeWorkflowCommands, + safeInfo, + safeDebug, + safeWarning, + safeError, +}; diff --git a/actions/setup/js/sanitized_logging.test.cjs b/actions/setup/js/sanitized_logging.test.cjs new file mode 100644 index 00000000000..0a9fbb5833d --- /dev/null +++ b/actions/setup/js/sanitized_logging.test.cjs @@ -0,0 +1,254 @@ +// @ts-check +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { neutralizeWorkflowCommands, safeInfo, safeDebug, safeWarning, safeError } from "./sanitized_logging.cjs"; + +// Mock the global core object +global.core = { + info: vi.fn(), + debug: vi.fn(), + warning: vi.fn(), + error: vi.fn(), +}; + +describe("neutralizeWorkflowCommands", () => { + it("should neutralize double colons at start of line", () => { + const input = "::set-output name=test::value"; + const output = neutralizeWorkflowCommands(input); + // Should replace :: at start with : (zero-width space) : + expect(output).toBe(":\u200B:set-output name=test::value"); + expect(output).not.toBe(input); + }); + + it("should neutralize ::warning:: command at start of line", () => { + const input = "::warning file=app.js,line=1::This is a warning"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(":\u200B:warning file=app.js,line=1::This is a warning"); + }); + + it("should neutralize ::error:: command at start of line", () => { + const input = "::error file=app.js,line=1::This is an error"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(":\u200B:error file=app.js,line=1::This is an error"); + }); + + it("should neutralize ::debug:: command at start of line", () => { + const input = "::debug::Debug message"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(":\u200B:debug::Debug message"); + }); + + it("should neutralize ::group:: and ::endgroup:: commands at line starts", () => { + const input = "::group::My Group\nContent\n::endgroup::"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(":\u200B:group::My Group\nContent\n:\u200B:endgroup::"); + }); + + it("should neutralize ::add-mask:: command at start of line", () => { + const input = "::add-mask::secret123"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(":\u200B:add-mask::secret123"); + }); + + it("should handle text without workflow commands", () => { + const input = "This is a normal message with no commands"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(input); + }); + + it("should handle text with single colons (not workflow commands)", () => { + const input = "Time is 12:30 PM, ratio is 3:1"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(input); + }); + + it("should preserve :: in middle of text (IPv6, C++, etc)", () => { + const input = "IPv6 address ::1, C++ namespace std::vector"; + const output = neutralizeWorkflowCommands(input); + // :: in middle of text should NOT be neutralized + expect(output).toBe(input); + }); + + it("should preserve :: after text on same line", () => { + const input = "Time 12:30 or ratio 3::1 is fine"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(input); + }); + + it("should neutralize workflow command at start but preserve :: in middle", () => { + const input = "::warning::Message about std::vector"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(":\u200B:warning::Message about std::vector"); + }); + + it("should handle multiple workflow commands on separate lines", () => { + const input = "::warning::First\n::error::Second\n::debug::Third"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(":\u200B:warning::First\n:\u200B:error::Second\n:\u200B:debug::Third"); + }); + + it("should not neutralize indented :: patterns", () => { + const input = " ::warning::This is indented"; + const output = neutralizeWorkflowCommands(input); + // Indented :: is not at line start, should be preserved + expect(output).toBe(input); + }); + + it("should neutralize after newline but not in middle of line", () => { + const input = "Some text ::not-command::\n::real-command::value"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe("Some text ::not-command::\n:\u200B:real-command::value"); + }); + + it("should handle empty string", () => { + const input = ""; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(""); + }); + + it("should handle non-string input by converting to string", () => { + const input = 12345; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe("12345"); + }); + + it("should handle null by converting to string", () => { + const input = null; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe("null"); + }); + + it("should handle undefined by converting to string", () => { + const input = undefined; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe("undefined"); + }); + + it("should preserve readability with zero-width space at line start only", () => { + const input = "User message: ::set-output name=token::abc123"; + const output = neutralizeWorkflowCommands(input); + // The zero-width space should be invisible but prevent command execution + // Only the :: in middle of line is preserved + expect(output).toBe("User message: ::set-output name=token::abc123"); + }); + + it("should neutralize workflow command at actual start of string", () => { + const input = "::set-output name=token::abc123"; + const output = neutralizeWorkflowCommands(input); + expect(output).toBe(":\u200B:set-output name=token::abc123"); + }); +}); + +describe("safeInfo", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should call core.info with neutralized message at line start", () => { + const message = "::set-output name=test::value"; + safeInfo(message); + expect(core.info).toHaveBeenCalledWith(":\u200B:set-output name=test::value"); + }); + + it("should handle normal messages without modification", () => { + const message = "This is a normal message"; + safeInfo(message); + expect(core.info).toHaveBeenCalledWith(message); + }); + + it("should handle messages with single colons unchanged", () => { + const message = "Time: 12:30 PM"; + safeInfo(message); + expect(core.info).toHaveBeenCalledWith(message); + }); + + it("should preserve :: in middle of message", () => { + const message = "C++ std::vector is fine"; + safeInfo(message); + expect(core.info).toHaveBeenCalledWith(message); + }); +}); + +describe("safeDebug", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should call core.debug with neutralized message at line start", () => { + const message = "::debug::User input"; + safeDebug(message); + expect(core.debug).toHaveBeenCalledWith(":\u200B:debug::User input"); + }); +}); + +describe("safeWarning", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should call core.warning with neutralized message at line start", () => { + const message = "::warning::Malicious warning"; + safeWarning(message); + expect(core.warning).toHaveBeenCalledWith(":\u200B:warning::Malicious warning"); + }); +}); + +describe("safeError", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should call core.error with neutralized message at line start", () => { + const message = "::error::Malicious error"; + safeError(message); + expect(core.error).toHaveBeenCalledWith(":\u200B:error::Malicious error"); + }); +}); + +describe("Integration tests", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should prevent workflow command injection at start of message", () => { + const userMessage = "::set-output name=hack::compromised"; + safeInfo(userMessage); + const callArg = core.info.mock.calls[0][0]; + + // Verify :: at start is neutralized + expect(callArg).toBe(":\u200B:set-output name=hack::compromised"); + }); + + it("should preserve :: in middle of text", () => { + const userMessage = "No changes needed for std::vector"; + safeInfo(`No-op message: ${userMessage}`); + const callArg = core.info.mock.calls[0][0]; + + // :: in middle should be preserved + expect(callArg).toBe("No-op message: No changes needed for std::vector"); + }); + + it("should prevent workflow command injection in multiline text", () => { + const title = "Bug report\n::add-mask::secret123"; + safeInfo(`Created issue: ${title}`); + const callArg = core.info.mock.calls[0][0]; + + // :: after newline should be neutralized + expect(callArg).toBe("Created issue: Bug report\n:\u200B:add-mask::secret123"); + }); + + it("should handle real-world case with command at line start", () => { + const body = "::warning file=x.js::injected"; + safeInfo(body); + const callArg = core.info.mock.calls[0][0]; + + expect(callArg).toBe(":\u200B:warning file=x.js::injected"); + }); + + it("should preserve legitimate :: usage in logged content", () => { + const content = "IPv6 ::1 and C++::function are preserved"; + safeInfo(content); + const callArg = core.info.mock.calls[0][0]; + + expect(callArg).toBe(content); + }); +}); diff --git a/actions/setup/js/staged_preview.cjs b/actions/setup/js/staged_preview.cjs index 8097800ac35..de38c7360b8 100644 --- a/actions/setup/js/staged_preview.cjs +++ b/actions/setup/js/staged_preview.cjs @@ -11,6 +11,7 @@ * @param {(item: any, index: number) => string} options.renderItem - Function to render each item as markdown * @returns {Promise} */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); async function generateStagedPreview(options) { const { title, description, items, renderItem } = options; @@ -26,7 +27,7 @@ async function generateStagedPreview(options) { try { await core.summary.addRaw(summaryContent).write(); core.info(summaryContent); - core.info(`📝 ${title} preview written to step summary`); + safeInfo(`📝 ${title} preview written to step summary`); } catch (error) { core.setFailed(error instanceof Error ? error : String(error)); } diff --git a/actions/setup/js/sub_issue_helpers.cjs b/actions/setup/js/sub_issue_helpers.cjs index e6bde48909d..d0e7b5986db 100644 --- a/actions/setup/js/sub_issue_helpers.cjs +++ b/actions/setup/js/sub_issue_helpers.cjs @@ -4,6 +4,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); // Maximum number of sub-issues per parent issue +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const MAX_SUB_ISSUES = 64; /** @@ -35,7 +36,7 @@ async function getSubIssueCount(owner, repo, issueNumber) { return result?.repository?.issue?.subIssues?.totalCount || 0; } catch (error) { - core.warning(`Could not check sub-issue count for #${issueNumber}: ${getErrorMessage(error)}`); + safeWarning(`Could not check sub-issue count for #${issueNumber}: ${getErrorMessage(error)}`); return null; } } diff --git a/actions/setup/js/temporary_id.cjs b/actions/setup/js/temporary_id.cjs index 883b458e46a..5bc385e0896 100644 --- a/actions/setup/js/temporary_id.cjs +++ b/actions/setup/js/temporary_id.cjs @@ -9,6 +9,7 @@ const crypto = require("crypto"); * Regex pattern for matching temporary ID references in text * Format: #aw_XXXXXXXXXXXX (aw_ prefix + 12 hex characters) */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; /** @@ -118,7 +119,7 @@ function loadTemporaryIdMap() { return result; } catch (error) { if (typeof core !== "undefined") { - core.warning(`Failed to parse temporary ID map: ${getErrorMessage(error)}`); + safeWarning(`Failed to parse temporary ID map: ${getErrorMessage(error)}`); } return new Map(); } @@ -328,7 +329,7 @@ function loadTemporaryProjectMap() { return result; } catch (error) { if (typeof core !== "undefined") { - core.warning(`Failed to parse temporary project map: ${getErrorMessage(error)}`); + safeWarning(`Failed to parse temporary project map: ${getErrorMessage(error)}`); } return new Map(); } diff --git a/actions/setup/js/unlock-issue.cjs b/actions/setup/js/unlock-issue.cjs index 44924b48ea0..115b406c468 100644 --- a/actions/setup/js/unlock-issue.cjs +++ b/actions/setup/js/unlock-issue.cjs @@ -6,12 +6,13 @@ * This script is used in the conclusion job to ensure the issue is unlocked * after agent workflow execution completes or fails */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); async function main() { // Log actor and event information for debugging - core.info(`Unlock-issue debug: actor=${context.actor}, eventName=${context.eventName}`); + safeInfo(`Unlock-issue debug: actor=${context.actor}, eventName=${context.eventName}`); // Get issue number from context const issueNumber = context.issue.number; @@ -58,7 +59,7 @@ async function main() { core.info(`✅ Successfully unlocked issue #${issueNumber}`); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to unlock issue: ${errorMessage}`); + safeError(`Failed to unlock issue: ${errorMessage}`); core.setFailed(`Failed to unlock issue #${issueNumber}: ${errorMessage}`); } } diff --git a/actions/setup/js/update_activation_comment.cjs b/actions/setup/js/update_activation_comment.cjs index c3525948bb6..452a6df2b19 100644 --- a/actions/setup/js/update_activation_comment.cjs +++ b/actions/setup/js/update_activation_comment.cjs @@ -3,6 +3,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { getMessages } = require("./messages_core.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Update the activation comment with a link to the created pull request or issue @@ -141,7 +142,7 @@ async function updateActivationCommentWithMessage(github, context, core, message return; } catch (error) { // Don't fail the workflow if we can't create the comment - just log a warning - core.warning(`Failed to create append-only comment: ${getErrorMessage(error)}`); + safeWarning(`Failed to create append-only comment: ${getErrorMessage(error)}`); return; } } @@ -154,7 +155,7 @@ async function updateActivationCommentWithMessage(github, context, core, message } core.info(`Updating activation comment ${commentId}`); - core.info(`Updating comment in ${repoOwner}/${repoName}`); + safeInfo(`Updating comment in ${repoOwner}/${repoName}`); // Check if this is a discussion comment (GraphQL node ID format) const isDiscussionComment = commentId.startsWith("DC_"); @@ -236,7 +237,7 @@ async function updateActivationCommentWithMessage(github, context, core, message } } catch (error) { // Don't fail the workflow if we can't update the comment - just log a warning - core.warning(`Failed to update activation comment: ${getErrorMessage(error)}`); + safeWarning(`Failed to update activation comment: ${getErrorMessage(error)}`); } } diff --git a/actions/setup/js/update_activation_comment.test.cjs b/actions/setup/js/update_activation_comment.test.cjs index 38145b21682..4848902d913 100644 --- a/actions/setup/js/update_activation_comment.test.cjs +++ b/actions/setup/js/update_activation_comment.test.cjs @@ -5,7 +5,7 @@ const createTestableFunction = scriptContent => { const beforeMainCall = scriptContent.match(/^([\s\S]*?)\s*module\.exports\s*=\s*{[\s\S]*?};?\s*$/); if (!beforeMainCall) throw new Error("Could not extract script content before module.exports"); let scriptBody = beforeMainCall[1]; - // Mock the error_helpers and messages_core modules + // Mock the error_helpers, messages_core, and sanitized_logging modules const mockRequire = module => { if (module === "./error_helpers.cjs") { return { getErrorMessage: error => (error instanceof Error ? error.message : String(error)) }; @@ -24,6 +24,14 @@ const createTestableFunction = scriptContent => { }, }; } + if (module === "./sanitized_logging.cjs") { + return { + safeInfo: vi.fn(), + safeDebug: vi.fn(), + safeWarning: vi.fn(), + safeError: vi.fn(), + }; + } throw new Error(`Module ${module} not mocked in test`); }; return new Function(`\n const { github, core, context, process } = arguments[0];\n const require = ${mockRequire.toString()};\n \n ${scriptBody}\n \n return { updateActivationComment };\n `); diff --git a/actions/setup/js/update_handler_factory.cjs b/actions/setup/js/update_handler_factory.cjs index f25e866d645..82044cb2c01 100644 --- a/actions/setup/js/update_handler_factory.cjs +++ b/actions/setup/js/update_handler_factory.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTarget } = require("./safe_output_helpers.cjs"); @@ -54,7 +55,7 @@ function createUpdateHandlerFactory(handlerConfig) { } }); - core.info(`Update ${itemTypeName} configuration: ${configParts.join(", ")}`); + safeInfo(`Update ${itemTypeName} configuration: ${configParts.join(", ")}`); // Track state let processedCount = 0; @@ -91,7 +92,7 @@ function createUpdateHandlerFactory(handlerConfig) { } const itemNumber = itemNumberResult.number; - core.info(`Resolved target ${itemTypeName} #${itemNumber} (target config: ${updateTarget})`); + safeInfo(`Resolved target ${itemTypeName} #${itemNumber} (target config: ${updateTarget})`); // Build update data (handler-specific logic) const updateDataResult = buildUpdateData(item, config); @@ -106,7 +107,7 @@ function createUpdateHandlerFactory(handlerConfig) { // Check if buildUpdateData returned a skipped result (for update_pull_request) if (updateDataResult.skipped) { - core.info(`No update fields provided for ${itemTypeName} #${itemNumber} - treating as no-op (skipping update)`); + safeInfo(`No update fields provided for ${itemTypeName} #${itemNumber} - treating as no-op (skipping update)`); return { success: true, skipped: true, @@ -123,7 +124,7 @@ function createUpdateHandlerFactory(handlerConfig) { const hasRawBody = updateData._rawBody !== undefined; if (updateFields.length === 0 && !hasRawBody) { - core.info(`No update fields provided for ${itemTypeName} #${itemNumber} - treating as no-op (skipping update)`); + safeInfo(`No update fields provided for ${itemTypeName} #${itemNumber} - treating as no-op (skipping update)`); return { success: true, skipped: true, @@ -131,18 +132,18 @@ function createUpdateHandlerFactory(handlerConfig) { }; } - core.info(`Updating ${itemTypeName} #${itemNumber} with: ${JSON.stringify(updateFields)}`); + safeInfo(`Updating ${itemTypeName} #${itemNumber} with: ${JSON.stringify(updateFields)}`); // Execute the update try { const updatedItem = await executeUpdate(github, context, itemNumber, updateData); - core.info(`Successfully updated ${itemTypeName} #${itemNumber}: ${updatedItem.html_url || updatedItem.url}`); + safeInfo(`Successfully updated ${itemTypeName} #${itemNumber}: ${updatedItem.html_url || updatedItem.url}`); // Format and return success result return formatSuccessResult(itemNumber, updatedItem); } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to update ${itemTypeName} #${itemNumber}: ${errorMessage}`); + safeError(`Failed to update ${itemTypeName} #${itemNumber}: ${errorMessage}`); return { success: false, error: errorMessage, diff --git a/actions/setup/js/update_issue.cjs b/actions/setup/js/update_issue.cjs index 3c7af9d8fa5..5fc6698dcab 100644 --- a/actions/setup/js/update_issue.cjs +++ b/actions/setup/js/update_issue.cjs @@ -4,6 +4,7 @@ /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "update_issue"; @@ -65,7 +66,7 @@ async function executeIssueUpdate(github, context, issueNumber, updateData) { includeFooter, // Pass footer flag to helper }); - core.info(`Will update body (length: ${apiData.body.length})`); + safeInfo(`Will update body (length: ${apiData.body.length})`); } const { data: issue } = await github.rest.issues.update({ diff --git a/actions/setup/js/update_project.cjs b/actions/setup/js/update_project.cjs index ebdc507ff8d..32947161e1f 100644 --- a/actions/setup/js/update_project.cjs +++ b/actions/setup/js/update_project.cjs @@ -4,6 +4,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { loadTemporaryIdMap, resolveIssueNumber, isTemporaryId, normalizeTemporaryId } = require("./temporary_id.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Normalize agent output keys for update_project. @@ -39,7 +40,7 @@ function normalizeUpdateProjectOutput(value) { */ function logGraphQLError(error, operation) { core.info(`GraphQL Error during: ${operation}`); - core.info(`Message: ${getErrorMessage(error)}`); + safeInfo(`Message: ${getErrorMessage(error)}`); const errorList = Array.isArray(error.errors) ? error.errors : []; const hasInsufficientScopes = errorList.some(e => e?.type === "INSUFFICIENT_SCOPES"); @@ -56,17 +57,17 @@ function logGraphQLError(error, operation) { } if (error.errors) { - core.info(`Errors array (${error.errors.length} error(s)):`); + safeInfo(`Errors array (${error.errors.length} error(s)):`); error.errors.forEach((err, idx) => { - core.info(` [${idx + 1}] ${err.message}`); - if (err.type) core.info(` Type: ${err.type}`); - if (err.path) core.info(` Path: ${JSON.stringify(err.path)}`); - if (err.locations) core.info(` Locations: ${JSON.stringify(err.locations)}`); + safeInfo(` [${idx + 1}] ${err.message}`); + if (err.type) safeInfo(` Type: ${err.type}`); + if (err.path) safeInfo(` Path: ${JSON.stringify(err.path)}`); + if (err.locations) safeInfo(` Locations: ${JSON.stringify(err.locations)}`); }); } - if (error.request) core.info(`Request: ${JSON.stringify(error.request, null, 2)}`); - if (error.data) core.info(`Response data: ${JSON.stringify(error.data, null, 2)}`); + if (error.request) safeInfo(`Request: ${JSON.stringify(error.request, null, 2)}`); + if (error.data) safeInfo(`Response data: ${JSON.stringify(error.data, null, 2)}`); } /** * Parse project number from URL @@ -249,7 +250,7 @@ async function resolveProjectV2(projectInfo, projectNumberInt, github) { if (project) return project; } catch (error) { - core.warning(`Direct projectV2(number) query failed; falling back to projectsV2 list search: ${getErrorMessage(error)}`); + safeWarning(`Direct projectV2(number) query failed; falling back to projectsV2 list search: ${getErrorMessage(error)}`); } // Wrap fallback query in try-catch to handle transient API errors gracefully @@ -458,7 +459,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = core.info(`✓ Authenticated as: ${viewerLogin}`); } } catch (viewerError) { - core.warning(`Could not resolve token identity (viewer.login): ${getErrorMessage(viewerError)}`); + safeWarning(`Could not resolve token identity (viewer.login): ${getErrorMessage(viewerError)}`); } // Projects v2 GraphQL API does not work with the default GITHUB_TOKEN. @@ -551,7 +552,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = ...(visibleFields ? { visible_fields: visibleFields } : {}), }; - core.info(`[3/4] Creating project view: ${name} (${layout})...`); + safeInfo(`[3/4] Creating project view: ${name} (${layout})...`); const response = await github.request(route, params); const created = response?.data; @@ -579,7 +580,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = const dataType = typeof fieldDef.data_type === "string" ? fieldDef.data_type.toUpperCase() : ""; if (!["DATE", "TEXT", "NUMBER", "SINGLE_SELECT", "ITERATION"].includes(dataType)) { - core.warning(`Skipping field "${fieldName}" with invalid data_type "${fieldDef.data_type}". Must be one of: DATE, TEXT, NUMBER, SINGLE_SELECT, ITERATION`); + safeWarning(`Skipping field "${fieldName}" with invalid data_type "${fieldDef.data_type}". Must be one of: DATE, TEXT, NUMBER, SINGLE_SELECT, ITERATION`); continue; } @@ -588,7 +589,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = if (dataType === "SINGLE_SELECT") { const options = Array.isArray(fieldDef.options) ? fieldDef.options : []; if (options.length === 0) { - core.warning(`Skipping SINGLE_SELECT field "${fieldName}" with no options`); + safeWarning(`Skipping SINGLE_SELECT field "${fieldName}" with no options`); continue; } @@ -653,9 +654,9 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = name: field.name, dataType: field.dataType, }); - core.info(`✓ Created field: ${field.name} (${field.dataType})`); + safeInfo(`✓ Created field: ${field.name} (${field.dataType})`); } catch (createError) { - core.warning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); + safeWarning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); } } @@ -733,7 +734,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = if (existingDraftItem) { itemId = existingDraftItem.id; - core.info(`✓ Found draft issue "${draftTitle}" by title fallback`); + safeInfo(`✓ Found draft issue "${draftTitle}" by title fallback`); } else { throw new Error(`draft_issue_id "${draftIssueId}" not found in temporary ID map and no draft with title "${draftTitle}" found`); } @@ -753,7 +754,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = if (existingDraftItem) { itemId = existingDraftItem.id; - core.info(`✓ Found existing draft issue "${draftTitle}" - updating fields instead of creating duplicate`); + safeInfo(`✓ Found existing draft issue "${draftTitle}" - updating fields instead of creating duplicate`); } else { const result = await github.graphql( `mutation($projectId: ID!, $title: String!, $body: String) { @@ -770,7 +771,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = { projectId, title: draftTitle, body: draftBody } ); itemId = result.addProjectV2DraftIssue.projectItem.id; - core.info(`✓ Created new draft issue "${draftTitle}"`); + safeInfo(`✓ Created new draft issue "${draftTitle}"`); // Store temporary_id mapping if provided if (temporaryId) { @@ -833,11 +834,11 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = ) ).createProjectV2Field.projectV2Field; } catch (createError) { - core.warning(`Failed to create date field "${fieldName}": ${getErrorMessage(createError)}`); + safeWarning(`Failed to create date field "${fieldName}": ${getErrorMessage(createError)}`); continue; } } else { - core.warning(`Field "${fieldName}" looks like a date field but value "${fieldValue}" is not in YYYY-MM-DD format. Skipping field creation.`); + safeWarning(`Field "${fieldName}" looks like a date field but value "${fieldValue}" is not in YYYY-MM-DD format. Skipping field creation.`); continue; } } else if ("classification" === fieldName.toLowerCase() || ("string" == typeof fieldValue && fieldValue.includes("|"))) @@ -849,7 +850,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = ) ).createProjectV2Field.projectV2Field; } catch (createError) { - core.warning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); + safeWarning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); continue; } else @@ -861,7 +862,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = ) ).createProjectV2Field.projectV2Field; } catch (createError) { - core.warning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); + safeWarning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); continue; } if (field.dataType === "DATE") valueToSet = { date: String(fieldValue) }; @@ -871,7 +872,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = // Convert string values to numbers if needed const numValue = typeof fieldValue === "number" ? fieldValue : parseFloat(String(fieldValue)); if (isNaN(numValue)) { - core.warning(`Invalid number value "${fieldValue}" for field "${fieldName}"`); + safeWarning(`Invalid number value "${fieldValue}" for field "${fieldName}"`); continue; } valueToSet = { number: numValue }; @@ -879,14 +880,14 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = // ITERATION fields use ProjectV2FieldValue input type with iterationId property // The value should match an iteration title or ID if (!field.configuration || !field.configuration.iterations) { - core.warning(`Iteration field "${fieldName}" has no configured iterations`); + safeWarning(`Iteration field "${fieldName}" has no configured iterations`); continue; } // Try to find iteration by title (case-insensitive match) const iteration = field.configuration.iterations.find(iter => iter.title.toLowerCase() === String(fieldValue).toLowerCase()); if (!iteration) { const availableIterations = field.configuration.iterations.map(i => i.title).join(", "); - core.warning(`Iteration "${fieldValue}" not found in field "${fieldName}". Available iterations: ${availableIterations}`); + safeWarning(`Iteration "${fieldValue}" not found in field "${fieldName}". Available iterations: ${availableIterations}`); continue; } valueToSet = { iterationId: iteration.id }; @@ -896,7 +897,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = // GitHub's GraphQL API does not support adding new options to existing single-select fields // The updateProjectV2Field mutation does not exist - users must add options manually via UI const availableOptions = field.options.map(o => o.name).join(", "); - core.warning(`Option "${fieldValue}" not found in field "${fieldName}". Available options: ${availableOptions}. To add this option, please update the field manually in the GitHub Projects UI.`); + safeWarning(`Option "${fieldValue}" not found in field "${fieldName}". Available options: ${availableOptions}. To add this option, please update the field manually in the GitHub Projects UI.`); continue; } valueToSet = { singleSelectOptionId: option.id }; @@ -932,7 +933,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = if (resolved.errorMessage || !resolved.resolved) { throw new Error(`Failed to resolve temporary ID in content_number: ${resolved.errorMessage || "Unknown error"}`); } - core.info(`✓ Resolved temporary ID ${sanitizedContentNumber} to issue #${resolved.resolved.number}`); + safeInfo(`✓ Resolved temporary ID ${sanitizedContentNumber} to issue #${resolved.resolved.number}`); contentNumber = resolved.resolved.number; } else { // Not a temporary ID - validate as numeric @@ -1030,11 +1031,11 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = ) ).createProjectV2Field.projectV2Field; } catch (createError) { - core.warning(`Failed to create date field "${fieldName}": ${getErrorMessage(createError)}`); + safeWarning(`Failed to create date field "${fieldName}": ${getErrorMessage(createError)}`); continue; } } else { - core.warning(`Field "${fieldName}" looks like a date field but value "${fieldValue}" is not in YYYY-MM-DD format. Skipping field creation.`); + safeWarning(`Field "${fieldName}" looks like a date field but value "${fieldValue}" is not in YYYY-MM-DD format. Skipping field creation.`); continue; } } else if ("classification" === fieldName.toLowerCase() || ("string" == typeof fieldValue && fieldValue.includes("|"))) @@ -1046,7 +1047,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = ) ).createProjectV2Field.projectV2Field; } catch (createError) { - core.warning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); + safeWarning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); continue; } else @@ -1058,7 +1059,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = ) ).createProjectV2Field.projectV2Field; } catch (createError) { - core.warning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); + safeWarning(`Failed to create field "${fieldName}": ${getErrorMessage(createError)}`); continue; } // Check dataType first to properly handle DATE fields before checking for options @@ -1074,7 +1075,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = // Convert string values to numbers if needed const numValue = typeof fieldValue === "number" ? fieldValue : parseFloat(String(fieldValue)); if (isNaN(numValue)) { - core.warning(`Invalid number value "${fieldValue}" for field "${fieldName}"`); + safeWarning(`Invalid number value "${fieldValue}" for field "${fieldName}"`); continue; } valueToSet = { number: numValue }; @@ -1082,14 +1083,14 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = // ITERATION fields use ProjectV2FieldValue input type with iterationId property // The value should match an iteration title or ID if (!field.configuration || !field.configuration.iterations) { - core.warning(`Iteration field "${fieldName}" has no configured iterations`); + safeWarning(`Iteration field "${fieldName}" has no configured iterations`); continue; } // Try to find iteration by title (case-insensitive match) const iteration = field.configuration.iterations.find(iter => iter.title.toLowerCase() === String(fieldValue).toLowerCase()); if (!iteration) { const availableIterations = field.configuration.iterations.map(i => i.title).join(", "); - core.warning(`Iteration "${fieldValue}" not found in field "${fieldName}". Available iterations: ${availableIterations}`); + safeWarning(`Iteration "${fieldValue}" not found in field "${fieldName}". Available iterations: ${availableIterations}`); continue; } valueToSet = { iterationId: iteration.id }; @@ -1099,7 +1100,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = // GitHub's GraphQL API does not support adding new options to existing single-select fields // The updateProjectV2Field mutation does not exist - users must add options manually via UI const availableOptions = field.options.map(o => o.name).join(", "); - core.warning(`Option "${fieldValue}" not found in field "${fieldName}". Available options: ${availableOptions}. To add this option, please update the field manually in the GitHub Projects UI.`); + safeWarning(`Option "${fieldValue}" not found in field "${fieldName}". Available options: ${availableOptions}. To add this option, please update the field manually in the GitHub Projects UI.`); continue; } valueToSet = { singleSelectOptionId: option.id }; @@ -1121,7 +1122,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient = (usingCustomToken ? "GH_AW_PROJECT_GITHUB_TOKEN is set but lacks access." : "Using default GITHUB_TOKEN - this cannot access Projects v2 API. You must configure GH_AW_PROJECT_GITHUB_TOKEN.") ); } else { - core.error(`Failed to manage project: ${getErrorMessage(error)}`); + safeError(`Failed to manage project: ${getErrorMessage(error)}`); } throw error; } @@ -1305,11 +1306,11 @@ async function main(config = {}, githubClient = null) { }; await updateProject(viewOutput, temporaryIdMap, github); - core.info(`✓ Created view ${i + 1}/${configuredViews.length}: ${viewConfig.name} (${viewConfig.layout})`); + safeInfo(`✓ Created view ${i + 1}/${configuredViews.length}: ${viewConfig.name} (${viewConfig.layout})`); } catch (err) { // prettier-ignore const error = /** @type {Error & { errors?: Array<{ type?: string, message: string, path?: unknown, locations?: unknown }>, request?: unknown, data?: unknown }} */ (err); - core.error(`Failed to create configured view ${i + 1}: ${viewConfig.name}`); + safeError(`Failed to create configured view ${i + 1}: ${viewConfig.name}`); logGraphQLError(error, `Creating configured view: ${viewConfig.name}`); } } diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 04f825c4219..003ae014b98 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -1,6 +1,8 @@ // @ts-check /// +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); + /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ @@ -53,7 +55,7 @@ async function executePRUpdate(github, context, prNumber, updateData) { runId: context.runId, }); - core.info(`Will update body (length: ${apiData.body.length})`); + safeInfo(`Will update body (length: ${apiData.body.length})`); } const { data: pr } = await github.rest.pulls.update({ diff --git a/actions/setup/js/update_release.cjs b/actions/setup/js/update_release.cjs index 08611d31160..b691b744ff1 100644 --- a/actions/setup/js/update_release.cjs +++ b/actions/setup/js/update_release.cjs @@ -3,6 +3,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { updateBody } = require("./update_pr_description_helpers.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Create a handler for update-release messages @@ -28,7 +29,7 @@ async function main(config = {}) { return async function handleUpdateRelease(message, resolvedTemporaryIds = {}) { // In staged mode, skip actual processing (preview is handled elsewhere) if (isStaged) { - core.info(`Staged mode: Would update release with tag ${message.tag || "(inferred)"}`); + safeInfo(`Staged mode: Would update release with tag ${message.tag || "(inferred)"}`); return { skipped: true, reason: "staged_mode" }; } @@ -79,7 +80,7 @@ async function main(config = {}) { tag: releaseTag, }); - core.info(`Found release: ${release.name || release.tag_name} (ID: ${release.id})`); + safeInfo(`Found release: ${release.name || release.tag_name} (ID: ${release.id})`); // Get workflow run URL for AI attribution const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; diff --git a/actions/setup/js/update_runner.cjs b/actions/setup/js/update_runner.cjs index b92fcda59ca..8e75776c1dd 100644 --- a/actions/setup/js/update_runner.cjs +++ b/actions/setup/js/update_runner.cjs @@ -1,6 +1,8 @@ // @ts-check /// +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); + /** * Shared update runner for safe-output scripts (update_issue, update_pull_request, etc.) * @@ -213,9 +215,9 @@ async function runUpdateWorkflow(config) { core.info(`Update target configuration: ${updateTarget}`); if (supportsStatus) { - core.info(`Can update status: ${canUpdateStatus}, title: ${canUpdateTitle}, body: ${canUpdateBody}, labels: ${canUpdateLabels}`); + safeInfo(`Can update status: ${canUpdateStatus}, title: ${canUpdateTitle}, body: ${canUpdateBody}, labels: ${canUpdateLabels}`); } else { - core.info(`Can update title: ${canUpdateTitle}, body: ${canUpdateBody}, labels: ${canUpdateLabels}`); + safeInfo(`Can update title: ${canUpdateTitle}, body: ${canUpdateBody}, labels: ${canUpdateLabels}`); } // Check context validity @@ -224,7 +226,7 @@ async function runUpdateWorkflow(config) { // Validate context based on target configuration if (updateTarget === "triggering" && !contextIsValid) { - core.info(`Target is "triggering" but not running in ${displayName} context, skipping ${displayName} update`); + safeInfo(`Target is "triggering" but not running in ${displayName} context, skipping ${displayName} update`); return; } @@ -251,7 +253,7 @@ async function runUpdateWorkflow(config) { } const targetNumber = targetResult.number; - core.info(`Updating ${displayName} #${targetNumber}`); + safeInfo(`Updating ${displayName} #${targetNumber}`); // Build update data const { hasUpdates, updateData, logMessages } = buildUpdateData({ @@ -284,7 +286,7 @@ async function runUpdateWorkflow(config) { try { // Execute the update using the provided function const updatedItem = await executeUpdate(github, context, targetNumber, updateData, handlerConfig); - core.info(`Updated ${displayName} #${updatedItem.number}: ${updatedItem.html_url}`); + safeInfo(`Updated ${displayName} #${updatedItem.number}: ${updatedItem.html_url}`); updatedItems.push(updatedItem); // Set output for the last updated item (for backward compatibility) @@ -293,7 +295,7 @@ async function runUpdateWorkflow(config) { core.setOutput(outputUrlKey, updatedItem.html_url); } } catch (error) { - core.error(`✗ Failed to update ${displayName} #${targetNumber}: ${getErrorMessage(error)}`); + safeError(`✗ Failed to update ${displayName} #${targetNumber}: ${getErrorMessage(error)}`); throw error; } } @@ -307,7 +309,7 @@ async function runUpdateWorkflow(config) { await core.summary.addRaw(summaryContent).write(); } - core.info(`Successfully updated ${updatedItems.length} ${displayName}(s)`); + safeInfo(`Successfully updated ${updatedItems.length} ${displayName}(s)`); return updatedItems; } diff --git a/actions/setup/js/upload_assets.cjs b/actions/setup/js/upload_assets.cjs index 93e4fae74fe..a8e9a3c8081 100644 --- a/actions/setup/js/upload_assets.cjs +++ b/actions/setup/js/upload_assets.cjs @@ -6,6 +6,7 @@ const path = require("path"); const crypto = require("crypto"); const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); /** * Normalizes a branch name to be a valid git branch name. @@ -68,7 +69,7 @@ async function main() { // Normalize the branch name to ensure it's a valid git branch name const normalizedBranchName = normalizeBranchName(branchName); - core.info(`Using assets branch: ${normalizedBranchName}`); + safeInfo(`Using assets branch: ${normalizedBranchName}`); const result = loadAgentOutput(); if (!result.success) { @@ -97,7 +98,7 @@ async function main() { try { await exec.exec(`git rev-parse --verify origin/${normalizedBranchName}`); await exec.exec(`git checkout -B ${normalizedBranchName} origin/${normalizedBranchName}`); - core.info(`Checked out existing branch from origin: ${normalizedBranchName}`); + safeInfo(`Checked out existing branch from origin: ${normalizedBranchName}`); } catch (originError) { // Validate that branch starts with "assets/" prefix before creating orphaned branch if (!normalizedBranchName.startsWith("assets/")) { @@ -110,7 +111,7 @@ async function main() { } // Branch doesn't exist on origin and has valid prefix, create orphaned branch - core.info(`Creating new orphaned branch: ${normalizedBranchName}`); + safeInfo(`Creating new orphaned branch: ${normalizedBranchName}`); await exec.exec(`git checkout --orphan ${normalizedBranchName}`); await exec.exec(`git rm -rf .`); await exec.exec(`git clean -fdx`); @@ -143,7 +144,7 @@ async function main() { // Check if file already exists in the branch if (fs.existsSync(targetFileName)) { - core.info(`Asset ${targetFileName} already exists, skipping`); + safeInfo(`Asset ${targetFileName} already exists, skipping`); continue; } @@ -157,7 +158,7 @@ async function main() { uploadCount++; hasChanges = true; - core.info(`Added asset: ${targetFileName} (${size} bytes)`); + safeInfo(`Added asset: ${targetFileName} (${size} bytes)`); } catch (error) { core.setFailed(`Failed to process asset ${fileName}: ${getErrorMessage(error)}`); return; @@ -173,7 +174,7 @@ async function main() { } else { await exec.exec(`git push origin ${normalizedBranchName}`); core.summary.addRaw("## Assets").addRaw(`Successfully uploaded **${uploadCount}** assets to branch \`${normalizedBranchName}\``).addRaw(""); - core.info(`Successfully uploaded ${uploadCount} assets to branch ${normalizedBranchName}`); + safeInfo(`Successfully uploaded ${uploadCount} assets to branch ${normalizedBranchName}`); } for (const asset of uploadItems) { diff --git a/actions/setup/js/validate_secrets.cjs b/actions/setup/js/validate_secrets.cjs index c8057aa8f73..420fe5cd990 100644 --- a/actions/setup/js/validate_secrets.cjs +++ b/actions/setup/js/validate_secrets.cjs @@ -1,6 +1,8 @@ // @ts-check /// +const { safeInfo, safeDebug, safeWarning, safeError } = require("./sanitized_logging.cjs"); + /** * Secret Validation Script * @@ -628,7 +630,7 @@ async function main() { test: "GitHub REST API", ...restResult, }); - core.info(` ${statusEmoji(restResult.status)} ${restResult.message}`); + safeInfo(` ${statusEmoji(restResult.status)} ${restResult.message}`); const graphqlResult = await testGitHubGraphQLAPI(ghAwToken, owner, repo); results.push({ @@ -636,7 +638,7 @@ async function main() { test: "GitHub GraphQL API", ...graphqlResult, }); - core.info(` ${statusEmoji(graphqlResult.status)} ${graphqlResult.message}`); + safeInfo(` ${statusEmoji(graphqlResult.status)} ${graphqlResult.message}`); // Test GH_AW_GITHUB_MCP_SERVER_TOKEN core.info("Testing GH_AW_GITHUB_MCP_SERVER_TOKEN..."); @@ -647,7 +649,7 @@ async function main() { test: "GitHub REST API", ...mcpRestResult, }); - core.info(` ${statusEmoji(mcpRestResult.status)} ${mcpRestResult.message}`); + safeInfo(` ${statusEmoji(mcpRestResult.status)} ${mcpRestResult.message}`); // Test GH_AW_PROJECT_GITHUB_TOKEN core.info("Testing GH_AW_PROJECT_GITHUB_TOKEN..."); @@ -658,7 +660,7 @@ async function main() { test: "GitHub REST API", ...projectRestResult, }); - core.info(` ${statusEmoji(projectRestResult.status)} ${projectRestResult.message}`); + safeInfo(` ${statusEmoji(projectRestResult.status)} ${projectRestResult.message}`); // Test GH_AW_COPILOT_TOKEN core.info("Testing GH_AW_COPILOT_TOKEN..."); @@ -669,7 +671,7 @@ async function main() { test: "Copilot CLI Availability", ...copilotResult, }); - core.info(` ${statusEmoji(copilotResult.status)} ${copilotResult.message}`); + safeInfo(` ${statusEmoji(copilotResult.status)} ${copilotResult.message}`); // Test ANTHROPIC_API_KEY core.info("Testing ANTHROPIC_API_KEY..."); @@ -680,7 +682,7 @@ async function main() { test: "Anthropic Claude API", ...anthropicResult, }); - core.info(` ${statusEmoji(anthropicResult.status)} ${anthropicResult.message}`); + safeInfo(` ${statusEmoji(anthropicResult.status)} ${anthropicResult.message}`); // Test OPENAI_API_KEY core.info("Testing OPENAI_API_KEY..."); @@ -691,7 +693,7 @@ async function main() { test: "OpenAI API", ...openaiResult, }); - core.info(` ${statusEmoji(openaiResult.status)} ${openaiResult.message}`); + safeInfo(` ${statusEmoji(openaiResult.status)} ${openaiResult.message}`); // Test BRAVE_API_KEY core.info("Testing BRAVE_API_KEY..."); @@ -702,7 +704,7 @@ async function main() { test: "Brave Search API", ...braveResult, }); - core.info(` ${statusEmoji(braveResult.status)} ${braveResult.message}`); + safeInfo(` ${statusEmoji(braveResult.status)} ${braveResult.message}`); // Test NOTION_API_TOKEN core.info("Testing NOTION_API_TOKEN..."); @@ -713,7 +715,7 @@ async function main() { test: "Notion API", ...notionResult, }); - core.info(` ${statusEmoji(notionResult.status)} ${notionResult.message}`); + safeInfo(` ${statusEmoji(notionResult.status)} ${notionResult.message}`); // Generate markdown report core.info("Generating report...");