From b0abd7c043fd267aa6e55050ac12faef6d45024c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Jul 2026 03:31:03 +0000
Subject: [PATCH 1/4] Initial plan
From 32d462e832588353830b03f0b8b45cd2fc4397e7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Jul 2026 03:43:57 +0000
Subject: [PATCH 2/4] Plan repo-memory precheck diff-size fix
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.github/skills/agentic-workflows/SKILL.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md
index 615a51e551e..3fc711d4035 100644
--- a/.github/skills/agentic-workflows/SKILL.md
+++ b/.github/skills/agentic-workflows/SKILL.md
@@ -49,6 +49,7 @@ Load these files from `github/gh-aw` (they are not available locally).
- `.github/aw/optimize-agentic-workflow.md`
- `.github/aw/patterns.md`
- `.github/aw/pr-reviewer.md`
+- `.github/aw/release-workflow.md`
- `.github/aw/report.md`
- `.github/aw/reuse.md`
- `.github/aw/safe-outputs-automation.md`
From 0c2184541017a4137b79202e903333a6f4fe5087 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Jul 2026 03:49:59 +0000
Subject: [PATCH 3/4] Use staged diff size for repo-memory max-patch-size
pre-check
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
actions/setup/js/push_repo_memory.cjs | 9 +---
actions/setup/js/repo_memory_patch_size.cjs | 34 +++++++++++++++
actions/setup/js/safe_outputs_handlers.cjs | 42 +++++++++++++------
.../setup/js/safe_outputs_handlers.test.cjs | 35 ++++++++++------
4 files changed, 88 insertions(+), 32 deletions(-)
create mode 100644 actions/setup/js/repo_memory_patch_size.cjs
diff --git a/actions/setup/js/push_repo_memory.cjs b/actions/setup/js/push_repo_memory.cjs
index 44349024b9b..45ca12758fd 100644
--- a/actions/setup/js/push_repo_memory.cjs
+++ b/actions/setup/js/push_repo_memory.cjs
@@ -7,6 +7,7 @@ const path = require("path");
const { getErrorMessage } = require("./error_helpers.cjs");
const { globPatternToRegex } = require("./glob_pattern_helpers.cjs");
const { execGitSync, getGitAuthEnv } = require("./git_helpers.cjs");
+const { getStagedPatchAdditionsSizeBytes } = require("./repo_memory_patch_size.cjs");
const { parseAllowedRepos, validateRepo } = require("./repo_helpers.cjs");
const { pushSignedCommits } = require("./push_signed_commits.cjs");
@@ -544,13 +545,7 @@ async function main() {
// Deletions are ignored since removing content is acceptable and does not
// contribute to the size of the content being pushed.
try {
- const patchContent = execGitSync(["diff", "--cached"], { stdio: "pipe" });
- // Count only added lines (starting with '+', excluding '+++' file-header lines)
- const addedSizeBytes = patchContent
- .split("\n")
- .filter(line => line.startsWith("+") && !line.startsWith("+++"))
- .reduce((sum, line) => sum + Buffer.byteLength(line + "\n", "utf8"), 0);
- const patchSizeBytes = addedSizeBytes;
+ const patchSizeBytes = getStagedPatchAdditionsSizeBytes({ execGitSyncFn: execGitSync });
const patchSizeKb = Math.ceil(patchSizeBytes / 1024);
const maxPatchSizeKb = Math.floor(maxPatchSize / 1024);
// Allow 20% overhead to account for git diff format (headers, context lines, etc.)
diff --git a/actions/setup/js/repo_memory_patch_size.cjs b/actions/setup/js/repo_memory_patch_size.cjs
new file mode 100644
index 00000000000..aa1959efd89
--- /dev/null
+++ b/actions/setup/js/repo_memory_patch_size.cjs
@@ -0,0 +1,34 @@
+// @ts-check
+///
+
+/**
+ * Count total UTF-8 bytes for added lines in a unified diff.
+ * Added lines start with "+" and exclude file header lines ("+++").
+ *
+ * @param {string} patchContent
+ * @returns {number}
+ */
+function getAddedPatchSizeBytesFromDiff(patchContent) {
+ return patchContent
+ .split("\n")
+ .filter(line => line.startsWith("+") && !line.startsWith("+++"))
+ .reduce((sum, line) => sum + Buffer.byteLength(line + "\n", "utf8"), 0);
+}
+
+/**
+ * Compute staged patch additions size from git diff --cached.
+ *
+ * @param {Object} options
+ * @param {(args: string[], opts?: Record) => string} options.execGitSyncFn
+ * @param {string} [options.cwd]
+ * @returns {number}
+ */
+function getStagedPatchAdditionsSizeBytes({ execGitSyncFn, cwd }) {
+ const patchContent = execGitSyncFn(["diff", "--cached"], { stdio: "pipe", cwd });
+ return getAddedPatchSizeBytesFromDiff(patchContent);
+}
+
+module.exports = {
+ getAddedPatchSizeBytesFromDiff,
+ getStagedPatchAdditionsSizeBytes,
+};
diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs
index b9a4fc8b84b..0af94ff315e 100644
--- a/actions/setup/js/safe_outputs_handlers.cjs
+++ b/actions/setup/js/safe_outputs_handlers.cjs
@@ -21,6 +21,7 @@ const { findRepoCheckout } = require("./find_repo_checkout.cjs");
const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs");
const { getOrGenerateTemporaryId } = require("./temporary_id.cjs");
const { parseAllowedExtensionsEnv } = require("./allowed_extensions_helpers.cjs");
+const { getStagedPatchAdditionsSizeBytes } = require("./repo_memory_patch_size.cjs");
const { sanitizeTitle, applyTitlePrefix } = require("./sanitize_title.cjs");
const { parseDeduplicateByTitle, normalizeTitleForDedup, findDuplicateByTitle } = require("./issue_title_dedup.cjs");
const { validateCreatePullRequestIntent, validatePushToPullRequestBranchIntent, validateCreateIssueIntent, validateAddCommentIntent } = require("./intent_probe.cjs");
@@ -1645,17 +1646,35 @@ function createHandlers(server, appendSafeOutput, config = {}) {
};
}
- // Check total content size. totalSize is the raw sum of all file sizes in the memory
- // directory (excluding .git). It is compared against max_patch_size × 1.2 so that
- // a full rewrite of all memory content would remain within the push gate limit in
- // push_repo_memory.cjs, which applies the same 1.2× factor to diff additions.
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
const totalSizeKb = Math.ceil(totalSize / 1024);
const effectiveMaxKb = Math.floor(effectiveMaxPatchSize / 1024);
+ const maxPatchSizeKb = Math.floor(maxPatchSize / 1024);
- core.debug(`push_repo_memory validation: ${files.length} files, total ${totalSize} bytes, effective limit ${effectiveMaxPatchSize} bytes`);
+ let patchSizeBytes;
+ try {
+ ensureSafeDirectoryTrust(memoryDir, server);
+ execGitSync(["add", "--sparse", "."], { cwd: memoryDir, stdio: "pipe" });
+ patchSizeBytes = getStagedPatchAdditionsSizeBytes({ execGitSyncFn: execGitSync, cwd: memoryDir });
+ } catch (/** @type {any} */ error) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ result: "error",
+ error: `Failed to compute staged patch additions size for '${memoryDir}': ${getErrorMessage(error)}`,
+ }),
+ },
+ ],
+ isError: true,
+ };
+ }
+ const patchSizeKb = Math.ceil(patchSizeBytes / 1024);
+
+ core.debug(`push_repo_memory validation: ${files.length} files, total ${totalSize} bytes, patch additions ${patchSizeBytes} bytes, effective limit ${effectiveMaxPatchSize} bytes`);
- if (totalSize > effectiveMaxPatchSize) {
+ if (patchSizeBytes > effectiveMaxPatchSize) {
return {
content: [
{
@@ -1663,11 +1682,10 @@ function createHandlers(server, appendSafeOutput, config = {}) {
text: JSON.stringify({
result: "error",
error:
- `Total memory size (${totalSizeKb} KB) exceeds the allowed limit of ${effectiveMaxKb} KB ` +
- `(configured max-patch-size: ${Math.floor(maxPatchSize / 1024)} KB).\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.`,
+ `Patch additions size (${patchSizeKb} KB, ${patchSizeBytes} bytes) exceeds the allowed limit of ${effectiveMaxKb} KB ` +
+ `(${effectiveMaxPatchSize} bytes, configured max-patch-size: ${maxPatchSizeKb} KB / ${maxPatchSize} bytes with 20% overhead).\n\n` +
+ `Please reduce the size of staged changes in '${memoryDir}' before the workflow completes. ` +
+ `Then call push_repo_memory again to verify the patch size is within limits.`,
}),
},
],
@@ -1681,7 +1699,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 content, ` + `${patchSizeKb} KB patch additions (${patchSizeBytes} bytes) (limit: ${effectiveMaxKb} KB / ${effectiveMaxPatchSize} bytes).`,
}),
},
],
diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs
index d26430e65ae..313a1757b96 100644
--- a/actions/setup/js/safe_outputs_handlers.test.cjs
+++ b/actions/setup/js/safe_outputs_handlers.test.cjs
@@ -2512,6 +2512,12 @@ describe("safe_outputs_handlers", () => {
});
}
+ function initGitRepo(repoDir) {
+ execSync("git init", { cwd: repoDir, stdio: "pipe" });
+ execSync('git config user.email "test@example.com"', { cwd: repoDir, stdio: "pipe" });
+ execSync('git config user.name "Test User"', { cwd: repoDir, stdio: "pipe" });
+ }
+
it("should return success when no repo-memory is configured", () => {
const h = createHandlers(mockServer, mockAppendSafeOutput, {});
const result = h.pushRepoMemoryHandler({});
@@ -2543,6 +2549,7 @@ describe("safe_outputs_handlers", () => {
it("should return success for valid files within limits", () => {
const h = makeHandlersWithMemory();
fs.mkdirSync(memoryDir, { recursive: true });
+ initGitRepo(memoryDir);
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);
@@ -2576,42 +2583,43 @@ describe("safe_outputs_handlers", () => {
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 });
+ it("should pass when total folder size is large but staged diff is tiny", () => {
+ const h = makeHandlersWithMemory({ max_patch_size: 50, 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));
+ initGitRepo(memoryDir);
+ fs.writeFileSync(path.join(memoryDir, "large.json"), `${"x\n".repeat(3000)}`);
+ execSync("git add . && git commit -m 'seed'", { cwd: memoryDir, stdio: "pipe" });
+ fs.appendFileSync(path.join(memoryDir, "large.json"), "small-diff\n");
const result = h.pushRepoMemoryHandler({ memory_id: "default" });
- expect(result.isError).toBe(true);
+ expect(result.isError).toBeUndefined();
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");
+ expect(data.result).toBe("success");
+ expect(data.message).toContain("patch additions");
});
it("should use 'default' memory_id when memory_id is not specified", () => {
const h = makeHandlersWithMemory();
fs.mkdirSync(memoryDir, { recursive: true });
+ initGitRepo(memoryDir);
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", () => {
+ it("should fail when staged patch additions exceed effective max_patch_size", () => {
// max_patch_size = 500 bytes, effective limit = 600 bytes
const h = makeHandlersWithMemory({ max_patch_size: 500, max_file_size: 1024 * 1024 });
+ fs.mkdirSync(memoryDir, { recursive: true });
+ initGitRepo(memoryDir);
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("Patch additions size");
expect(data.error).toContain("exceeds the allowed limit");
});
@@ -2622,6 +2630,7 @@ describe("safe_outputs_handlers", () => {
// files are small but .git directory content is large — must not count toward limit.
const h = makeHandlersWithMemory({ max_patch_size: 500, max_file_size: 1024 * 1024 });
fs.mkdirSync(memoryDir, { recursive: true });
+ initGitRepo(memoryDir);
// Small memory files (well within limit)
fs.writeFileSync(path.join(memoryDir, "memory.json"), "x".repeat(100));
fs.writeFileSync(path.join(memoryDir, "state.json"), "x".repeat(100));
From 28209531474542e1618b1ef42a3accac89cf9942 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Jul 2026 05:55:16 +0000
Subject: [PATCH 4/4] fix: use hunk-state tracking in
getAddedPatchSizeBytesFromDiff and add repo_memory_patch_size.cjs to
SAFE_OUTPUTS_FILES
- Replace filter-based +++ exclusion with a state-machine that tracks
whether parsing is inside a hunk. File headers (+++ before the first
@@ of each file) are correctly ignored; content lines that begin with
++ (appearing as +++ inside a hunk) are now correctly counted.
- Add repo_memory_patch_size.cjs to SAFE_OUTPUTS_FILES in setup.sh so
the transitive-dependency test passes (safe_outputs_handlers.cjs
requires it at runtime).
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
---
actions/setup/js/repo_memory_patch_size.cjs | 19 +++++++++++++++----
actions/setup/setup.sh | 1 +
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/actions/setup/js/repo_memory_patch_size.cjs b/actions/setup/js/repo_memory_patch_size.cjs
index aa1959efd89..b2de1919f80 100644
--- a/actions/setup/js/repo_memory_patch_size.cjs
+++ b/actions/setup/js/repo_memory_patch_size.cjs
@@ -4,15 +4,26 @@
/**
* Count total UTF-8 bytes for added lines in a unified diff.
* Added lines start with "+" and exclude file header lines ("+++").
+ * Uses hunk-state tracking so content lines that begin with "++" are
+ * counted correctly (they appear as "+++" inside a hunk but are not headers).
*
* @param {string} patchContent
* @returns {number}
*/
function getAddedPatchSizeBytesFromDiff(patchContent) {
- return patchContent
- .split("\n")
- .filter(line => line.startsWith("+") && !line.startsWith("+++"))
- .reduce((sum, line) => sum + Buffer.byteLength(line + "\n", "utf8"), 0);
+ let inHunk = false;
+ let total = 0;
+ for (const line of patchContent.split("\n")) {
+ if (line.startsWith("@@")) {
+ inHunk = true;
+ } else if (line.startsWith("diff ")) {
+ inHunk = false;
+ }
+ if (inHunk && line.startsWith("+")) {
+ total += Buffer.byteLength(line + "\n", "utf8");
+ }
+ }
+ return total;
}
/**
diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh
index 92968eb518e..e9addfff9bb 100755
--- a/actions/setup/setup.sh
+++ b/actions/setup/setup.sh
@@ -364,6 +364,7 @@ SAFE_OUTPUTS_FILES=(
"markdown_code_region_balancer.cjs"
"temporary_id.cjs"
"invocation_context_helpers.cjs"
+ "repo_memory_patch_size.cjs"
)
SAFE_OUTPUTS_COUNT=0