diff --git a/actions/setup/js/add_reaction_and_edit_comment.cjs b/actions/setup/js/add_reaction_and_edit_comment.cjs index 34a08807637..d8e2ef266cd 100644 --- a/actions/setup/js/add_reaction_and_edit_comment.cjs +++ b/actions/setup/js/add_reaction_and_edit_comment.cjs @@ -140,7 +140,14 @@ async function resolveEventEndpoints(eventName, owner, repo, payload) { async function main() { const reaction = process.env.GH_AW_REACTION || "eyes"; const commandsJSON = process.env.GH_AW_COMMANDS; - const command = commandsJSON ? (JSON.parse(commandsJSON)[0] ?? null) : null; // Only present for command workflows + let command = null; // Only present for command workflows + if (commandsJSON) { + try { + command = JSON.parse(commandsJSON)[0] ?? null; + } catch (err) { + throw new Error("Failed to parse GH_AW_COMMANDS: " + getErrorMessage(err), { cause: err }); + } + } const invocationContext = resolveInvocationContext(context); const runUrl = buildWorkflowRunUrl(context, invocationContext.workflowRepo); diff --git a/actions/setup/js/apply_safe_outputs_replay.cjs b/actions/setup/js/apply_safe_outputs_replay.cjs index 5583b0a1878..72b68fa52e4 100644 --- a/actions/setup/js/apply_safe_outputs_replay.cjs +++ b/actions/setup/js/apply_safe_outputs_replay.cjs @@ -100,7 +100,12 @@ async function downloadAgentArtifact(runId, destDir, repoSlug) { */ function buildHandlerConfigFromOutput(agentOutputFile) { const content = fs.readFileSync(agentOutputFile, "utf8"); - const validatedOutput = JSON.parse(content); + let validatedOutput; + try { + validatedOutput = JSON.parse(content); + } catch (err) { + throw new Error("Failed to parse agent output file " + agentOutputFile + ": " + getErrorMessage(err), { cause: err }); + } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { core.info("No items found in agent output; handler config will be empty"); diff --git a/actions/setup/js/artifact_client.cjs b/actions/setup/js/artifact_client.cjs index 652f3085e27..bc7fabbf475 100644 --- a/actions/setup/js/artifact_client.cjs +++ b/actions/setup/js/artifact_client.cjs @@ -15,6 +15,8 @@ const { Readable } = require("stream"); const { pipeline } = require("stream/promises"); const { spawnSync } = require("child_process"); +const { getErrorMessage } = require("./error_helpers.cjs"); + const DEFAULT_RETRY_ATTEMPTS = 5; const RETRY_DELAY_MS = 5000; const RESULTS_SCOPE_PREFIX = "Actions.Results:"; @@ -33,7 +35,13 @@ function decodeJWTPayload(token) { } const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/"); const padded = payload + "=".repeat((4 - (payload.length % 4 || 4)) % 4); - return JSON.parse(Buffer.from(padded, "base64").toString("utf8")); + let parsed; + try { + parsed = JSON.parse(Buffer.from(padded, "base64").toString("utf8")); + } catch (err) { + throw new Error("Failed to parse JWT payload: " + getErrorMessage(err), { cause: err }); + } + return parsed; } function getBackendIdsFromRuntimeToken() { diff --git a/actions/setup/js/build_checkout_manifest.cjs b/actions/setup/js/build_checkout_manifest.cjs index 3a585eaa3e5..3db58ce5c0d 100644 --- a/actions/setup/js/build_checkout_manifest.cjs +++ b/actions/setup/js/build_checkout_manifest.cjs @@ -10,7 +10,12 @@ const { execFileSync } = require("child_process"); const { getErrorMessage } = require("./error_helpers.cjs"); function parseManifestEntries(entriesJSON = process.env.GH_AW_CHECKOUT_MANIFEST_ENTRIES || "[]") { - const parsed = JSON.parse(entriesJSON); + let parsed; + try { + parsed = JSON.parse(entriesJSON); + } catch (err) { + throw new Error("Failed to parse GH_AW_CHECKOUT_MANIFEST_ENTRIES: " + getErrorMessage(err), { cause: err }); + } if (!Array.isArray(parsed)) { throw new Error("GH_AW_CHECKOUT_MANIFEST_ENTRIES must be a JSON array"); } diff --git a/actions/setup/js/convert_gateway_config_shared.cjs b/actions/setup/js/convert_gateway_config_shared.cjs index c539461e0ce..ea61e33a5b5 100644 --- a/actions/setup/js/convert_gateway_config_shared.cjs +++ b/actions/setup/js/convert_gateway_config_shared.cjs @@ -4,6 +4,8 @@ const fs = require("fs"); const path = require("path"); +const { getErrorMessage } = require("./error_helpers.cjs"); + /** * Rewrite a gateway URL to use the configured domain and port. * Replaces http:///mcp/ with http://:/mcp/. @@ -79,10 +81,20 @@ function loadGatewayContext(options = {}) { } /** @type {Set} */ - const cliServers = new Set(JSON.parse(process.env.GH_AW_MCP_CLI_SERVERS || "[]")); + let cliServers; + try { + cliServers = new Set(JSON.parse(process.env.GH_AW_MCP_CLI_SERVERS || "[]")); + } catch (err) { + throw new Error("Failed to parse GH_AW_MCP_CLI_SERVERS: " + getErrorMessage(err), { cause: err }); + } /** @type {Record} */ - const config = JSON.parse(fs.readFileSync(gatewayOutput, "utf8")); + let config; + try { + config = JSON.parse(fs.readFileSync(gatewayOutput, "utf8")); + } catch (err) { + throw new Error("Failed to parse gateway output file " + gatewayOutput + ": " + getErrorMessage(err), { cause: err }); + } const rawServers = config.mcpServers; /** @type {Record>} */ let servers = {}; diff --git a/actions/setup/js/generate_safe_outputs_tools.cjs b/actions/setup/js/generate_safe_outputs_tools.cjs index 00e49f933a2..187129b23cf 100644 --- a/actions/setup/js/generate_safe_outputs_tools.cjs +++ b/actions/setup/js/generate_safe_outputs_tools.cjs @@ -32,6 +32,7 @@ const fs = require("fs"); const path = require("path"); const { ERR_CONFIG } = require("./error_codes.cjs"); +const { getErrorMessage } = require("./error_helpers.cjs"); const { parseRuntimeFeatures, hasRuntimeFeature } = require("./runtime_features.cjs"); const ADD_COMMENT_DEFAULT_DISCUSSIONS_NOTE = @@ -95,7 +96,12 @@ async function main() { throw new Error(msg); } /** @type {Array<{name: string, description: string, inputSchema?: {properties?: Record}}>} */ - const allTools = JSON.parse(fs.readFileSync(toolsSourcePath, "utf8")); + let allTools; + try { + allTools = JSON.parse(fs.readFileSync(toolsSourcePath, "utf8")); + } catch (err) { + throw new Error("Failed to parse tools source file " + toolsSourcePath + ": " + getErrorMessage(err), { cause: err }); + } // Load config to determine which tools are enabled if (!fs.existsSync(configPath)) { @@ -104,13 +110,22 @@ async function main() { throw new Error(msg); } /** @type {Record} */ - const config = JSON.parse(fs.readFileSync(configPath, "utf8")); + let config; + try { + config = JSON.parse(fs.readFileSync(configPath, "utf8")); + } catch (err) { + throw new Error("Failed to parse config file " + configPath + ": " + getErrorMessage(err), { cause: err }); + } // Load tools meta (description suffixes, repo params, dynamic tools) /** @type {{description_suffixes?: Record, repo_params?: Record, dynamic_tools?: Array, required_field_removals?: Record, required_field_additions?: Record}} */ let toolsMeta = { description_suffixes: {}, repo_params: {}, dynamic_tools: [] }; if (fs.existsSync(toolsMetaPath)) { - toolsMeta = JSON.parse(fs.readFileSync(toolsMetaPath, "utf8")); + try { + toolsMeta = JSON.parse(fs.readFileSync(toolsMetaPath, "utf8")); + } catch (err) { + throw new Error("Failed to parse tools meta file " + toolsMetaPath + ": " + getErrorMessage(err), { cause: err }); + } } // Build set of source tool names (predefined/static tools only) @@ -127,7 +142,12 @@ async function main() { .filter(tool => enabledToolNames.has(tool.name)) .map(tool => { // Deep copy to avoid modifying the original - const enhancedTool = JSON.parse(JSON.stringify(tool)); + let enhancedTool; + try { + enhancedTool = JSON.parse(JSON.stringify(tool)); + } catch (err) { + throw new Error("Failed to deep-copy tool " + tool.name + ": " + getErrorMessage(err), { cause: err }); + } // Apply description suffix if available (e.g., " CONSTRAINTS: Maximum 5 issues.") const descSuffix = toolsMeta.description_suffixes?.[tool.name]; diff --git a/actions/setup/js/generate_workflow_overview.cjs b/actions/setup/js/generate_workflow_overview.cjs index 126599a3322..be0346d20a1 100644 --- a/actions/setup/js/generate_workflow_overview.cjs +++ b/actions/setup/js/generate_workflow_overview.cjs @@ -2,6 +2,7 @@ /// const { jsonObjectToMarkdown } = require("./json_object_to_markdown.cjs"); +const { getErrorMessage } = require("./error_helpers.cjs"); /** * Generate workflow overview step that writes an agentic workflow run overview @@ -16,7 +17,12 @@ async function generateWorkflowOverview(core) { const awInfoPath = "/tmp/gh-aw/aw_info.json"; // Load aw_info.json - const awInfo = JSON.parse(fs.readFileSync(awInfoPath, "utf8")); + let awInfo; + try { + awInfo = JSON.parse(fs.readFileSync(awInfoPath, "utf8")); + } catch (err) { + throw new Error("Failed to parse aw_info.json at " + awInfoPath + ": " + getErrorMessage(err), { cause: err }); + } // Build the collapsible summary label with engine_id and version const engineLabel = [awInfo.engine_id, awInfo.version].filter(Boolean).join(" "); diff --git a/actions/setup/js/mcp_scripts_config_loader.cjs b/actions/setup/js/mcp_scripts_config_loader.cjs index 9d1a607d8d8..ee0c638fa35 100644 --- a/actions/setup/js/mcp_scripts_config_loader.cjs +++ b/actions/setup/js/mcp_scripts_config_loader.cjs @@ -9,6 +9,7 @@ const fs = require("fs"); const { ERR_SYSTEM, ERR_VALIDATION } = require("./error_codes.cjs"); +const { getErrorMessage } = require("./error_helpers.cjs"); /** * @typedef {Object} MCPScriptsToolConfig @@ -40,7 +41,12 @@ function loadConfig(configPath) { } const configContent = fs.readFileSync(configPath, "utf-8"); - const config = JSON.parse(configContent); + let config; + try { + config = JSON.parse(configContent); + } catch (err) { + throw new Error("Failed to parse config file " + configPath + ": " + getErrorMessage(err), { cause: err }); + } // Validate required fields if (!config.tools || !Array.isArray(config.tools)) { diff --git a/actions/setup/js/patch_awf_chroot_config.cjs b/actions/setup/js/patch_awf_chroot_config.cjs index 780c3dcbd30..f7b61b879d5 100644 --- a/actions/setup/js/patch_awf_chroot_config.cjs +++ b/actions/setup/js/patch_awf_chroot_config.cjs @@ -4,6 +4,8 @@ const fs = require("fs"); const os = require("os"); const path = require("path"); +const { getErrorMessage } = require("./error_helpers.cjs"); + /** * Patch the AWF config file with chroot settings for ARC/DinD runners. * @@ -23,7 +25,12 @@ function patchAWFChrootConfig(options = {}) { const identityHome = options.identityHome || process.env.GH_AW_CHROOT_IDENTITY_HOME || "/tmp/gh-aw/home"; const configPath = path.join(runnerTemp, "gh-aw", "awf-config.json"); const artifactConfigPath = path.join(binariesSourcePath, "awf-config.json"); - const config = JSON.parse(fs.readFileSync(configPath, "utf8")); + let config; + try { + config = JSON.parse(fs.readFileSync(configPath, "utf8")); + } catch (err) { + throw new Error("Failed to parse AWF config file " + configPath + ": " + getErrorMessage(err), { cause: err }); + } const userInfo = os.userInfo(); config.chroot = { diff --git a/actions/setup/js/route_slash_command.cjs b/actions/setup/js/route_slash_command.cjs index 61712ddba4f..fd50980346e 100644 --- a/actions/setup/js/route_slash_command.cjs +++ b/actions/setup/js/route_slash_command.cjs @@ -6,6 +6,7 @@ const { REACTION_MAP } = require("./add_reaction.cjs"); const { createOrReuseStatusComment } = require("./add_workflow_run_comment.cjs"); const nodePath = require("node:path"); const { matchesCommandName, parseSlashCommand } = require("./slash_command_matcher.cjs"); +const { getErrorMessage } = require("./error_helpers.cjs"); // Keep this aligned with the current default stable GitHub REST API version used by workflows. // Update when GitHub advances the recommended version to avoid sunset/deprecation warnings. const GITHUB_API_VERSION = "2022-11-28"; @@ -638,8 +639,18 @@ async function main() { core.info("Starting centralized command routing."); core.info(`Incoming event name: '${context.eventName}'.`); - const slashRouteMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}"); - const labelRouteMap = JSON.parse(process.env.GH_AW_LABEL_ROUTING || "{}"); + let slashRouteMap; + try { + slashRouteMap = JSON.parse(process.env.GH_AW_SLASH_ROUTING || "{}"); + } catch (err) { + throw new Error("Failed to parse GH_AW_SLASH_ROUTING: " + getErrorMessage(err), { cause: err }); + } + let labelRouteMap; + try { + labelRouteMap = JSON.parse(process.env.GH_AW_LABEL_ROUTING || "{}"); + } catch (err) { + throw new Error("Failed to parse GH_AW_LABEL_ROUTING: " + getErrorMessage(err), { cause: err }); + } core.info(`Configured centralized slash commands: ${Object.keys(slashRouteMap).length}.`); core.info(`Configured decentralized label commands: ${Object.keys(labelRouteMap).length}.`); const text = resolveBodyText(); diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index d8db15ac063..8d272b5d1db 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -50,7 +50,13 @@ function resolveEffectiveContext(invocationContext, rawContext) { * @returns {any} */ function readJSONFile(filePath) { - return JSON.parse(fs.readFileSync(filePath, "utf8")); + let parsed; + try { + parsed = JSON.parse(fs.readFileSync(filePath, "utf8")); + } catch (err) { + throw new Error("Failed to parse JSON file " + filePath + ": " + getErrorMessage(err), { cause: err }); + } + return parsed; } const safeOutputsTools = readJSONFile(path.join(__dirname, "safe_outputs_tools.json")); diff --git a/actions/setup/js/safe_outputs_tools_loader.cjs b/actions/setup/js/safe_outputs_tools_loader.cjs index cb4d7669c75..a9e235b68b8 100644 --- a/actions/setup/js/safe_outputs_tools_loader.cjs +++ b/actions/setup/js/safe_outputs_tools_loader.cjs @@ -228,7 +228,11 @@ function registerPredefinedTools(server, tools, config, registerTool, normalizeT const isCreatePullRequestTool = tool.name === "create_pull_request" && config.create_pull_request; // Enrich create_pull_request tool description when target-repo is configured if (safetyWarning || isCreatePullRequestTool) { - toolToRegister = JSON.parse(JSON.stringify(tool)); + try { + toolToRegister = JSON.parse(JSON.stringify(tool)); + } catch (err) { + throw new Error("Failed to deep-copy tool " + tool.name + ": " + getErrorMessage(err), { cause: err }); + } if (tool.handler) { toolToRegister.handler = tool.handler; } diff --git a/actions/setup/js/start_mcp_gateway.cjs b/actions/setup/js/start_mcp_gateway.cjs index f4e1f502b6d..5c728276037 100644 --- a/actions/setup/js/start_mcp_gateway.cjs +++ b/actions/setup/js/start_mcp_gateway.cjs @@ -35,6 +35,7 @@ const http = require("http"); const path = require("path"); const { withRetry } = require("./error_recovery.cjs"); const { lstatGuard } = require("./symlink_guard.cjs"); +const { getErrorMessage } = require("./error_helpers.cjs"); // --------------------------------------------------------------------------- // Timing helpers @@ -743,7 +744,12 @@ async function main() { fs.chmodSync(outputPath, 0o600); // Check for error payload - const gatewayOutput = JSON.parse(fs.readFileSync(outputPath, "utf8")); + let gatewayOutput; + try { + gatewayOutput = JSON.parse(fs.readFileSync(outputPath, "utf8")); + } catch (err) { + throw new Error("Failed to parse gateway output file " + outputPath + ": " + getErrorMessage(err), { cause: err }); + } if (gatewayOutput.error) { core.error("ERROR: Gateway returned an error payload instead of configuration"); core.error("");