From 6615374904c66d8bb9340ef85a54352532721b97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 18:11:18 +0000 Subject: [PATCH 01/11] Initial plan From 2209bc4c6656dd79f4d13dd544bb5c4cae58910d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 18:39:04 +0000 Subject: [PATCH 02/11] feat: Add push_repo_memory MCP tool for early memory size validation Adds a new `push_repo_memory` safe output MCP tool that allows the AI agent to validate repo-memory file sizes during its session, before the post-run push job runs. When memory exceeds configured limits, the tool returns a clear error message suggesting the agent reduce content size. Changes: - Add `push_repo_memory` tool to safe_outputs_tools.json - Add `pushRepoMemoryHandler` to safe_outputs_handlers.cjs - Register handler in safe_outputs_tools_loader.cjs - Update safe_outputs_generation.go to include repo-memory config in safe outputs JSON when repo-memory is configured - Update repo_memory_prompt.md and repo_memory_prompt_multi.md to instruct agents to call push_repo_memory after writing memory - Update TestGetSafeOutputsToolsJSON to include push_repo_memory Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- actions/setup/js/safe_outputs_handlers.cjs | 174 ++++++++++++++++++ actions/setup/js/safe_outputs_tools.json | 91 ++++++--- .../setup/js/safe_outputs_tools_loader.cjs | 1 + actions/setup/md/repo_memory_prompt.md | 2 + actions/setup/md/repo_memory_prompt_multi.md | 2 + pkg/workflow/js/safe_outputs_tools.json | 14 ++ pkg/workflow/safe_outputs_generation.go | 25 +++ pkg/workflow/safe_outputs_tools_test.go | 1 + 8 files changed, 282 insertions(+), 28 deletions(-) diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index fe5b1d73346..5d036360a9b 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -470,6 +470,179 @@ function createHandlers(server, appendSafeOutput, config = {}) { }; }; + /** + * Handler for push_repo_memory tool + * Validates that memory files in the configured memory directory are within size limits. + * Returns an error if any file or the total size exceeds the configured limits, + * with guidance to reduce memory size before the workflow completes. + */ + const pushRepoMemoryHandler = args => { + const memoryId = (args && args.memory_id) || "default"; + const repoMemoryConfig = config.push_repo_memory; + + if (!repoMemoryConfig || !repoMemoryConfig.memories || repoMemoryConfig.memories.length === 0) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ result: "success", message: "No repo-memory configured." }), + }, + ], + }; + } + + // Find the memory config for the requested memory_id + const memoryConf = repoMemoryConfig.memories.find(m => m.id === memoryId); + if (!memoryConf) { + const availableIds = repoMemoryConfig.memories.map(m => m.id).join(", "); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "error", + error: `Memory ID '${memoryId}' not found. Available memory IDs: ${availableIds}`, + }), + }, + ], + isError: true, + }; + } + + const memoryDir = memoryConf.dir; + const maxFileSize = memoryConf.max_file_size || 10240; + const maxPatchSize = memoryConf.max_patch_size || 10240; + const maxFileCount = memoryConf.max_file_count || 100; + // Allow 20% overhead for git diff format (headers, context lines, etc.) + const effectiveMaxPatchSize = Math.floor(maxPatchSize * 1.2); + + if (!fs.existsSync(memoryDir)) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ result: "success", message: `Memory directory '${memoryDir}' does not exist yet. No files to validate.` }), + }, + ], + }; + } + + // Recursively scan all files in the memory directory + /** @type {Array<{relativePath: string, size: number}>} */ + const files = []; + + /** + * @param {string} dirPath + * @param {string} relativePath + */ + function scanDir(dirPath, relativePath) { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name); + const relPath = relativePath ? path.join(relativePath, entry.name) : entry.name; + if (entry.isDirectory()) { + scanDir(fullPath, relPath); + } else if (entry.isFile()) { + const stats = fs.statSync(fullPath); + files.push({ relativePath: relPath.replace(/\\/g, "/"), size: stats.size }); + } + } + } + + try { + scanDir(memoryDir, ""); + } catch (/** @type {any} */ error) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "error", + error: `Failed to scan memory directory: ${getErrorMessage(error)}`, + }), + }, + ], + isError: true, + }; + } + + // Check individual file sizes + const oversizedFiles = files.filter(f => f.size > maxFileSize); + if (oversizedFiles.length > 0) { + const details = oversizedFiles.map(f => ` - ${f.relativePath} (${f.size} bytes > ${maxFileSize} bytes limit)`).join("\n"); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "error", + error: + `${oversizedFiles.length} file(s) exceed the maximum file size of ${maxFileSize} bytes (${Math.ceil(maxFileSize / 1024)} KB):\n${details}\n\n` + + `Please reduce the size of these files before the workflow completes. Consider summarizing or truncating the content.`, + }), + }, + ], + isError: true, + }; + } + + // Check file count + if (files.length > maxFileCount) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "error", + error: `Too many files in memory: ${files.length} files exceeds the limit of ${maxFileCount} files.\n\n` + `Please reduce the number of files in '${memoryDir}' before the workflow completes.`, + }), + }, + ], + isError: true, + }; + } + + // Check total size (approximation: sum of all file sizes + 20% overhead for git diff format) + const totalSize = files.reduce((sum, f) => sum + f.size, 0); + const totalSizeKb = Math.ceil(totalSize / 1024); + const maxPatchSizeKb = Math.floor(maxPatchSize / 1024); + const effectiveMaxKb = Math.floor(effectiveMaxPatchSize / 1024); + + server.debug(`push_repo_memory validation: ${files.length} files, total ${totalSize} bytes, effective limit ${effectiveMaxPatchSize} bytes`); + + if (totalSize > effectiveMaxPatchSize) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "error", + error: + `Total memory size (${totalSizeKb} KB, ${totalSize} bytes) exceeds the maximum allowed patch size ` + + `(${effectiveMaxKb} KB, ${effectiveMaxPatchSize} bytes, configured limit: ${maxPatchSizeKb} KB with 20% overhead allowance).\n\n` + + `Please reduce the total size of files in '${memoryDir}' before the workflow completes. ` + + `Consider: summarizing notes instead of keeping full history, removing outdated entries, or compressing data. ` + + `Then call push_repo_memory again to verify the size is within limits.`, + }), + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "success", + message: `Memory validation passed: ${files.length} file(s), ${totalSizeKb} KB total (limit: ${effectiveMaxKb} KB).`, + }), + }, + ], + }; + }; + /** * Handler for create_project tool * Auto-generates a temporary ID if not provided and returns it to the agent @@ -565,6 +738,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { uploadAssetHandler, createPullRequestHandler, pushToPullRequestBranchHandler, + pushRepoMemoryHandler, createProjectHandler, addCommentHandler, }; diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index 0be865a5a9c..ecd598df9cc 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -128,7 +128,7 @@ }, { "name": "close_discussion", - "description": "Close a GitHub discussion with a resolution comment and optional reason. Use this to mark discussions as resolved, answered, or no longer needed. The closing comment should explain why the discussion is being closed.", + "description": "Close a GitHub discussion with a resolution comment and optional reason. You can and should always add a comment when closing a discussion to explain the action or provide context. Use this to mark discussions as resolved, answered, or no longer needed. The closing comment should explain why the discussion is being closed. If the discussion is already closed, a comment will still be posted.", "inputSchema": { "type": "object", "required": ["body"], @@ -160,7 +160,7 @@ }, { "name": "close_issue", - "description": "Close a GitHub issue with a closing comment. Use this when work is complete, the issue is no longer relevant, or it's a duplicate. The closing comment should explain the resolution or reason for closing.", + "description": "Close a GitHub issue with a closing comment. You can and should always add a comment when closing an issue to explain the action or provide context. This tool is ONLY for closing issues - use update_issue if you need to change the title, body, labels, or other metadata without closing. Use close_issue when work is complete, the issue is no longer relevant, or it's a duplicate. The closing comment should explain the resolution or reason for closing. If the issue is already closed, a comment will still be posted.", "inputSchema": { "type": "object", "required": ["body"], @@ -173,11 +173,6 @@ "type": ["number", "string"], "description": "Issue number to close. This is the numeric ID from the GitHub URL (e.g., 901 in github.com/owner/repo/issues/901). If omitted, closes the issue that triggered this workflow (requires an issue event trigger)." }, - "state_reason": { - "type": "string", - "enum": ["COMPLETED", "NOT_PLANNED", "DUPLICATE"], - "description": "The reason for closing the issue. Use 'COMPLETED' for resolved issues, 'NOT_PLANNED' for issues that won't be addressed, or 'DUPLICATE' for duplicate issues. Defaults to 'COMPLETED'." - }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -192,7 +187,7 @@ }, { "name": "close_pull_request", - "description": "Close a pull request WITHOUT merging, adding a closing comment. Use this for PRs that should be abandoned, superseded, or closed for other reasons. The closing comment should explain why the PR is being closed. This does NOT merge the changes.", + "description": "Close a pull request WITHOUT merging, adding a closing comment. You can and should always add a comment when closing a PR to explain the action or provide context. Use this for PRs that should be abandoned, superseded, or closed for other reasons. The closing comment should explain why the PR is being closed. This does NOT merge the changes. If the PR is already closed, a comment will still be posted.", "inputSchema": { "type": "object", "required": ["body"], @@ -219,18 +214,18 @@ }, { "name": "add_comment", - "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead.", + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission.", "inputSchema": { "type": "object", "required": ["body"], "properties": { "body": { "type": "string", - "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation." + "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated." }, "item_number": { "type": ["number", "string"], - "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). Can also be a temporary_id (e.g., 'aw_abc123') from a previously created issue in the same workflow run. If omitted, the tool will attempt to resolve the target from the current workflow context (triggering issue, PR, or discussion). If auto-targeting fails (e.g., for schedule or workflow_dispatch triggers), the tool call will fail with an error." + "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). Can also be a temporary_id (e.g., 'aw_abc123') from a previously created issue in the same workflow run. If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers \u2014 it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the tool call will fail with an error." }, "temporary_id": { "type": "string", @@ -281,7 +276,7 @@ }, "repo": { "type": "string", - "description": "Target repository in 'owner/repo' format. Required when changes are in a subdirectory checkout (e.g., 'repos/repo-a/'). Must be in the allowed-repos list. If omitted, uses the repository at the workspace root." + "description": "Target repository in 'owner/repo' format. For multi-repo workflows where the target repo differs from the workflow repo, this must match a repo in the allowed-repos list or the configured target-repo. If omitted, defaults to the configured target-repo (from safe-outputs config), NOT the workflow repository. In most cases, you should omit this parameter and let the system use the configured default." }, "secrecy": { "type": "string", @@ -475,7 +470,7 @@ }, "item_number": { "type": "number", - "description": "Issue or PR number to add labels to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/issues/456). If omitted, adds labels to the item that triggered this workflow." + "description": "Issue or PR number to add labels to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/issues/456). If omitted, adds labels to the issue or PR that triggered this workflow. Only works for issue or pull_request event triggers. For schedule, workflow_dispatch, or other triggers, item_number is required \u2014 omitting it will silently skip the label operation." }, "secrecy": { "type": "string", @@ -491,7 +486,7 @@ }, { "name": "remove_labels", - "description": "Remove labels from an existing GitHub issue or pull request. If a label is not present on the item, it will be silently skipped. Use this to clean up labels that are no longer applicable.", + "description": "Remove labels from an existing GitHub issue or pull request. Silently skips labels that don't exist on the item. Use this to clean up labels or manage label lifecycles (e.g., removing 'needs-review' after review is complete).", "inputSchema": { "type": "object", "properties": { @@ -500,8 +495,7 @@ "items": { "type": "string" }, - "minItems": 1, - "description": "Label names to remove (e.g., ['bug', 'needs-triage']). Labels that don't exist on the item are silently skipped. At least one label must be provided." + "description": "Label names to remove (e.g., ['smoke', 'needs-triage']). Non-existent labels are silently skipped." }, "item_number": { "type": "number", @@ -536,7 +530,7 @@ }, "pull_request_number": { "type": ["number", "string"], - "description": "Pull request number to add reviewers to. This is the numeric ID from the GitHub URL (e.g., 876 in github.com/owner/repo/pull/876). If omitted, adds reviewers to the PR that triggered this workflow." + "description": "Pull request number to add reviewers to. This is the numeric ID from the GitHub URL (e.g., 876 in github.com/owner/repo/pull/876). If omitted, adds reviewers to the PR that triggered this workflow. Only works for pull_request event triggers. For workflow_dispatch, schedule, or other triggers, pull_request_number is required \u2014 omitting it will silently skip the reviewer assignment." }, "secrecy": { "type": "string", @@ -579,13 +573,13 @@ }, { "name": "assign_to_agent", - "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\")", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\")", "inputSchema": { "type": "object", "properties": { "issue_number": { "type": ["number", "string"], - "description": "Issue number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both." + "description": "Issue number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from an issue created earlier in the same workflow run. The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both." }, "pull_number": { "type": ["number", "string"], @@ -684,7 +678,7 @@ }, { "name": "update_issue", - "description": "Update an existing GitHub issue's status, title, or body. Use this to modify issue properties after creation. Only the fields you specify will be updated; other fields remain unchanged.", + "description": "Update an existing GitHub issue's title, body, labels, assignees, or milestone WITHOUT closing it. This tool is primarily for editing issue metadata and content. While it supports changing status between 'open' and 'closed', use close_issue instead when you want to close an issue with a closing comment. Body updates support replacing, appending to, prepending content, or updating a per-run \"island\" section.", "inputSchema": { "type": "object", "properties": { @@ -699,11 +693,7 @@ }, "body": { "type": "string", - "description": "New issue body to replace the existing content. Use Markdown formatting." - }, - "issue_number": { - "type": ["number", "string"], - "description": "Issue number to update. This is the numeric ID from the GitHub URL (e.g., 789 in github.com/owner/repo/issues/789). Required when the workflow target is '*' (any issue)." + "description": "Issue body content in Markdown. For 'replace', this becomes the entire body. For 'append'/'prepend', this content is added with a separator and an attribution footer. For 'replace-island', only the run-specific section is updated." }, "operation": { "type": "string", @@ -728,6 +718,10 @@ "type": ["number", "string"], "description": "Milestone number to assign (e.g., 1). Use null to clear." }, + "issue_number": { + "type": ["number", "string"], + "description": "Issue number to update. This is the numeric ID from the GitHub URL (e.g., 789 in github.com/owner/repo/issues/789). Required when the workflow target is '*' (any issue)." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -948,7 +942,7 @@ }, { "name": "hide_comment", - "description": "Hide a comment on a GitHub issue, pull request, or discussion. This collapses the comment and marks it as spam, abuse, off-topic, outdated, or resolved. Use this for inappropriate, off-topic, or outdated comments. The comment_id must be a GraphQL node ID (string like 'IC_kwDOABCD123456'), not a numeric REST API comment ID.", + "description": "Hide a comment on a GitHub issue, pull request, or discussion. This collapses the comment and marks it as spam, abuse, off-topic, outdated, or resolved. Use this for inappropriate, off-topic, or outdated comments. The comment_id must be a GraphQL node ID (string like 'IC_kwDOABCD123456'), not a numeric REST API comment ID. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.hide-comment configuration to exclude this permission.", "inputSchema": { "type": "object", "required": ["comment_id"], @@ -974,6 +968,33 @@ "additionalProperties": false } }, + { + "name": "set_issue_type", + "description": "Set the type of a GitHub issue. Pass an empty string \"\" to clear the issue type. Issue types must be configured in the repository or organization settings before they can be assigned.", + "inputSchema": { + "type": "object", + "required": ["issue_type"], + "properties": { + "issue_number": { + "type": ["number", "string"], + "description": "Issue number to set the type for. If omitted, sets the type on the issue that triggered this workflow." + }, + "issue_type": { + "type": "string", + "description": "Issue type name to set (e.g., \"Bug\", \"Feature\", \"Task\"). Use an empty string \"\" to clear the current issue type." + }, + "secrecy": { + "type": "string", + "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." + }, + "integrity": { + "type": "string", + "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\")." + } + }, + "additionalProperties": false + } + }, { "name": "update_project", "description": "Manage GitHub Projects: add issues/pull requests/draft issues, update item fields (status, priority, effort, dates), manage custom fields, and create project views. Use this to organize work by adding items to projects, updating field values, creating custom fields up-front, and setting up project views (table, board, roadmap).\n\nThree modes: (1) Add or update project items with custom field values; (2) Create project fields; (3) Create project views. This is the primary tool for ProjectOps automation - add items to projects, set custom fields for tracking, and organize project boards.", @@ -1002,7 +1023,7 @@ }, "draft_title": { "type": "string", - "description": "Title for a Projects v2 draft issue. Required when content_type is 'draft_issue' and creating a new draft (when draft_issue_id is not provided)." + "description": "Title for a Projects v2 draft issue. Required when content_type is 'draft_issue'." }, "draft_body": { "type": "string", @@ -1127,7 +1148,7 @@ }, { "name": "create_project", - "description": "Create a new GitHub Project for organizing and tracking work across issues and pull requests. You can optionally provide a title, owner, owner_type, and an initial issue item_url to add to the project. You may also provide a temporary_id to reference this project before it is created; if not provided, a temporary_id will be auto-generated and returned, and can be used in subsequent update_project calls via the '#aw_ID' syntax.", + "description": "Create a new GitHub Project board. Use this to create a new project for organizing and tracking work across issues and pull requests, owned by a specific user or organization. You can optionally provide a custom title and add an initial issue item to the project via item_url.", "inputSchema": { "type": "object", "required": [], @@ -1267,5 +1288,19 @@ }, "additionalProperties": false } + }, + { + "name": "push_repo_memory", + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "type": "object", + "properties": { + "memory_id": { + "type": "string", + "description": "Memory identifier to validate. Defaults to 'default' if not specified." + } + }, + "additionalProperties": false + } } ] diff --git a/actions/setup/js/safe_outputs_tools_loader.cjs b/actions/setup/js/safe_outputs_tools_loader.cjs index c1b9d3d313a..7d50a9638ac 100644 --- a/actions/setup/js/safe_outputs_tools_loader.cjs +++ b/actions/setup/js/safe_outputs_tools_loader.cjs @@ -56,6 +56,7 @@ function attachHandlers(tools, handlers) { const handlerMap = { create_pull_request: handlers.createPullRequestHandler, push_to_pull_request_branch: handlers.pushToPullRequestBranchHandler, + push_repo_memory: handlers.pushRepoMemoryHandler, upload_asset: handlers.uploadAssetHandler, create_project: handlers.createProjectHandler, add_comment: handlers.addCommentHandler, diff --git a/actions/setup/md/repo_memory_prompt.md b/actions/setup/md/repo_memory_prompt.md index a4dd0f9c32a..d3ed121716d 100644 --- a/actions/setup/md/repo_memory_prompt.md +++ b/actions/setup/md/repo_memory_prompt.md @@ -18,4 +18,6 @@ Examples of what you can store: - `__GH_AW_MEMORY_DIR__history/` - organized history files in subdirectories (with allowed file types) Feel free to create, read, update, and organize files in this folder as needed for your tasks, using only the allowed file types. + +**Important**: After writing or updating memory files, call the `push_repo_memory` tool to validate that the total memory size is within the configured limits. If the tool returns an error, reduce the size of your memory files (e.g., summarize notes, remove outdated entries) and try again before completing your task. diff --git a/actions/setup/md/repo_memory_prompt_multi.md b/actions/setup/md/repo_memory_prompt_multi.md index 7d1741b3476..d689a292da6 100644 --- a/actions/setup/md/repo_memory_prompt_multi.md +++ b/actions/setup/md/repo_memory_prompt_multi.md @@ -20,4 +20,6 @@ Examples of what you can store: - `/tmp/gh-aw/repo-memory/history/` - organized history files (with allowed file types) Feel free to create, read, update, and organize files in these folders as needed for your tasks, using only the allowed file types. + +**Important**: After writing or updating memory files, call the `push_repo_memory` tool (with the appropriate `memory_id`) to validate that the total memory size is within the configured limits. If the tool returns an error, reduce the size of your memory files (e.g., summarize notes, remove outdated entries) and try again before completing your task. diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 6740c37cd34..8d7764a4e69 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -1518,5 +1518,19 @@ }, "additionalProperties": false } + }, + { + "name": "push_repo_memory", + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "type": "object", + "properties": { + "memory_id": { + "type": "string", + "description": "Memory identifier to validate. Defaults to 'default' if not specified." + } + }, + "additionalProperties": false + } } ] diff --git a/pkg/workflow/safe_outputs_generation.go b/pkg/workflow/safe_outputs_generation.go index 1482fd51c3c..4c90f7b7605 100644 --- a/pkg/workflow/safe_outputs_generation.go +++ b/pkg/workflow/safe_outputs_generation.go @@ -599,6 +599,25 @@ func generateSafeOutputsConfig(data *WorkflowData) string { } } + // Add push_repo_memory config if repo-memory is configured + // This enables the push_repo_memory MCP tool for early size validation during agent session + if data.RepoMemoryConfig != nil && len(data.RepoMemoryConfig.Memories) > 0 { + var memories []map[string]any + for _, memory := range data.RepoMemoryConfig.Memories { + memories = append(memories, map[string]any{ + "id": memory.ID, + "dir": "/tmp/gh-aw/repo-memory/" + memory.ID, + "max_file_size": memory.MaxFileSize, + "max_patch_size": memory.MaxPatchSize, + "max_file_count": memory.MaxFileCount, + }) + } + safeOutputsConfig["push_repo_memory"] = map[string]any{ + "memories": memories, + } + safeOutputsConfigLog.Printf("Added push_repo_memory config with %d memory entries", len(memories)) + } + configJSON, _ := json.Marshal(safeOutputsConfig) safeOutputsConfigLog.Printf("Safe outputs config generation complete: %d tool types configured", len(safeOutputsConfig)) return string(configJSON) @@ -1210,6 +1229,12 @@ func generateFilteredToolsJSON(data *WorkflowData, markdownPath string) (string, } // Note: dispatch_workflow tools are generated dynamically below, not from the static tools list + // Add push_repo_memory tool if repo-memory is configured + // This tool enables early size validation during the agent session + if data.RepoMemoryConfig != nil && len(data.RepoMemoryConfig.Memories) > 0 { + enabledTools["push_repo_memory"] = true + } + // Filter tools to only include enabled ones and enhance descriptions var filteredTools []map[string]any for _, tool := range allTools { diff --git a/pkg/workflow/safe_outputs_tools_test.go b/pkg/workflow/safe_outputs_tools_test.go index 03e66a6e40c..70ae2011041 100644 --- a/pkg/workflow/safe_outputs_tools_test.go +++ b/pkg/workflow/safe_outputs_tools_test.go @@ -339,6 +339,7 @@ func TestGetSafeOutputsToolsJSON(t *testing.T) { "create_project", "create_project_status_update", "autofix_code_scanning_alert", + "push_repo_memory", "missing_tool", "missing_data", "noop", From f6dcd7f436493a648f6d99298dafcc3be1eac7d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 18:42:51 +0000 Subject: [PATCH 03/11] fix: Restore actions/setup/js/safe_outputs_tools.json to only add push_repo_memory Address code review: only add push_repo_memory to actions/setup/js/safe_outputs_tools.json without bringing in unrelated sync changes from pkg/workflow/js/safe_outputs_tools.json. Also improve handler comments and error message clarity. Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- actions/setup/js/safe_outputs_handlers.cjs | 12 ++-- actions/setup/js/safe_outputs_tools.json | 77 ++++++++-------------- 2 files changed, 35 insertions(+), 54 deletions(-) diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 5d036360a9b..31456aa9999 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -602,10 +602,12 @@ function createHandlers(server, appendSafeOutput, config = {}) { }; } - // Check total size (approximation: sum of all file sizes + 20% overhead for git diff format) + // Check total size. The effective limit allows 20% overhead to account for + // git diff format overhead (headers, context lines, metadata). This mirrors + // the same calculation in push_repo_memory.cjs. The totalSize is the raw + // sum of file sizes; it is compared against the overhead-adjusted limit. const totalSize = files.reduce((sum, f) => sum + f.size, 0); const totalSizeKb = Math.ceil(totalSize / 1024); - const maxPatchSizeKb = Math.floor(maxPatchSize / 1024); const effectiveMaxKb = Math.floor(effectiveMaxPatchSize / 1024); server.debug(`push_repo_memory validation: ${files.length} files, total ${totalSize} bytes, effective limit ${effectiveMaxPatchSize} bytes`); @@ -618,8 +620,8 @@ function createHandlers(server, appendSafeOutput, config = {}) { text: JSON.stringify({ result: "error", error: - `Total memory size (${totalSizeKb} KB, ${totalSize} bytes) exceeds the maximum allowed patch size ` + - `(${effectiveMaxKb} KB, ${effectiveMaxPatchSize} bytes, configured limit: ${maxPatchSizeKb} KB with 20% overhead allowance).\n\n` + + `Total memory size (${totalSizeKb} KB) exceeds the allowed limit of ${effectiveMaxKb} KB ` + + `(configured limit: ${Math.floor(maxPatchSize / 1024)} KB with 20% overhead for git diff format).\n\n` + `Please reduce the total size of files in '${memoryDir}' before the workflow completes. ` + `Consider: summarizing notes instead of keeping full history, removing outdated entries, or compressing data. ` + `Then call push_repo_memory again to verify the size is within limits.`, @@ -636,7 +638,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { type: "text", text: JSON.stringify({ result: "success", - message: `Memory validation passed: ${files.length} file(s), ${totalSizeKb} KB total (limit: ${effectiveMaxKb} KB).`, + message: `Memory validation passed: ${files.length} file(s), ${totalSizeKb} KB total (limit: ${effectiveMaxKb} KB with 20% overhead).`, }), }, ], diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index ecd598df9cc..83b77af4d74 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -128,7 +128,7 @@ }, { "name": "close_discussion", - "description": "Close a GitHub discussion with a resolution comment and optional reason. You can and should always add a comment when closing a discussion to explain the action or provide context. Use this to mark discussions as resolved, answered, or no longer needed. The closing comment should explain why the discussion is being closed. If the discussion is already closed, a comment will still be posted.", + "description": "Close a GitHub discussion with a resolution comment and optional reason. Use this to mark discussions as resolved, answered, or no longer needed. The closing comment should explain why the discussion is being closed.", "inputSchema": { "type": "object", "required": ["body"], @@ -160,7 +160,7 @@ }, { "name": "close_issue", - "description": "Close a GitHub issue with a closing comment. You can and should always add a comment when closing an issue to explain the action or provide context. This tool is ONLY for closing issues - use update_issue if you need to change the title, body, labels, or other metadata without closing. Use close_issue when work is complete, the issue is no longer relevant, or it's a duplicate. The closing comment should explain the resolution or reason for closing. If the issue is already closed, a comment will still be posted.", + "description": "Close a GitHub issue with a closing comment. Use this when work is complete, the issue is no longer relevant, or it's a duplicate. The closing comment should explain the resolution or reason for closing.", "inputSchema": { "type": "object", "required": ["body"], @@ -173,6 +173,11 @@ "type": ["number", "string"], "description": "Issue number to close. This is the numeric ID from the GitHub URL (e.g., 901 in github.com/owner/repo/issues/901). If omitted, closes the issue that triggered this workflow (requires an issue event trigger)." }, + "state_reason": { + "type": "string", + "enum": ["COMPLETED", "NOT_PLANNED", "DUPLICATE"], + "description": "The reason for closing the issue. Use 'COMPLETED' for resolved issues, 'NOT_PLANNED' for issues that won't be addressed, or 'DUPLICATE' for duplicate issues. Defaults to 'COMPLETED'." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -187,7 +192,7 @@ }, { "name": "close_pull_request", - "description": "Close a pull request WITHOUT merging, adding a closing comment. You can and should always add a comment when closing a PR to explain the action or provide context. Use this for PRs that should be abandoned, superseded, or closed for other reasons. The closing comment should explain why the PR is being closed. This does NOT merge the changes. If the PR is already closed, a comment will still be posted.", + "description": "Close a pull request WITHOUT merging, adding a closing comment. Use this for PRs that should be abandoned, superseded, or closed for other reasons. The closing comment should explain why the PR is being closed. This does NOT merge the changes.", "inputSchema": { "type": "object", "required": ["body"], @@ -214,18 +219,18 @@ }, { "name": "add_comment", - "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission.", + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead.", "inputSchema": { "type": "object", "required": ["body"], "properties": { "body": { "type": "string", - "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated." + "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation." }, "item_number": { "type": ["number", "string"], - "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). Can also be a temporary_id (e.g., 'aw_abc123') from a previously created issue in the same workflow run. If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers \u2014 it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the tool call will fail with an error." + "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). Can also be a temporary_id (e.g., 'aw_abc123') from a previously created issue in the same workflow run. If omitted, the tool will attempt to resolve the target from the current workflow context (triggering issue, PR, or discussion). If auto-targeting fails (e.g., for schedule or workflow_dispatch triggers), the tool call will fail with an error." }, "temporary_id": { "type": "string", @@ -276,7 +281,7 @@ }, "repo": { "type": "string", - "description": "Target repository in 'owner/repo' format. For multi-repo workflows where the target repo differs from the workflow repo, this must match a repo in the allowed-repos list or the configured target-repo. If omitted, defaults to the configured target-repo (from safe-outputs config), NOT the workflow repository. In most cases, you should omit this parameter and let the system use the configured default." + "description": "Target repository in 'owner/repo' format. Required when changes are in a subdirectory checkout (e.g., 'repos/repo-a/'). Must be in the allowed-repos list. If omitted, uses the repository at the workspace root." }, "secrecy": { "type": "string", @@ -470,7 +475,7 @@ }, "item_number": { "type": "number", - "description": "Issue or PR number to add labels to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/issues/456). If omitted, adds labels to the issue or PR that triggered this workflow. Only works for issue or pull_request event triggers. For schedule, workflow_dispatch, or other triggers, item_number is required \u2014 omitting it will silently skip the label operation." + "description": "Issue or PR number to add labels to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/issues/456). If omitted, adds labels to the item that triggered this workflow." }, "secrecy": { "type": "string", @@ -486,7 +491,7 @@ }, { "name": "remove_labels", - "description": "Remove labels from an existing GitHub issue or pull request. Silently skips labels that don't exist on the item. Use this to clean up labels or manage label lifecycles (e.g., removing 'needs-review' after review is complete).", + "description": "Remove labels from an existing GitHub issue or pull request. If a label is not present on the item, it will be silently skipped. Use this to clean up labels that are no longer applicable.", "inputSchema": { "type": "object", "properties": { @@ -495,7 +500,8 @@ "items": { "type": "string" }, - "description": "Label names to remove (e.g., ['smoke', 'needs-triage']). Non-existent labels are silently skipped." + "minItems": 1, + "description": "Label names to remove (e.g., ['bug', 'needs-triage']). Labels that don't exist on the item are silently skipped. At least one label must be provided." }, "item_number": { "type": "number", @@ -530,7 +536,7 @@ }, "pull_request_number": { "type": ["number", "string"], - "description": "Pull request number to add reviewers to. This is the numeric ID from the GitHub URL (e.g., 876 in github.com/owner/repo/pull/876). If omitted, adds reviewers to the PR that triggered this workflow. Only works for pull_request event triggers. For workflow_dispatch, schedule, or other triggers, pull_request_number is required \u2014 omitting it will silently skip the reviewer assignment." + "description": "Pull request number to add reviewers to. This is the numeric ID from the GitHub URL (e.g., 876 in github.com/owner/repo/pull/876). If omitted, adds reviewers to the PR that triggered this workflow." }, "secrecy": { "type": "string", @@ -573,13 +579,13 @@ }, { "name": "assign_to_agent", - "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\")", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\")", "inputSchema": { "type": "object", "properties": { "issue_number": { "type": ["number", "string"], - "description": "Issue number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from an issue created earlier in the same workflow run. The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both." + "description": "Issue number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both." }, "pull_number": { "type": ["number", "string"], @@ -678,7 +684,7 @@ }, { "name": "update_issue", - "description": "Update an existing GitHub issue's title, body, labels, assignees, or milestone WITHOUT closing it. This tool is primarily for editing issue metadata and content. While it supports changing status between 'open' and 'closed', use close_issue instead when you want to close an issue with a closing comment. Body updates support replacing, appending to, prepending content, or updating a per-run \"island\" section.", + "description": "Update an existing GitHub issue's status, title, or body. Use this to modify issue properties after creation. Only the fields you specify will be updated; other fields remain unchanged.", "inputSchema": { "type": "object", "properties": { @@ -693,7 +699,11 @@ }, "body": { "type": "string", - "description": "Issue body content in Markdown. For 'replace', this becomes the entire body. For 'append'/'prepend', this content is added with a separator and an attribution footer. For 'replace-island', only the run-specific section is updated." + "description": "New issue body to replace the existing content. Use Markdown formatting." + }, + "issue_number": { + "type": ["number", "string"], + "description": "Issue number to update. This is the numeric ID from the GitHub URL (e.g., 789 in github.com/owner/repo/issues/789). Required when the workflow target is '*' (any issue)." }, "operation": { "type": "string", @@ -718,10 +728,6 @@ "type": ["number", "string"], "description": "Milestone number to assign (e.g., 1). Use null to clear." }, - "issue_number": { - "type": ["number", "string"], - "description": "Issue number to update. This is the numeric ID from the GitHub URL (e.g., 789 in github.com/owner/repo/issues/789). Required when the workflow target is '*' (any issue)." - }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -942,7 +948,7 @@ }, { "name": "hide_comment", - "description": "Hide a comment on a GitHub issue, pull request, or discussion. This collapses the comment and marks it as spam, abuse, off-topic, outdated, or resolved. Use this for inappropriate, off-topic, or outdated comments. The comment_id must be a GraphQL node ID (string like 'IC_kwDOABCD123456'), not a numeric REST API comment ID. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.hide-comment configuration to exclude this permission.", + "description": "Hide a comment on a GitHub issue, pull request, or discussion. This collapses the comment and marks it as spam, abuse, off-topic, outdated, or resolved. Use this for inappropriate, off-topic, or outdated comments. The comment_id must be a GraphQL node ID (string like 'IC_kwDOABCD123456'), not a numeric REST API comment ID.", "inputSchema": { "type": "object", "required": ["comment_id"], @@ -968,33 +974,6 @@ "additionalProperties": false } }, - { - "name": "set_issue_type", - "description": "Set the type of a GitHub issue. Pass an empty string \"\" to clear the issue type. Issue types must be configured in the repository or organization settings before they can be assigned.", - "inputSchema": { - "type": "object", - "required": ["issue_type"], - "properties": { - "issue_number": { - "type": ["number", "string"], - "description": "Issue number to set the type for. If omitted, sets the type on the issue that triggered this workflow." - }, - "issue_type": { - "type": "string", - "description": "Issue type name to set (e.g., \"Bug\", \"Feature\", \"Task\"). Use an empty string \"\" to clear the current issue type." - }, - "secrecy": { - "type": "string", - "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." - }, - "integrity": { - "type": "string", - "description": "Trustworthiness level of the message source (e.g., \"low\", \"medium\", \"high\")." - } - }, - "additionalProperties": false - } - }, { "name": "update_project", "description": "Manage GitHub Projects: add issues/pull requests/draft issues, update item fields (status, priority, effort, dates), manage custom fields, and create project views. Use this to organize work by adding items to projects, updating field values, creating custom fields up-front, and setting up project views (table, board, roadmap).\n\nThree modes: (1) Add or update project items with custom field values; (2) Create project fields; (3) Create project views. This is the primary tool for ProjectOps automation - add items to projects, set custom fields for tracking, and organize project boards.", @@ -1023,7 +1002,7 @@ }, "draft_title": { "type": "string", - "description": "Title for a Projects v2 draft issue. Required when content_type is 'draft_issue'." + "description": "Title for a Projects v2 draft issue. Required when content_type is 'draft_issue' and creating a new draft (when draft_issue_id is not provided)." }, "draft_body": { "type": "string", @@ -1148,7 +1127,7 @@ }, { "name": "create_project", - "description": "Create a new GitHub Project board. Use this to create a new project for organizing and tracking work across issues and pull requests, owned by a specific user or organization. You can optionally provide a custom title and add an initial issue item to the project via item_url.", + "description": "Create a new GitHub Project for organizing and tracking work across issues and pull requests. You can optionally provide a title, owner, owner_type, and an initial issue item_url to add to the project. You may also provide a temporary_id to reference this project before it is created; if not provided, a temporary_id will be auto-generated and returned, and can be used in subsequent update_project calls via the '#aw_ID' syntax.", "inputSchema": { "type": "object", "required": [], From 885dd9cea783cb732d9edc8a81be744ccf4bee6d Mon Sep 17 00:00:00 2001 From: Don Syme Date: Sat, 7 Mar 2026 20:06:15 +0000 Subject: [PATCH 04/11] Update actions/setup/js/safe_outputs_tools.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/setup/js/safe_outputs_tools.json | 1 + 1 file changed, 1 insertion(+) diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index 83b77af4d74..d21a682c1d8 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -1273,6 +1273,7 @@ "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", "inputSchema": { "type": "object", + "required": [], "properties": { "memory_id": { "type": "string", From 2376d3281e2273787affacfc86be6c30719e28ad Mon Sep 17 00:00:00 2001 From: Don Syme Date: Sat, 7 Mar 2026 20:06:25 +0000 Subject: [PATCH 05/11] Update pkg/workflow/js/safe_outputs_tools.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/workflow/js/safe_outputs_tools.json | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 8d7764a4e69..c07ab295fe3 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -1524,6 +1524,7 @@ "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", "inputSchema": { "type": "object", + "required": [], "properties": { "memory_id": { "type": "string", From 137b3aeb2646f5291345d2c179dcfcd4ccadfe15 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Sat, 7 Mar 2026 20:06:47 +0000 Subject: [PATCH 06/11] Update actions/setup/md/repo_memory_prompt.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/setup/md/repo_memory_prompt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/md/repo_memory_prompt.md b/actions/setup/md/repo_memory_prompt.md index d3ed121716d..9d8403bf20a 100644 --- a/actions/setup/md/repo_memory_prompt.md +++ b/actions/setup/md/repo_memory_prompt.md @@ -19,5 +19,5 @@ Examples of what you can store: Feel free to create, read, update, and organize files in this folder as needed for your tasks, using only the allowed file types. -**Important**: After writing or updating memory files, call the `push_repo_memory` tool to validate that the total memory size is within the configured limits. If the tool returns an error, reduce the size of your memory files (e.g., summarize notes, remove outdated entries) and try again before completing your task. +**Important**: If the `push_repo_memory` tool is available in your tool list, call it after writing or updating memory files to validate that the total memory size is within the configured limits. If the tool returns an error, reduce the size of your memory files (e.g., summarize notes, remove outdated entries) and try again before completing your task. If the tool is not available, you can skip this validation step. From 6d65da416e0c18e6112303c5663ec8c95780699f Mon Sep 17 00:00:00 2001 From: Don Syme Date: Sat, 7 Mar 2026 20:07:00 +0000 Subject: [PATCH 07/11] Update actions/setup/md/repo_memory_prompt_multi.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/setup/md/repo_memory_prompt_multi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/md/repo_memory_prompt_multi.md b/actions/setup/md/repo_memory_prompt_multi.md index d689a292da6..c62ee559266 100644 --- a/actions/setup/md/repo_memory_prompt_multi.md +++ b/actions/setup/md/repo_memory_prompt_multi.md @@ -21,5 +21,5 @@ Examples of what you can store: Feel free to create, read, update, and organize files in these folders as needed for your tasks, using only the allowed file types. -**Important**: After writing or updating memory files, call the `push_repo_memory` tool (with the appropriate `memory_id`) to validate that the total memory size is within the configured limits. If the tool returns an error, reduce the size of your memory files (e.g., summarize notes, remove outdated entries) and try again before completing your task. +**Important**: After writing or updating memory files, if the `push_repo_memory` tool is available, call it (with the appropriate `memory_id`) to validate that the total memory size is within the configured limits. If the tool returns an error, reduce the size of your memory files (e.g., summarize notes, remove outdated entries) and try again before completing your task. From 05738333ee1947e892eebf82eae0cac34c57c1da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:13:21 +0000 Subject: [PATCH 08/11] test: Add unit tests for pushRepoMemoryHandler in safe_outputs_handlers.test.cjs Covers: unknown memory_id, missing memory dir, oversized file, too many files, total size over limit, default memory_id, recursive subdirectory scanning, and handler structure export check. Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 17 ++- .github/workflows/audit-workflows.lock.yml | 17 ++- .../workflows/code-scanning-fixer.lock.yml | 17 ++- .../workflows/copilot-agent-analysis.lock.yml | 17 ++- .../copilot-cli-deep-research.lock.yml | 17 ++- .../copilot-pr-nlp-analysis.lock.yml | 17 ++- .../copilot-pr-prompt-analysis.lock.yml | 17 ++- .../copilot-session-insights.lock.yml | 17 ++- .../workflows/daily-cli-performance.lock.yml | 17 ++- .github/workflows/daily-code-metrics.lock.yml | 17 ++- .../daily-copilot-token-report.lock.yml | 17 ++- .github/workflows/daily-news.lock.yml | 17 ++- .../daily-testify-uber-super-expert.lock.yml | 17 ++- .github/workflows/deep-report.lock.yml | 17 ++- .github/workflows/delight.lock.yml | 17 ++- .../developer-docs-consolidator.lock.yml | 17 ++- .../workflows/discussion-task-miner.lock.yml | 17 ++- .github/workflows/firewall-escape.lock.yml | 17 ++- .../workflows/glossary-maintainer.lock.yml | 17 ++- .github/workflows/pr-triage-agent.lock.yml | 17 ++- ...ecurity-alert-burndown.campaign.g.lock.yml | 17 ++- .../workflows/security-compliance.lock.yml | 17 ++- .../workflows/technical-doc-writer.lock.yml | 17 ++- .../workflow-health-manager.lock.yml | 17 ++- .../setup/js/safe_outputs_handlers.test.cjs | 137 ++++++++++++++++++ 25 files changed, 521 insertions(+), 24 deletions(-) diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index b57a4db6c9e..e167ef9f13e 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -408,7 +408,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_comment":{"max":10},"create_discussion":{"expires":24,"max":2},"create_issue":{"expires":48,"group":true,"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":10},"create_discussion":{"expires":24,"max":2},"create_issue":{"expires":48,"group":true,"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -624,6 +624,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 6253a3d3b17..62dd87c79d2 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -465,7 +465,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}} + {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"upload_asset":{"max":0}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -620,6 +620,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/code-scanning-fixer.lock.yml b/.github/workflows/code-scanning-fixer.lock.yml index b89b2602a37..9481717e9f3 100644 --- a/.github/workflows/code-scanning-fixer.lock.yml +++ b/.github/workflows/code-scanning-fixer.lock.yml @@ -354,7 +354,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_labels":{"allowed":["agentic-campaign","z_campaign_security-alert-burndown"],"max":3},"create_pull_request":{"expires":48,"max":1,"reviewers":["copilot"],"title_prefix":"[code-scanning-fix] "},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_labels":{"allowed":["agentic-campaign","z_campaign_security-alert-burndown"],"max":3},"create_pull_request":{"expires":48,"max":1,"reviewers":["copilot"],"title_prefix":"[code-scanning-fix] "},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/campaigns","id":"campaigns","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -528,6 +528,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index a36fc1be151..bac503d9ebb 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -396,7 +396,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -526,6 +526,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index fe93a4449e7..02623be2527 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -353,7 +353,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":204800,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -483,6 +483,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 77b3039406a..9464f2a2242 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -422,7 +422,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}} + {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"upload_asset":{"max":0}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -577,6 +577,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index d486021f73c..4067b5f2731 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -388,7 +388,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -518,6 +518,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 35525e2d7ad..3a8db462fbc 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -433,7 +433,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}} + {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"upload_asset":{"max":0}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -588,6 +588,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index e444f70d111..34c796d9d14 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -358,7 +358,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_comment":{"max":5},"create_issue":{"expires":48,"group":true,"max":3},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":5},"create_issue":{"expires":48,"group":true,"max":3},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":512000,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -540,6 +540,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index 170c6117b82..42f41550615 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -407,7 +407,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":72,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}} + {"create_discussion":{"expires":72,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"upload_asset":{"max":0}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -562,6 +562,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/daily-copilot-token-report.lock.yml b/.github/workflows/daily-copilot-token-report.lock.yml index b4266c72779..ea48b8453dc 100644 --- a/.github/workflows/daily-copilot-token-report.lock.yml +++ b/.github/workflows/daily-copilot-token-report.lock.yml @@ -430,7 +430,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":72,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}} + {"create_discussion":{"expires":72,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"upload_asset":{"max":0}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -585,6 +585,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 2520ffb5fd8..450f9964ae8 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -475,7 +475,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":72,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}} + {"create_discussion":{"expires":72,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"upload_asset":{"max":0}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -630,6 +630,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index 4e28a0e709e..19201c18b5d 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -363,7 +363,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_issue":{"expires":48,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_issue":{"expires":48,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":51200,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -508,6 +508,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index cda13b6daa4..17601ca6d7b 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -449,7 +449,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":168,"max":1},"create_issue":{"expires":48,"group":true,"max":3},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}} + {"create_discussion":{"expires":168,"max":1},"create_issue":{"expires":48,"group":true,"max":3},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":1048576,"max_patch_size":10240}]},"upload_asset":{"max":0}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -653,6 +653,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index 29691ebda4e..602dd6b56ca 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -358,7 +358,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":168,"max":1},"create_issue":{"expires":48,"group":true,"max":2},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_discussion":{"expires":168,"max":1},"create_issue":{"expires":48,"group":true,"max":2},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -537,6 +537,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index a74ec6c713b..35157994599 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -384,7 +384,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":168,"max":1},"create_pull_request":{"expires":48,"max":1,"title_prefix":"[docs] "},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_discussion":{"expires":168,"max":1},"create_pull_request":{"expires":48,"max":1,"title_prefix":"[docs] "},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -563,6 +563,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/discussion-task-miner.lock.yml b/.github/workflows/discussion-task-miner.lock.yml index ddbb66fbfa6..33c6b521b83 100644 --- a/.github/workflows/discussion-task-miner.lock.yml +++ b/.github/workflows/discussion-task-miner.lock.yml @@ -349,7 +349,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_comment":{"max":3},"create_issue":{"expires":24,"group":true,"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":3},"create_issue":{"expires":24,"group":true,"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -531,6 +531,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/firewall-escape.lock.yml b/.github/workflows/firewall-escape.lock.yml index 29100220eb6..1b0609447aa 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -383,7 +383,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_discussion":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":50,"max_file_size":524288,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -513,6 +513,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 96b00004117..c8ac0aaefcb 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -393,7 +393,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_pull_request":{"expires":48,"max":1,"title_prefix":"[docs] "},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_pull_request":{"expires":48,"max":1,"title_prefix":"[docs] "},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -538,6 +538,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index e944423b52c..9d4011c582f 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -340,7 +340,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_comment":{"max":50},"add_labels":{"max":100},"create_issue":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":50},"add_labels":{"max":100},"create_issue":{"expires":24,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -551,6 +551,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/security-alert-burndown.campaign.g.lock.yml b/.github/workflows/security-alert-burndown.campaign.g.lock.yml index f80453442a3..e5d64468ea7 100644 --- a/.github/workflows/security-alert-burndown.campaign.g.lock.yml +++ b/.github/workflows/security-alert-burndown.campaign.g.lock.yml @@ -367,7 +367,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_comment":{"max":3},"create_issue":{"max":1},"create_project_status_update":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"update_project":{"max":10}} + {"add_comment":{"max":3},"create_issue":{"max":1},"create_project_status_update":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/campaigns","id":"campaigns","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]},"update_project":{"max":10}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -745,6 +745,21 @@ jobs: "type": "object" }, "name": "create_project_status_update" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index a1ead447ecb..0274f8b5f75 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -377,7 +377,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_issue":{"expires":48,"group":true,"max":100},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"create_issue":{"expires":48,"group":true,"max":100},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -522,6 +522,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index c0466a227b3..9dd971ec39e 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -411,7 +411,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_comment":{"max":1},"create_pull_request":{"expires":48,"max":1,"reviewers":["copilot"],"title_prefix":"[docs] "},"missing_data":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{"max":0}} + {"add_comment":{"max":1},"create_pull_request":{"expires":48,"max":1,"reviewers":["copilot"],"title_prefix":"[docs] "},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]},"upload_asset":{"max":0}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -618,6 +618,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 50f7d6a060e..b7f82c3089d 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -358,7 +358,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_comment":{"max":15},"create_issue":{"expires":24,"group":true,"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1},"update_issue":{"max":5}} + {"add_comment":{"max":15},"create_issue":{"expires":24,"group":true,"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"update_issue":{"max":5}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ @@ -612,6 +612,21 @@ jobs: "type": "object" }, "name": "missing_data" + }, + { + "description": "Validate repo-memory files are within configured size limits before the workflow completes. Call this after writing files to memory to check that the total size is within limits. Returns an error if files are too large, with guidance on how to reduce memory size so the memory can be saved successfully.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "memory_id": { + "description": "Memory identifier to validate. Defaults to 'default' if not specified.", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "push_repo_memory" } ] GH_AW_SAFE_OUTPUTS_TOOLS_EOF diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs index f8bcd54a126..32604538db3 100644 --- a/actions/setup/js/safe_outputs_handlers.test.cjs +++ b/actions/setup/js/safe_outputs_handlers.test.cjs @@ -447,6 +447,7 @@ describe("safe_outputs_handlers", () => { expect(handlers.uploadAssetHandler).toBeDefined(); expect(handlers.createPullRequestHandler).toBeDefined(); expect(handlers.pushToPullRequestBranchHandler).toBeDefined(); + expect(handlers.pushRepoMemoryHandler).toBeDefined(); expect(handlers.addCommentHandler).toBeDefined(); }); @@ -506,4 +507,140 @@ describe("safe_outputs_handlers", () => { expect(() => handlers.addCommentHandler({ body: longBody })).toThrow(); }); }); + + describe("pushRepoMemoryHandler", () => { + let memoryDir; + + beforeEach(() => { + const testId = Math.random().toString(36).substring(7); + memoryDir = `/tmp/test-repo-memory-${testId}`; + }); + + afterEach(() => { + try { + if (fs.existsSync(memoryDir)) { + fs.rmSync(memoryDir, { recursive: true, force: true }); + } + } catch (_error) { + // Ignore cleanup errors + } + }); + + function makeHandlersWithMemory(overrides = {}) { + const memConf = { + id: "default", + dir: memoryDir, + max_file_size: 1024, // 1 KB + max_patch_size: 2048, // 2 KB + max_file_count: 5, + ...overrides, + }; + return createHandlers(mockServer, mockAppendSafeOutput, { + push_repo_memory: { memories: [memConf] }, + }); + } + + it("should return success when no repo-memory is configured", () => { + const h = createHandlers(mockServer, mockAppendSafeOutput, {}); + const result = h.pushRepoMemoryHandler({}); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("success"); + expect(data.message).toContain("No repo-memory configured"); + }); + + it("should return error for unknown memory_id", () => { + const h = makeHandlersWithMemory(); + fs.mkdirSync(memoryDir, { recursive: true }); + const result = h.pushRepoMemoryHandler({ memory_id: "nonexistent" }); + expect(result.isError).toBe(true); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("error"); + expect(data.error).toContain("'nonexistent' not found"); + expect(data.error).toContain("default"); + }); + + it("should return success when memory directory does not exist yet", () => { + const h = makeHandlersWithMemory(); + // memoryDir not created + const result = h.pushRepoMemoryHandler({ memory_id: "default" }); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("success"); + expect(data.message).toContain("does not exist yet"); + }); + + it("should return success for valid files within limits", () => { + const h = makeHandlersWithMemory(); + fs.mkdirSync(memoryDir, { recursive: true }); + fs.writeFileSync(path.join(memoryDir, "state.json"), "x".repeat(100)); + const result = h.pushRepoMemoryHandler({ memory_id: "default" }); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("success"); + expect(data.message).toContain("validation passed"); + }); + + it("should return error when a file exceeds max_file_size", () => { + const h = makeHandlersWithMemory({ max_file_size: 100 }); + fs.mkdirSync(memoryDir, { recursive: true }); + fs.writeFileSync(path.join(memoryDir, "big.json"), "x".repeat(200)); + const result = h.pushRepoMemoryHandler({ memory_id: "default" }); + expect(result.isError).toBe(true); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("error"); + expect(data.error).toContain("big.json"); + expect(data.error).toContain("200 bytes"); + }); + + it("should return error when file count exceeds max_file_count", () => { + const h = makeHandlersWithMemory({ max_file_count: 2 }); + fs.mkdirSync(memoryDir, { recursive: true }); + for (let i = 0; i < 3; i++) { + fs.writeFileSync(path.join(memoryDir, `file${i}.json`), "x".repeat(10)); + } + const result = h.pushRepoMemoryHandler({ memory_id: "default" }); + expect(result.isError).toBe(true); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("error"); + expect(data.error).toContain("Too many files"); + expect(data.error).toContain("3 files"); + }); + + it("should return error when total size exceeds effective max_patch_size", () => { + // max_patch_size = 500 bytes, effective limit = floor(500 * 1.2) = 600 bytes + const h = makeHandlersWithMemory({ max_patch_size: 500, max_file_size: 1024 * 1024 }); + fs.mkdirSync(memoryDir, { recursive: true }); + // Write two files totaling 650 bytes (above the 600 byte effective limit) + fs.writeFileSync(path.join(memoryDir, "a.json"), "x".repeat(350)); + fs.writeFileSync(path.join(memoryDir, "b.json"), "x".repeat(300)); + const result = h.pushRepoMemoryHandler({ memory_id: "default" }); + expect(result.isError).toBe(true); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("error"); + expect(data.error).toContain("exceeds the allowed limit"); + expect(data.error).toContain("push_repo_memory again"); + }); + + it("should use 'default' memory_id when memory_id is not specified", () => { + const h = makeHandlersWithMemory(); + fs.mkdirSync(memoryDir, { recursive: true }); + fs.writeFileSync(path.join(memoryDir, "notes.md"), "hello"); + const result = h.pushRepoMemoryHandler({}); // no memory_id + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("success"); + }); + + it("should scan files recursively in subdirectories", () => { + // max_patch_size = 500 bytes, effective limit = 600 bytes + const h = makeHandlersWithMemory({ max_patch_size: 500, max_file_size: 1024 * 1024 }); + const subDir = path.join(memoryDir, "history"); + fs.mkdirSync(subDir, { recursive: true }); + // Write a nested file that pushes total above effective limit + fs.writeFileSync(path.join(subDir, "log.jsonl"), "x".repeat(700)); + const result = h.pushRepoMemoryHandler({ memory_id: "default" }); + expect(result.isError).toBe(true); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("error"); + // The nested file path should appear correctly + expect(data.error).toContain("exceeds the allowed limit"); + }); + }); }); From 13419ccefae6588c5f072034480a617066542074 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:19:39 +0000 Subject: [PATCH 09/11] test: Add Go unit tests for push_repo_memory config and tool generation - TestGenerateSafeOutputsConfigRepoMemory: verifies memories array is populated - TestGenerateSafeOutputsConfigNoRepoMemory: verifies absent when config is nil - TestGenerateSafeOutputsConfigEmptyRepoMemory: verifies absent with empty memories - TestGenerateFilteredToolsJSONIncludesPushRepoMemoryWithRepoMemoryConfig - TestGenerateFilteredToolsJSONExcludesPushRepoMemoryWithoutRepoMemoryConfig - TestGenerateFilteredToolsJSONExcludesPushRepoMemoryWithEmptyMemories Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- .../safe_outputs_config_generation_test.go | 93 +++++++++++++++++++ .../safe_outputs_tools_generation_test.go | 73 +++++++++++++++ 2 files changed, 166 insertions(+) diff --git a/pkg/workflow/safe_outputs_config_generation_test.go b/pkg/workflow/safe_outputs_config_generation_test.go index 4fa2f50820b..c5777dbe9fb 100644 --- a/pkg/workflow/safe_outputs_config_generation_test.go +++ b/pkg/workflow/safe_outputs_config_generation_test.go @@ -453,3 +453,96 @@ func TestGenerateSafeOutputsConfigCreatePullRequestBackwardCompat(t *testing.T) _, hasAllowedRepos := prConfig["allowed_repos"] assert.False(t, hasAllowedRepos, "allowed_repos should not be present when not configured") } + +// TestGenerateSafeOutputsConfigRepoMemory tests that generateSafeOutputsConfig includes +// push_repo_memory configuration with the expected memories entries when RepoMemoryConfig is present. +func TestGenerateSafeOutputsConfigRepoMemory(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{}, + RepoMemoryConfig: &RepoMemoryConfig{ + Memories: []RepoMemoryEntry{ + { + ID: "default", + MaxFileSize: 5120, + MaxPatchSize: 20480, + MaxFileCount: 50, + }, + { + ID: "notes", + MaxFileSize: 2048, + MaxPatchSize: 8192, + MaxFileCount: 20, + }, + }, + }, + } + + result := generateSafeOutputsConfig(data) + require.NotEmpty(t, result, "Expected non-empty config") + + var parsed map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &parsed), "Result must be valid JSON") + + pushRepoMemory, ok := parsed["push_repo_memory"].(map[string]any) + require.True(t, ok, "Expected push_repo_memory key in config") + + memories, ok := pushRepoMemory["memories"].([]any) + require.True(t, ok, "Expected memories to be an array") + require.Len(t, memories, 2, "Expected 2 memory entries") + + // Check first memory entry + mem0, ok := memories[0].(map[string]any) + require.True(t, ok, "First memory entry should be a map") + assert.Equal(t, "default", mem0["id"], "First memory id should match") + assert.Equal(t, "/tmp/gh-aw/repo-memory/default", mem0["dir"], "First memory dir should be correct") + assert.InDelta(t, float64(5120), mem0["max_file_size"], 0.0001, "First memory max_file_size should match") + assert.InDelta(t, float64(20480), mem0["max_patch_size"], 0.0001, "First memory max_patch_size should match") + assert.InDelta(t, float64(50), mem0["max_file_count"], 0.0001, "First memory max_file_count should match") + + // Check second memory entry + mem1, ok := memories[1].(map[string]any) + require.True(t, ok, "Second memory entry should be a map") + assert.Equal(t, "notes", mem1["id"], "Second memory id should match") + assert.Equal(t, "/tmp/gh-aw/repo-memory/notes", mem1["dir"], "Second memory dir should be correct") + assert.InDelta(t, float64(2048), mem1["max_file_size"], 0.0001, "Second memory max_file_size should match") +} + +// TestGenerateSafeOutputsConfigNoRepoMemory tests that push_repo_memory is absent +// from the config when RepoMemoryConfig is not present. +func TestGenerateSafeOutputsConfigNoRepoMemory(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{}, + }, + RepoMemoryConfig: nil, + } + + result := generateSafeOutputsConfig(data) + require.NotEmpty(t, result, "Expected non-empty config") + + var parsed map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &parsed), "Result must be valid JSON") + + _, hasPushRepoMemory := parsed["push_repo_memory"] + assert.False(t, hasPushRepoMemory, "push_repo_memory should not be present when RepoMemoryConfig is nil") +} + +// TestGenerateSafeOutputsConfigEmptyRepoMemory tests that push_repo_memory is absent +// from the config when RepoMemoryConfig has no memories. +func TestGenerateSafeOutputsConfigEmptyRepoMemory(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{}, + RepoMemoryConfig: &RepoMemoryConfig{ + Memories: []RepoMemoryEntry{}, + }, + } + + result := generateSafeOutputsConfig(data) + require.NotEmpty(t, result, "Expected non-empty config") + + var parsed map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &parsed), "Result must be valid JSON") + + _, hasPushRepoMemory := parsed["push_repo_memory"] + assert.False(t, hasPushRepoMemory, "push_repo_memory should not be present when Memories slice is empty") +} diff --git a/pkg/workflow/safe_outputs_tools_generation_test.go b/pkg/workflow/safe_outputs_tools_generation_test.go index f31f71b381b..b6defc598f3 100644 --- a/pkg/workflow/safe_outputs_tools_generation_test.go +++ b/pkg/workflow/safe_outputs_tools_generation_test.go @@ -475,3 +475,76 @@ func TestGenerateFilteredToolsJSONSortsCustomJobs(t *testing.T) { assert.Equal(t, "m_job", tools[1]["name"], "Second tool should be m_job") assert.Equal(t, "z_job", tools[2]["name"], "Third tool should be z_job") } + +// TestGenerateFilteredToolsJSONIncludesPushRepoMemoryWithRepoMemoryConfig tests that +// push_repo_memory is included in the filtered tools when RepoMemoryConfig is present. +func TestGenerateFilteredToolsJSONIncludesPushRepoMemoryWithRepoMemoryConfig(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{}, + RepoMemoryConfig: &RepoMemoryConfig{ + Memories: []RepoMemoryEntry{ + {ID: "default", MaxFileSize: 10240, MaxPatchSize: 10240, MaxFileCount: 100}, + }, + }, + } + + result, err := generateFilteredToolsJSON(data, ".github/workflows/test.md") + require.NoError(t, err, "generateFilteredToolsJSON should not error") + + var tools []map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &tools), "Result should be valid JSON") + + toolNames := make(map[string]bool) + for _, tool := range tools { + if name, ok := tool["name"].(string); ok { + toolNames[name] = true + } + } + assert.True(t, toolNames["push_repo_memory"], "push_repo_memory should be present when RepoMemoryConfig is set") +} + +// TestGenerateFilteredToolsJSONExcludesPushRepoMemoryWithoutRepoMemoryConfig tests that +// push_repo_memory is NOT included in the filtered tools when RepoMemoryConfig is absent. +func TestGenerateFilteredToolsJSONExcludesPushRepoMemoryWithoutRepoMemoryConfig(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{}, + }, + RepoMemoryConfig: nil, + } + + result, err := generateFilteredToolsJSON(data, ".github/workflows/test.md") + require.NoError(t, err, "generateFilteredToolsJSON should not error") + + var tools []map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &tools), "Result should be valid JSON") + + for _, tool := range tools { + name, _ := tool["name"].(string) + assert.NotEqual(t, "push_repo_memory", name, "push_repo_memory should NOT be present when RepoMemoryConfig is nil") + } +} + +// TestGenerateFilteredToolsJSONExcludesPushRepoMemoryWithEmptyMemories tests that +// push_repo_memory is NOT included when RepoMemoryConfig has an empty Memories slice. +func TestGenerateFilteredToolsJSONExcludesPushRepoMemoryWithEmptyMemories(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{ + MissingData: &MissingDataConfig{}, + }, + RepoMemoryConfig: &RepoMemoryConfig{ + Memories: []RepoMemoryEntry{}, + }, + } + + result, err := generateFilteredToolsJSON(data, ".github/workflows/test.md") + require.NoError(t, err, "generateFilteredToolsJSON should not error") + + var tools []map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &tools), "Result should be valid JSON") + + for _, tool := range tools { + name, _ := tool["name"].(string) + assert.NotEqual(t, "push_repo_memory", name, "push_repo_memory should NOT be present when Memories is empty") + } +} From 720967e16f4c749145c19b2b44808da8a99d87a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 21:27:26 +0000 Subject: [PATCH 10/11] refactor: Use core.debug in pushRepoMemoryHandler since core is always available via shim.cjs Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/safe_outputs_handlers.cjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 31456aa9999..c8588efc571 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -1,4 +1,5 @@ // @ts-check +/// const fs = require("fs"); const path = require("path"); @@ -610,7 +611,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { const totalSizeKb = Math.ceil(totalSize / 1024); const effectiveMaxKb = Math.floor(effectiveMaxPatchSize / 1024); - server.debug(`push_repo_memory validation: ${files.length} files, total ${totalSize} bytes, effective limit ${effectiveMaxPatchSize} bytes`); + core.debug(`push_repo_memory validation: ${files.length} files, total ${totalSize} bytes, effective limit ${effectiveMaxPatchSize} bytes`); if (totalSize > effectiveMaxPatchSize) { return { From c2476bb532b8b0b66cf3ea766481c476e35a3554 Mon Sep 17 00:00:00 2001 From: runner Date: Sat, 7 Mar 2026 22:21:26 +0000 Subject: [PATCH 11/11] Add changeset [skip-ci] --- .changeset/patch-add-push-repo-memory-tool.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-add-push-repo-memory-tool.md diff --git a/.changeset/patch-add-push-repo-memory-tool.md b/.changeset/patch-add-push-repo-memory-tool.md new file mode 100644 index 00000000000..bab93da7730 --- /dev/null +++ b/.changeset/patch-add-push-repo-memory-tool.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Added the new `push_repo_memory` safe output tool so workflows can validate repo memory limits early and wired it into the setup prompts/tests.