Skip to content

fix: prevent ENOBUFS in push_repo_memory on large repos#23069

Merged
pelikhan merged 10 commits into
mainfrom
copilot/fix-push-repo-memory-error
Mar 26, 2026
Merged

fix: prevent ENOBUFS in push_repo_memory on large repos#23069
pelikhan merged 10 commits into
mainfrom
copilot/fix-push-repo-memory-error

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 26, 2026

push_repo_memory fails with spawnSync git ENOBUFS on repos with 10K+ files (e.g. Azure/azure-sdk-for-js). The orphan branch setup called git sparse-checkout disable (materialising the full working tree) then git rm -r -f --ignore-unmatch . with stdio: "pipe", which streams a rm 'path' line per file—overflowing spawnSync's pipe buffer.

Changes

  • Remove git sparse-checkout disable — unnecessary for both orphan branch creation and memory branch checkout; memory branches contain only a handful of files regardless.

  • Replace git rm -r -f --ignore-unmatch . with buffer-safe alternatives:

    • git read-tree --empty — resets the index to an empty tree in O(1) with zero output
    • Node.js fs.rmSync loop — cleans the working directory without any pipe
// Before — ENOBUFS on 10K+ file repos
execGitSync(["sparse-checkout", "disable"], { stdio: "pipe" });
// ...
execGitSync(["checkout", "--orphan", branchName], { stdio: "inherit" });
execGitSync(["rm", "-r", "-f", "--ignore-unmatch", "."], { stdio: "pipe" }); // ← boom

