Skip to content

Commit 7570484

Browse files
Copilotpelikhangithub-actions[bot]gh-aw-bot
authored
Increase Metrics Collector repo-memory patch cap to prevent push_repo_memory gate failures (#49970)
* Initial plan * Increase Metrics Collector repo-memory patch cap Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Plan patch-size computation fix Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Scope repo-memory patch size to managed diff Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Apply remaining changes Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> Co-authored-by: Peli de Halleux <pelikhan@users.noreply.github.com>
1 parent 6d26b8f commit 7570484

4 files changed

Lines changed: 55 additions & 14 deletions

File tree

.github/workflows/metrics-collector.lock.yml

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/metrics-collector.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ tools:
2727
repo-memory:
2828
branch-name: memory/meta-orchestrators
2929
file-glob: "metrics/**"
30+
max-patch-size: 131072 # 128KB - handles large daily metrics snapshots without patch-size gate failures
3031
timeout-minutes: 15
3132
safe-outputs:
3233
noop:

actions/setup/js/push_repo_memory.cjs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -498,10 +498,20 @@ async function main() {
498498
}
499499
}
500500

501-
// Check if we have any changes to commit
501+
// Build literal pathspecs from the relative paths of files to copy.
502+
// The :(literal) magic prefix tells Git to treat each entry as a plain string,
503+
// preventing glob expansion or pathspec-magic interpretation (e.g. :(top),
504+
// wildcards) even when a filename happens to contain those characters.
505+
const literalPathspecs = Array.from(new Set(filesToCopy.map(file => `:(literal)${file.relativePath}`))).sort();
506+
507+
// Check if we have any changes to commit, scoped to managed memory files only.
502508
let changedFileCount = 0;
503509
try {
504-
const status = execGitSync(["status", "--porcelain"]);
510+
const statusArgs = ["status", "--porcelain"];
511+
if (literalPathspecs.length > 0) {
512+
statusArgs.push("--", ...literalPathspecs);
513+
}
514+
const status = execGitSync(statusArgs, { cwd: workspaceDir });
505515
const changedEntries = status
506516
.split("\n")
507517
.map(line => line.trim())
@@ -534,7 +544,13 @@ async function main() {
534544
// sparse-checkout, causing a plain "git add ." to silently skip or reject
535545
// files on the first run for a new memory branch.
536546
try {
537-
execGitSync(["add", "--sparse", "."], { stdio: "inherit" });
547+
const addArgs = ["add", "--sparse"];
548+
if (literalPathspecs.length > 0) {
549+
addArgs.push("--", ...literalPathspecs);
550+
} else {
551+
addArgs.push(".");
552+
}
553+
execGitSync(addArgs, { stdio: "inherit", cwd: workspaceDir });
538554
} catch (error) {
539555
core.setFailed(`Failed to stage changes: ${getErrorMessage(error)}`);
540556
return;
@@ -546,7 +562,7 @@ async function main() {
546562
// (e.g. a regenerated JSON object) from being counted as "entire source code size"
547563
// even though only a small portion of the data actually changed.
548564
try {
549-
const patchSizeBytes = getStagedPatchDiffSizeBytes({ execGitSyncFn: execGitSync });
565+
const patchSizeBytes = getStagedPatchDiffSizeBytes({ execGitSyncFn: execGitSync, cwd: workspaceDir });
550566
const patchSizeKb = Math.ceil(patchSizeBytes / 1024);
551567
const maxPatchSizeKb = Math.floor(maxPatchSize / 1024);
552568
// Allow 20% overhead to account for git diff format (headers, context lines, etc.)
@@ -559,7 +575,7 @@ async function main() {
559575
// Add per-file diff stats to diagnose what's causing the large patch
560576
// (e.g. a full rewrite of an accumulated history file shows old + new content in the diff)
561577
try {
562-
const diffStat = execGitSync(["diff", "--cached", "--stat"], { stdio: "pipe" });
578+
const diffStat = execGitSync(["diff", "--cached", "--stat"], { stdio: "pipe", cwd: workspaceDir });
563579
core.warning(`Patch content breakdown (git diff --stat):\n${diffStat}`);
564580
} catch (statError) {
565581
core.warning(`Could not retrieve diff stat: ${getErrorMessage(statError)}`);

actions/setup/js/push_repo_memory.test.cjs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,13 +1073,36 @@ describe("push_repo_memory.cjs - shell injection security tests", () => {
10731073
const scriptPath = path.join(import.meta.dirname, "push_repo_memory.cjs");
10741074
const scriptContent = fs.readFileSync(scriptPath, "utf8");
10751075

1076-
// Must use "git add --sparse ." to stage files regardless of sparse-checkout state.
1077-
expect(scriptContent).toContain('"add", "--sparse", "."');
1076+
// Must use git add --sparse with literal pathspec staging so only managed memory paths are included.
1077+
expect(scriptContent).toContain('"add", "--sparse"');
1078+
expect(scriptContent).toContain('addArgs.push("--", ...literalPathspecs)');
10781079

10791080
// Must NOT use plain "git add ." which breaks under sparse-checkout.
10801081
expect(scriptContent).not.toContain('"add", "."');
10811082
});
10821083

1084+
it("should encode pathspecs as :(literal) to prevent Git glob/magic interpretation (source check)", () => {
1085+
// Regression test for: filenames with glob metacharacters or pathspec-magic prefixes
1086+
// (e.g. ":(top)foo", "metrics/*.json") being interpreted as Git pathspecs rather
1087+
// than literal paths, which could cause git status/add to match files outside the
1088+
// managed memory scope.
1089+
//
1090+
// Fix: all artifact-derived paths are wrapped with the :(literal) magic prefix so
1091+
// Git treats them as plain strings regardless of their content.
1092+
1093+
const fs = require("fs");
1094+
const path = require("path");
1095+
1096+
const scriptPath = path.join(import.meta.dirname, "push_repo_memory.cjs");
1097+
const scriptContent = fs.readFileSync(scriptPath, "utf8");
1098+
1099+
// Must build literalPathspecs using the :(literal) magic prefix.
1100+
expect(scriptContent).toContain("`:(literal)${");
1101+
// Must pass literalPathspecs (not raw paths) to git status and git add.
1102+
expect(scriptContent).toContain("literalPathspecs");
1103+
expect(scriptContent).not.toContain("changedPathspecs");
1104+
});
1105+
10831106
it("should safely handle malicious branch names", () => {
10841107
// Test that malicious branch names would be rejected by git, not executed as shell commands
10851108
const maliciousBranchNames = [
@@ -1562,7 +1585,8 @@ describe("push_repo_memory.cjs - changed-file limit checks", () => {
15621585
const scriptContent = nodeFs.readFileSync(scriptPath, "utf8");
15631586

15641587
expect(scriptContent).toContain("changedFileCount");
1565-
expect(scriptContent).toContain('execGitSync(["status", "--porcelain"])');
1588+
expect(scriptContent).toContain('["status", "--porcelain"]');
1589+
expect(scriptContent).toContain('statusArgs.push("--", ...literalPathspecs)');
15661590
expect(scriptContent).toContain("Too many changed files");
15671591
expect(scriptContent).not.toContain("if (filesToCopy.length > maxFileCount)");
15681592
expect(scriptContent).toContain("if (changedFileCount > maxFileCount)");

0 commit comments

Comments
 (0)