From 1bae061a1b2583fc6559f6f45ee18d1623bfee62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 01:27:50 +0000 Subject: [PATCH 1/4] Initial plan From a75cf53dce0fdd6225a31e870a7d32eda58db3a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 01:46:04 +0000 Subject: [PATCH 2/4] Initial plan Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-security-observability.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/daily-security-observability.lock.yml b/.github/workflows/daily-security-observability.lock.yml index 0710e6aded3..1e2331e065b 100644 --- a/.github/workflows/daily-security-observability.lock.yml +++ b/.github/workflows/daily-security-observability.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a869be8d8328a194956925e2c7e3c98a784c555ad426caa6dd28a7106eeb3d77","body_hash":"00f1554cf88325e0c2ca074274809f77e0d9c3da9e314279de36ed80113ab312","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.63","copilot-sdk":"1.0.1"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"0951baf3154975d8a440cade065101df032fb2f800128f93719d2c0e5667d319","body_hash":"c3e3b328c5d10f596d0197ff6a67df16e271742259a40f2d7df0adad169a7336","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.63","copilot-sdk":"1.0.1"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"f9f3042f7e2789586610d6e8b85c8f03e5195baf","version":"v7.2.0"},{"repo":"docker/setup-buildx-action","sha":"d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5","version":"v4.1.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.6","digest":"sha256:5b778c712a25397a38a47cee3467a9cbc726b16320cc133a0758c0592a6f0792","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.6@sha256:5b778c712a25397a38a47cee3467a9cbc726b16320cc133a0758c0592a6f0792"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.6","digest":"sha256:7b14e481f3a9898f1e9be50acc4e58541d9fcd85b49b1e4945b708f1bf1bf68e","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.6@sha256:7b14e481f3a9898f1e9be50acc4e58541d9fcd85b49b1e4945b708f1bf1bf68e"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.6","digest":"sha256:194b21f5d3284b0b2abf2603a14ec607f89d798165a7ef453667706c69401735","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.6@sha256:194b21f5d3284b0b2abf2603a14ec607f89d798165a7ef453667706c69401735"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.6","digest":"sha256:730985e67931b9774545bce76b3ac5a354aa1dc11f19ee8f2d9cbf3211d73c3a","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.6@sha256:730985e67931b9774545bce76b3ac5a354aa1dc11f19ee8f2d9cbf3211d73c3a"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.27","digest":"sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.3.0","digest":"sha256:5c83359327a0bacc3d34db730bea6557d39d341cee0bf6c58c9a896e33150e80","pinned_image":"ghcr.io/github/github-mcp-server:v1.3.0@sha256:5c83359327a0bacc3d34db730bea6557d39d341cee0bf6c58c9a896e33150e80"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # From 722e6a6057bd88d8b389ecfefbc49f73ef9edbc3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 03:10:45 +0000 Subject: [PATCH 3/4] fix(push_repo_memory): seed new memory branches via GitHub API to satisfy signed-commit rules Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/push_repo_memory.cjs | 83 +++++++++++++++++----- actions/setup/js/push_repo_memory.test.cjs | 66 +++++++++++++++++ 2 files changed, 132 insertions(+), 17 deletions(-) diff --git a/actions/setup/js/push_repo_memory.cjs b/actions/setup/js/push_repo_memory.cjs index 46b2d3ec758..9a5d4d65fc8 100644 --- a/actions/setup/js/push_repo_memory.cjs +++ b/actions/setup/js/push_repo_memory.cjs @@ -151,6 +151,10 @@ async function main() { const workspaceDir = process.env.GITHUB_WORKSPACE || process.cwd(); core.info(`Working in repository: ${workspaceDir}`); + // Split targetRepo into owner and repo name here so they are available for + // the GitHub REST API seeding calls below (before the checkout block). + const [targetOwner, targetRepoName] = targetRepo.split("/"); + // Checkout or create the memory branch // Note: we do NOT disable sparse checkout here. Disabling sparse checkout on a // large repository forces git to materialize all tracked files into the working @@ -189,24 +193,70 @@ async function main() { throw fetchError; } - // Branch doesn't exist, create orphan branch - // baseRef stays "" — pushSignedCommits will create the branch via - // rest.git.createRef before the first GraphQL mutation. - core.info(`Branch ${branchName} does not exist, creating orphan branch...`); - execGitSync(["checkout", "--orphan", branchName], { stdio: "inherit" }); - // Reset the index to an empty tree. This is O(1) regardless of how many - // files the source branch contained, avoiding the ENOBUFS error that - // "git rm -rf ." (with stdio:pipe) causes on large repos (10K+ files). - execGitSync(["read-tree", "--empty"], { stdio: "pipe" }); - // Clean the working directory using Node.js so we never pipe large git - // output back through spawnSync buffers. - core.info("Cleaning working directory for orphan branch..."); - for (const entry of fs.readdirSync(workspaceDir)) { - if (entry !== ".git") { - fs.rmSync(path.join(workspaceDir, entry), { recursive: true, force: true }); + // Branch doesn't exist – attempt to seed it via the GitHub REST API so + // the seed commit is server-signed, satisfying "Require signed commits" + // branch protection rules. Commits created via the REST API with + // GITHUB_TOKEN are automatically signed by GitHub. + const EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; + try { + core.info(`Branch ${branchName} does not exist, seeding via GitHub REST API...`); + const { data: seedCommit } = await github.rest.git.createCommit({ + owner: targetOwner, + repo: targetRepoName, + message: `Initialize ${branchName}`, + tree: EMPTY_TREE_SHA, + parents: [], + }); + let useSeedSha = true; + try { + await github.rest.git.createRef({ + owner: targetOwner, + repo: targetRepoName, + ref: `refs/heads/${branchName}`, + sha: seedCommit.sha, + }); + } catch (createRefError) { + // 422 "Reference already exists": another concurrent run created the + // branch between our fetch-check and this createRef call. Treat as + // success and use the existing branch instead. + const createRefErrMsg = createRefError instanceof Error ? createRefError.message : String(createRefError); + if (!/422|Reference already exists/i.test(createRefErrMsg)) { + throw createRefError; + } + core.info(`Branch ${branchName} was created concurrently (422 Reference already exists); using existing branch.`); + useSeedSha = false; } + // Fetch the newly seeded (or concurrently created) branch and check it out. + execGitSync(["fetch", repoUrl, `${branchName}:${branchName}`], { stdio: "pipe", suppressLogs: true }); + execGitSync(["checkout", branchName], { stdio: "inherit" }); + // Set baseRef to the seed commit SHA (or the existing branch HEAD for + // the 422 concurrent-creation case) so pushSignedCommits can use the + // GraphQL signed-commit path instead of the unsigned git push fallback. + baseRef = useSeedSha ? seedCommit.sha : execGitSync(["rev-parse", "HEAD"]).trim(); + core.info(`Seeded and checked out new branch ${branchName} via GitHub API (baseRef: ${baseRef})`); + } catch (seedError) { + // Fallback: API seeding failed (e.g. insufficient token permissions). + // Fall back to the original orphan-branch + git push path and emit a + // warning so the operator knows signed commits may not be produced. + core.warning(`Failed to seed branch ${branchName} via GitHub API, falling back to orphan branch: ${getErrorMessage(seedError)}`); + // baseRef stays "" — pushSignedCommits will use git push for this + // orphan-branch first push (unsigned, may be rejected by strict rulesets). + core.info(`Branch ${branchName} does not exist, creating orphan branch...`); + execGitSync(["checkout", "--orphan", branchName], { stdio: "inherit" }); + // Reset the index to an empty tree. This is O(1) regardless of how many + // files the source branch contained, avoiding the ENOBUFS error that + // "git rm -rf ." (with stdio:pipe) causes on large repos (10K+ files). + execGitSync(["read-tree", "--empty"], { stdio: "pipe" }); + // Clean the working directory using Node.js so we never pipe large git + // output back through spawnSync buffers. + core.info("Cleaning working directory for orphan branch..."); + for (const entry of fs.readdirSync(workspaceDir)) { + if (entry !== ".git") { + fs.rmSync(path.join(workspaceDir, entry), { recursive: true, force: true }); + } + } + core.info(`Created orphan branch: ${branchName}`); } - core.info(`Created orphan branch: ${branchName}`); } } catch (error) { core.setFailed(`Failed to checkout branch: ${getErrorMessage(error)}`); @@ -517,7 +567,6 @@ async function main() { // strict signed-commits ruleset that fallback will also be rejected — // that is expected behaviour: remove the unsupported file types and // re-run. - const [targetOwner, targetRepoName] = targetRepo.split("/"); // URL with embedded token used for the pull-on-retry merge step only; // pushSignedCommits authenticates via the git extraheader set by // actions/checkout (and the gitAuthEnv fallback for the git-push path). diff --git a/actions/setup/js/push_repo_memory.test.cjs b/actions/setup/js/push_repo_memory.test.cjs index da845acffc2..12610f386ef 100644 --- a/actions/setup/js/push_repo_memory.test.cjs +++ b/actions/setup/js/push_repo_memory.test.cjs @@ -1575,3 +1575,69 @@ describe("push_repo_memory.cjs - signed commit push (pushSignedCommits delegatio }); }); }); + +// ────────────────────────────────────────────────────────────────────────────── +// API branch seeding tests +// Verifies that push_repo_memory seeds new memory branches via the GitHub REST +// API (server-signed commits) before falling back to the orphan-branch path. +// ────────────────────────────────────────────────────────────────────────────── + +describe("push_repo_memory.cjs - API branch seeding for signed commits", () => { + it("should use EMPTY_TREE_SHA and parents:[] to create a root seed commit (source check)", () => { + const nodeFs = require("fs"); + const nodePath = require("path"); + const scriptPath = nodePath.join(import.meta.dirname, "push_repo_memory.cjs"); + const scriptContent = nodeFs.readFileSync(scriptPath, "utf8"); + + // Must define the well-known empty-tree SHA as the seed tree + expect(scriptContent).toContain("4b825dc642cb6eb9a060e54bf8d69288fbee4904"); + // Must call createCommit with an empty parents array (root/orphan commit) + expect(scriptContent).toContain("github.rest.git.createCommit"); + expect(scriptContent).toContain("parents: []"); + // Must create the branch ref pointing at the seed commit + expect(scriptContent).toContain("github.rest.git.createRef"); + // Must set baseRef to the seed commit SHA so pushSignedCommits can use the + // GraphQL signed-commit path instead of the unsigned git push fallback + expect(scriptContent).toContain("seedCommit.sha"); + }); + + it("should fall back to orphan branch with core.warning when API seeding fails (source check)", () => { + const nodeFs = require("fs"); + const nodePath = require("path"); + const scriptPath = nodePath.join(import.meta.dirname, "push_repo_memory.cjs"); + const scriptContent = nodeFs.readFileSync(scriptPath, "utf8"); + + // Must emit a warning identifying the API seeding failure and fallback + expect(scriptContent).toContain("falling back to orphan branch"); + // The fallback must still create an orphan branch (original path preserved) + expect(scriptContent).toContain('"--orphan"'); + expect(scriptContent).toContain('"read-tree", "--empty"'); + }); + + it("should treat 422 Reference-already-exists as success during concurrent branch creation (source check)", () => { + const nodeFs = require("fs"); + const nodePath = require("path"); + const scriptPath = nodePath.join(import.meta.dirname, "push_repo_memory.cjs"); + const scriptContent = nodeFs.readFileSync(scriptPath, "utf8"); + + // Must detect the 422 status code or the GitHub "Reference already exists" message + expect(scriptContent).toContain("422|Reference already exists"); + // Must not rethrow a 422 error – branch is used as-is after concurrent creation + expect(scriptContent).toContain("Reference already exists"); + }); + + it("should split targetOwner/targetRepoName before the checkout block (source check)", () => { + const nodeFs = require("fs"); + const nodePath = require("path"); + const scriptPath = nodePath.join(import.meta.dirname, "push_repo_memory.cjs"); + const scriptContent = nodeFs.readFileSync(scriptPath, "utf8"); + + // targetOwner/targetRepoName must be declared before the checkout section + // so they are available for the GitHub REST API seeding calls + const splitIdx = scriptContent.indexOf('targetRepo.split("/")'); + const checkoutIdx = scriptContent.indexOf("Checking out branch:"); + expect(splitIdx).toBeGreaterThan(-1); + expect(checkoutIdx).toBeGreaterThan(-1); + expect(splitIdx).toBeLessThan(checkoutIdx); + }); +}); From fb99f95d0c59f09dda4ff35cdf5cd54869b1a00d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 03:11:51 +0000 Subject: [PATCH 4/4] refactor(push_repo_memory): rename useSeedSha to useApiSeedSha for clarity, improve 422 comment Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/push_repo_memory.cjs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/actions/setup/js/push_repo_memory.cjs b/actions/setup/js/push_repo_memory.cjs index 9a5d4d65fc8..ae530cf3258 100644 --- a/actions/setup/js/push_repo_memory.cjs +++ b/actions/setup/js/push_repo_memory.cjs @@ -207,7 +207,7 @@ async function main() { tree: EMPTY_TREE_SHA, parents: [], }); - let useSeedSha = true; + let useApiSeedSha = true; try { await github.rest.git.createRef({ owner: targetOwner, @@ -216,15 +216,17 @@ async function main() { sha: seedCommit.sha, }); } catch (createRefError) { - // 422 "Reference already exists": another concurrent run created the - // branch between our fetch-check and this createRef call. Treat as - // success and use the existing branch instead. + // GitHub returns HTTP 422 with "Reference already exists" when the + // branch was created concurrently between our fetch-check and this + // createRef call. Check for either the status code or the message + // text since different Octokit versions surface errors differently. + // Treat as success and use the existing branch instead. const createRefErrMsg = createRefError instanceof Error ? createRefError.message : String(createRefError); if (!/422|Reference already exists/i.test(createRefErrMsg)) { throw createRefError; } core.info(`Branch ${branchName} was created concurrently (422 Reference already exists); using existing branch.`); - useSeedSha = false; + useApiSeedSha = false; } // Fetch the newly seeded (or concurrently created) branch and check it out. execGitSync(["fetch", repoUrl, `${branchName}:${branchName}`], { stdio: "pipe", suppressLogs: true }); @@ -232,7 +234,7 @@ async function main() { // Set baseRef to the seed commit SHA (or the existing branch HEAD for // the 422 concurrent-creation case) so pushSignedCommits can use the // GraphQL signed-commit path instead of the unsigned git push fallback. - baseRef = useSeedSha ? seedCommit.sha : execGitSync(["rev-parse", "HEAD"]).trim(); + baseRef = useApiSeedSha ? seedCommit.sha : execGitSync(["rev-parse", "HEAD"]).trim(); core.info(`Seeded and checked out new branch ${branchName} via GitHub API (baseRef: ${baseRef})`); } catch (seedError) { // Fallback: API seeding failed (e.g. insufficient token permissions).