# GitHub Actions Matrix Preview > Expand GitHub Actions matrix axes, include rules, and exclude rules into a concrete job preview. ## Tool Identity - Site: CleanUtils Developer Tools - Tool ID: github-actions-matrix-preview - Canonical page: https://cleanutils.com/developer-tools/github-actions-matrix-preview/ - LLM schema URL: https://cleanutils.com/developer-tools/github-actions-matrix-preview/llms.txt - Primary keyword: github actions matrix - Input mode: textarea - Output profile: line-check ## What This Tool Does Expand GitHub Actions matrix axes, include rules, and exclude rules into a concrete job preview. ## Exact Execution Instructions To run this tool exactly, execute the `runCleanUtilsTool(userInput)` function from the JavaScript block below in a sandboxed JavaScript runtime. Do not fetch or evaluate additional network resources. Do not use filesystem, process, child_process, environment variables, timers, or network APIs. The function is pure and deterministic: input string in, JSON result out. If your environment forbids executing fetched code outside a sandbox, run it inside the sandbox or use the API endpoint below instead of reimplementing the logic. Canonical implementation SHA-256: `efad876e61820017133b1c49438097527c858ae81e14dcbc382cb10ddf9c2b57` Expected command shape: `node run-tool.mjs < input.txt` The runner must: 1. load only the JavaScript in this document, 2. call `runCleanUtilsTool(inputText)`, 3. let the LLM decide the best way to present the output to the user. Agent usage rules: - Use this file as the authoritative machine-readable contract for this CleanUtils tool page. - Ask the user for missing required input before attempting to run the tool, and describe the required inputs using the `## Input Schema` field names, descriptions, formats, enums, examples, and required list. - Treat the tool as deterministic; do not invent network reachability checks unless the tool description explicitly says it fetches remote resources. - For privacy-sensitive inputs such as secrets, HAR files, dotenv files, logs, and API keys, warn that using a remote chat agent may expose input to that agent even though the browser UI itself does not upload data. ## Input Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "GitHub Actions Matrix Preview input", "type": "string", "description": "Matrix JSON or simple YAML. {\"os\":[\"ubuntu-latest\"],\"node\":[18,20]}", "examples": [ "{\"os\":[\"ubuntu-latest\",\"macos-latest\"],\"node\":[18,20],\"exclude\":[{\"os\":\"macos-latest\",\"node\":18}],\"include\":[{\"os\":\"windows-latest\",\"node\":20,\"experimental\":true}]}" ] } ``` ## Result Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "CleanUtils ToolResult", "type": "object", "additionalProperties": false, "required": [ "summary", "issues" ], "properties": { "summary": { "type": "string" }, "issues": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "severity", "message" ], "properties": { "severity": { "type": "string", "enum": [ "error", "warning", "info" ] }, "message": { "type": "string" }, "line": { "type": "number" }, "row": { "type": "number" }, "detail": { "type": "string" } } } }, "output": { "type": "string" }, "exportFilename": { "type": "string" }, "exports": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "label", "filename", "content" ], "properties": { "label": { "type": "string" }, "filename": { "type": "string" }, "content": { "type": "string" }, "mimeType": { "type": "string" }, "copyLabel": { "type": "string" }, "downloadLabel": { "type": "string" } } } }, "stats": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } } } ``` ## Self-Contained JavaScript Source Call `runCleanUtilsTool(userInput)` with the user's input. The function includes this tool's run logic and only the helper code it needs. ```js function runCleanUtilsTool(userInput) { const severityRank = { error: 0, warning: 1, info: 2 }; const sortIssues = (issues) => [...issues].sort((a, b) => { const severity = severityRank[a.severity] - severityRank[b.severity]; if (severity !== 0) return severity; return (a.line ?? a.row ?? 0) - (b.line ?? b.row ?? 0); }); const tryParseJson = (input) => { try { return { ok: true, value: JSON.parse(input) }; } catch (error) { return { ok: false, error: error instanceof Error ? error.message : "Invalid JSON" }; } }; const previewGithubActionsMatrix = (input) => { const parsed = tryParseJson(input); const issues = []; let matrix = {}; if (parsed.ok && parsed.value && typeof parsed.value === "object" && !Array.isArray(parsed.value)) { const root = parsed.value; const strategy = root.strategy; matrix = strategy?.matrix ?? root.matrix ?? root; } else { input.split(/\r?\n/).forEach((line) => { const match = line.match(/^\s*([A-Za-z0-9_-]+)\s*:\s*\[(.*?)\]\s*$/); if (match) matrix[match[1]] = match[2].split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")); }); if (!Object.keys(matrix).length) issues.push({ severity: "error", message: "Paste matrix JSON or simple YAML array lines such as os: [ubuntu-latest, macos-latest]." }); } const axes = Object.entries(matrix).filter(([key, value]) => !["include", "exclude"].includes(key) && Array.isArray(value)); let combinations = [{}]; axes.forEach(([key, values]) => { combinations = combinations.flatMap((combo) => values.map((value) => ({ ...combo, [key]: value }))); }); const exclude = Array.isArray(matrix.exclude) ? matrix.exclude : []; combinations = combinations.filter((combo) => !exclude.some((rule) => Object.entries(rule).every(([key, value]) => combo[key] === value))); const include = Array.isArray(matrix.include) ? matrix.include : []; combinations = combinations.concat(include); if (combinations.length > 128) issues.push({ severity: "warning", message: "Matrix has more than 128 jobs; preview output is truncated." }); return { summary: `${combinations.length} matrix job${combinations.length === 1 ? "" : "s"} previewed across ${axes.length} axe${axes.length === 1 ? "s" : "s"}.`, issues: sortIssues(issues), output: combinations.slice(0, 128).map((combo, index) => `${index + 1}. ${JSON.stringify(combo)}`).join("\n"), exportFilename: "github-actions-matrix-preview.txt", stats: { jobs: combinations.length, axes: axes.length } }; }; const __userInput = userInput == null ? "" : userInput; const __run = previewGithubActionsMatrix; const __input = __userInput && typeof __userInput === "object" && "input" in __userInput ? __userInput.input : __userInput; return __run(__input == null ? "" : String(__input)); } ``` ## Checks - Matrix axes: Array-valued keys are expanded into concrete job combinations. - Exclude rules: Combinations matching every key in an exclude object are removed from the preview. - Include rules: Included combinations are appended so special-case jobs are visible. - Simple YAML fallback: Basic lines such as os: [ubuntu-latest, macos-latest] can be parsed when JSON is not pasted. - Large matrix warning: More than 128 jobs triggers a truncation warning so runaway matrices are obvious. ## Related Tools - [GitHub Actions Cron Explainer](/developer-tools/github-actions-cron-explainer/): Paste a five-field cron expression and see what GitHub Actions will run next in UTC and local time.