// After — O(1), no pipe output
execGitSync(["checkout", "--orphan", branchName], { stdio: "inherit" });
execGitSync(["read-tree", "--empty"], { stdio: "pipe" });
for (const entry of fs.readdirSync(workspaceDir)) {
  if (entry !== ".git") fs.rmSync(path.join(workspaceDir, entry), { recursive: true, force: true });
}
  • Add branch name validationBRANCH_NAME is validated at runtime against the naming constraints enforced by the Go compiler:

    • Namespaced branches ({prefix}/{id}) are accepted, supporting the default memory/ prefix and any valid custom branch-prefix (e.g. campaigns/metrics)
    • Known wiki branch names (master, main, gh-pages) are accepted as bare names, but only when TARGET_REPO ends with .wiki — the compiler always appends .wiki for wiki memory, so this cross-validation catches any mismatch (e.g. passing master against a non-wiki repo is rejected with ERR_VALIDATION)
    • Any other bare branch name triggers an ERR_VALIDATION error immediately, before any git or repo operations
  • Add fetch error discrimination — the catch (fetchError) block now inspects the git error message and only falls through to orphan-branch creation when git reports a canonical "missing branch" error (couldn't find remote ref / remote branch .* not found). Authentication failures, network errors, and any other unexpected fetch failures are re-thrown so the outer handler calls core.setFailed instead of silently creating an orphan branch.

  • Add regression test asserting sparse-checkout disable, git rm -r -f, and execGitSync(["rm" are absent, and read-tree --empty + fs.rmSync are present.

  • Fix pre-existing test failure: afterEach was used but not imported from vitest.


📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

Copilot AI and others added 2 commits March 26, 2026 07:05
Replace git sparse-checkout disable + git rm -rf . (which pipes one
line per file and overflows the buffer on 10K+ file repos) with:
- git read-tree --empty  (clears index in O(1), zero output)
- Node.js fs.rmSync loop (no pipe, no buffer issues)

Also remove sparse-checkout disable entirely: it forces a full
working-tree expansion and is not needed for orphan branch creation
or memory branch checkout.

Fixes: push_repo_memory fails with ENOBUFS on large repos after
disabling sparse checkout (Azure/azure-sdk-for-js ~10K files)

Also fix pre-existing test failure: import afterEach from vitest

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/04d764a4-b984-43c6-a10f-b599f2dc3c16

Co-authored-by: pelikhan <[email protected]>
Copilot AI changed the title [WIP] Fix push_repo_memory error with ENOBUFS on large repos fix: prevent ENOBUFS in push_repo_memory on large repos Mar 26, 2026
Copilot AI requested a review from pelikhan March 26, 2026 07:07
@pelikhan pelikhan marked this pull request as ready for review March 26, 2026 07:09
Copilot AI review requested due to automatic review settings March 26, 2026 07:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes push_repo_memory failures (spawnSync git ENOBUFS) on very large repositories by avoiding git commands that emit per-file output into spawnSync pipe buffers.

Changes:

  • Removes git sparse-checkout disable from the push_repo_memory flow.
  • Replaces orphan-branch cleanup from git rm -r -f --ignore-unmatch . to git read-tree --empty + filesystem cleanup via fs.rmSync.
  • Adds a regression test and fixes a pre-existing Vitest import issue (afterEach).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
actions/setup/js/push_repo_memory.cjs Avoids ENOBUFS by clearing the index with read-tree --empty and cleaning the working directory without piping large git output.
actions/setup/js/push_repo_memory.test.cjs Imports afterEach and adds a regression test asserting the new buffer-safe approach is used.
.changeset/patch-fix-push-repo-memory-enobufs.md Adds a patch changeset documenting the ENOBUFS fix and rationale.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread actions/setup/js/push_repo_memory.test.cjs Outdated
@github-actions github-actions Bot added the lgtm label Mar 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Hey @Copilot 👋 — great work tracking down and fixing the ENOBUFS crash in push_repo_memory on large repos! The root-cause analysis is thorough, the fix is elegant (swapping to git read-tree --empty + fs.rmSync is exactly the right call), and the regression test clearly guards against future regressions. The changeset entry is also in place.

This PR looks well-aligned with the project's contribution guidelines and ready for maintainer review. 🎉

Generated by Contribution Check ·

@pelikhan
Copy link
Copy Markdown
Collaborator

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 26, 2026

@copilot lint https://github.com/github/gh-aw/actions/runs/23582366667/job/68667746730

Fixed in b32815d — Prettier wanted single quotes for the mixed-quote string "execGitSync([\"rm\"", so it's now 'execGitSync(["rm"'.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot add more runtime checks to ensure that the branch name is compliant to the naming constraints (memory/...) and that the git fetch failure was due to a missing branch and not a temporary network issue or auth problem.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 26, 2026

@copilot add more runtime checks to ensure that the branch name is compliant to the naming constraints (memory/...) and that the git fetch failure was due to a missing branch and not a temporary netwo...

Done in 5eed9f2. Two runtime checks added:

  1. Branch name validation (lines 101–109): branchName must match ^memory\/.+ — an ERR_VALIDATION error is returned immediately if not, before any git or repo operations.

  2. Fetch error discrimination (lines 151–161): the catch (fetchError) block now inspects the error message for git's canonical "missing branch" messages (couldn't find remote ref / remote branch .* not found). Only those fall through to orphan-branch creation; everything else (auth, network, etc.) is re-thrown so the outer handler calls core.setFailed.

Tests: 2 runtime tests for the branch-name constraint, 1 runtime test asserting auth/network fetch failures surface as setFailed("Failed to checkout branch: …"), and 1 source-code assertion test verifying the discrimination logic is present.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot validate code changes with the safe output specifications

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 26, 2026

@copilot validate code changes with the safe output specifications

Validated in 07de838. The previous ^memory\/.+ check was inconsistent with the safe output specifications — the Go compiler (repo_memory.go) generates three valid branch name forms:

  1. {prefix}/{id} — non-wiki memory with the default memory prefix or any custom branch-prefix (4–32 alphanumeric/hyphen/underscore chars, validated by validateBranchPrefix)
  2. master — wiki memory default (Wiki: true sets BranchName = "master" when no explicit branch-name is given)
  3. Explicit branch-name — user-provided value (no compile-time format restriction)

The corrected runtime check now mirrors the compiler's contract:

const isNamespaced = /^[a-zA-Z0-9_-]+\/.+/.test(branchName);   // covers memory/*, campaigns/*, etc.
const isKnownWikiBranch = branchName === "master" || branchName === "main" || branchName === "gh-pages";
if (!isNamespaced && !isKnownWikiBranch) { core.setFailed(...); }

Added four new tests: two runtime tests (invalid bare name, memory/ with no suffix), and two source-code assertion tests (custom-prefix acceptance, wiki branch acceptance).

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot
-add runtime check cross validate that know wiki branches are only enable for the wiki git

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 26, 2026

@copilot
-add runtime check cross validate that know wiki branches are only enable for the wiki git

Done in 9929054. Added isWikiRepo = targetRepo.endsWith(".wiki") and a second guard:

if (isKnownWikiBranch && !isWikiRepo) {
  core.setFailed(
    `ERR_VALIDATION: Branch name "${branchName}" is only valid for wiki repositories (TARGET_REPO must end with ".wiki", got "${targetRepo}")`
  );
  return;
}

The compiler always appends .wiki to TARGET_REPO for wiki memory (memory.Wiki = true), so this cross-validation catches any mismatch at runtime. Two new tests added: a runtime test that verifies master + non-wiki repo → ERR_VALIDATION, and a source-code assertion test checking isWikiRepo + .endsWith(".wiki") + isKnownWikiBranch && !isWikiRepo are all present.

@pelikhan
Copy link
Copy Markdown
Collaborator

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 26, 2026

@copilot fix https://github.com/github/gh-aw/actions/runs/23605394949/job/68746626039?pr=23069

The TestHashConsistencyAcrossLockFiles failure was caused by a stale lock file on main that hadn't been merged into the branch yet. It was already resolved by the main merge commit 6010a31 — both pkg/parser and pkg/workflow tests pass cleanly now.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha -bool -buildtags /home/REDACTED/.cargo/bin/bash -errorsas -ifaceassert -nilfunc bash --no�� --noprofile -tests /opt/hostedtoolcache/node/24.14.0/x64/bin/node (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha se 9406725/b085/vet.cfg 0/x64/bin/node (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha needs.build.outputs.version (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel x_amd64/vet /usr/bin/git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha k/gh-aw/gh-aw/.github/workflows/agentic-observability-kit.md /tmp/go-build1299406725/b105/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -c=4 -nolocalimports -importcfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -ato�� -bool -buildtags /home/REDACTED/.dotnet/tools/bash -errorsas -ifaceassert -nilfunc bash (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha k/gh-aw/gh-aw/.github/workflows/auto-triage-issues.md -buildtags /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -errorsas -ifaceassert -nilfunc /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -ato�� -bool -buildtags /home/REDACTED/work/_temp/uv-python-dir/bash -errorsas -ifaceassert -nilfunc bash (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel x_amd64/vet /usr/bin/git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -unreachable=false /tmp/go-build1299406725/b084/vet.cfg 9406725/b368/vet.cfg (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha k/gh-aw/gh-aw/.github/workflows/artifacts-summary.md -buildtags /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -errorsas -ifaceassert -nilfunc /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -ato�� -bool -buildtags /usr/bin/pager -errorsas -ifaceassert -nilfunc /usr/bin/pager (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha k/gh-aw/gh-aw/.github/workflows/blog-auditor.md -buildtags /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -errorsas -ifaceassert -nilfunc /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -ato�� -bool -buildtags /home/REDACTED/.local/bin/bash -errorsas -ifaceassert -nilfunc bash (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha k/gh-aw/gh-aw/.github/workflows/archie.md -buildtags /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -errorsas -ifaceassert -nilfunc /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -uns�� -unreachable=false /tmp/go-build1299406725/b258/vet.cfg /usr/bin/bash (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha k/gh-aw/gh-aw/.github/workflows/ai-moderator.md /tmp/go-build1299406725/b073/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -c=4 -nolocalimports -importcfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet -uns�� -unreachable=false /tmp/go-build1299406725/b164/vet.cfg /usr/local/bin/bash (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha get .cfg 64/pkg/tool/linux_amd64/vet user.name (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0.1.2
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq .object.sha -unreachable=false /tmp/go-build1299406725/b088/vet.cfg /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha -repo-memory-error^{commit} --local ache/go/1.25.0/x64/pkg/tool/linux_amd64/compile user.email (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha -repo-memory-error^{commit} --local de/node/bin/bash user.email (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha 0 -j ACCEPT (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha INVALID,NEW -j DROP (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan pelikhan merged commit 4c73121 into main Mar 26, 2026
@pelikhan pelikhan deleted the copilot/fix-push-repo-memory-error branch March 26, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

push_repo_memory fails with ENOBUFS on large repos after disabling sparse checkout

3 participants