Skip to content

Remove push_repo_memory concurrency group so fan-out memory writes are not dropped - #62414

Merged
pelikhan merged 5 commits into
mainfrom
copilot/fix-push-repo-memory-issue
Sep 21, 2026
Merged

pelikhan merged 5 commits into
mainfrom
copilot/fix-push-repo-memory-issue

Conversation

Copilot AI commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

GitHub Actions keeps at most one running and one pending job per concurrency group — every additional pending job is cancelled. push_repo_memory set a per-branch group, so when more than two runs sharing a repo-memory branch finished close together, the extra push jobs were cancelled and their memory writes were silently lost while agent reported success. A 50-worker fan-out writing 50 distinct files persisted only 11 transactions; 39 push jobs were cancelled with Canceling since a higher priority waiting request ... exists.

cancel-in-progress: false only protects the running job; the single pending slot is still overwritten by each newcomer. No Actions setting queues more than one pending job, so any shared group is lossy under fan-out and a per-run unique key is equivalent to no group at all.

Changes

  • pkg/workflow/repo_memory.go — buildPushRepoMemoryJob no longer sets Concurrency. buildPushRepoMemoryConcurrencyGroup and encodeConcurrencyKeyPart are deleted (unused), along with the sort import. A comment at the former call site records the GitHub semantics and the convergence strategy.
  • actions/setup/js/push_repo_memory.cjs — the retry loop now has to stand on its own, so MAX_RETRIES goes 3 → 10 with capped full-jitter backoff. The ls-remote → currentBaseRef refresh → configureRepoMemoryMergePolicy → pull --no-rebase -X ours re-sync is unchanged.
  • Tests — TestPushRepoMemoryJobHasNoConcurrencyGroup replaces the two key-format tests; a vitest guard parses MAX_RETRIES (≥ 10) and asserts the cap and jitter expressions.
  • Docs / changeset / lock files — troubleshooting bullets on merge=union, on behavior when many runs finish at once, and recommending one file per writer; a patch changeset; recompiled workflows, whose only diff is the dropped concurrency: block.

The convergence primitives were already in place — createCommitOnBranch is a compare-and-swap on expectedHeadOid, and rejection triggers a remote-head refresh and a *.jsonl merge=union pull. The group was masking them behind a 3-attempt fixed backoff.

// before: fixed backoff, 4 attempts total
const delay = BASE_DELAY_MS * Math.pow(2, attempt);

// after: 11 attempts, jitter spreads out simultaneous finishers
const ceiling = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
const delay = Math.floor(Math.random() * ceiling) + 1;

10 attempts with a 20 s cap bounds the worst case at roughly two minutes. Re-running the 50-worker scenario against this change: 0/50 cancellations, all 50 transactions persisted, 43 succeeding on the first attempt and the rest within three.

Exposing the retry count in frontmatter was considered and dropped to keep the change minimal.

Review note

The new vitest guard asserts against raw source text, matching the adjacent pre-existing BASE_DELAY_MS * Math.pow(2, attempt) assertion. A behavioral test driving the loop with a stubbed failing push, mocked Math.random, and fake timers would be stronger and is a reasonable follow-up.


pr-sous-chef run: https://github.com/github/gh-aw/actions/runs/35645137146

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "github.com"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 19.7 AIC · ⊞ 9.4K · ◷
Comment /souschef to run again

…ries

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix concurrency group issue on push_repo_memory Remove push_repo_memory concurrency group so fan-out memory writes are not dropped Sep 21, 2026
Copilot AI requested a review from pelikhan September 21, 2026 18:16
@pelikhan
pelikhan marked this pull request as ready for review September 21, 2026 18:28
Copilot AI balanced review requested due to automatic review settings September 21, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

A late compare-and-swap race can create merge history rejected by the signed-commit path, preventing reliable convergence.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity

Open (3)
What changed in this PR

Removes lossy push_repo_memory concurrency groups to address #61644 and strengthens optimistic retry handling.

Changes:

  • Removes generated concurrency groups.
  • Adds capped, jittered retries and regression tests.
  • Updates documentation, changeset, and compiled workflows.
File Description
pkg/​workflow/​repo_memory.go Removes concurrency generation.
pkg/​workflow/​repo_memory_test.go Tests concurrency omission.
docs/​src/​content/​docs/​reference/​repo-memory.md Documents concurrent-write behavior.
actions/​setup/​js/​push_repo_memory.test.cjs Adds retry configuration guards.
actions/​setup/​js/​push_repo_memory.cjs Expands and jitters retries.
.github/​workflows/​workflow-health-manager.lock.yml Removes generated concurrency.
.github/​workflows/​weekly-blog-post-writer.lock.yml Removes generated concurrency.
.github/​workflows/​technical-doc-writer.lock.yml Removes generated concurrency.
.github/​workflows/​smoke-ci.lock.yml Removes generated concurrency.
.github/​workflows/​sergo.lock.yml Removes generated concurrency.
.github/​workflows/​security-compliance.lock.yml Removes generated concurrency.
.github/​workflows/​pr-triage-agent.lock.yml Removes generated concurrency.
.github/​workflows/​metrics-collector.lock.yml Removes generated concurrency.
.github/​workflows/​glossary-maintainer.lock.yml Removes generated concurrency.
.github/​workflows/​firewall-escape.lock.yml Removes generated concurrency.
.github/​workflows/​eslint-refiner.lock.yml Removes generated concurrency.
.github/​workflows/​developer-docs-consolidator.lock.yml Removes generated concurrency.
.github/​workflows/​delight.lock.yml Removes generated concurrency.
.github/​workflows/​deep-report.lock.yml Removes generated concurrency.
.github/​workflows/​daily-testify-uber-super-expert.lock.yml Removes generated concurrency.
.github/​workflows/​daily-storify.lock.yml Removes generated concurrency.
.github/​workflows/​daily-safeoutputs-git-simulator.lock.yml Removes generated concurrency.
.github/​workflows/​daily-news.lock.yml Removes generated concurrency.
.github/​workflows/​daily-harness-experiment-proposer.lock.yml Removes generated concurrency.
.github/​workflows/​daily-formal-spec-verifier.lock.yml Removes generated concurrency.
.github/​workflows/​daily-community-attribution.lock.yml Removes generated concurrency.
.github/​workflows/​daily-code-metrics.lock.yml Removes generated concurrency.
.github/​workflows/​daily-cli-performance.lock.yml Removes generated concurrency.
.github/​workflows/​daily-awf-spec-compiler-surfacing.lock.yml Removes generated concurrency.
.github/​workflows/​daily-arxiv-researcher.lock.yml Removes generated concurrency.
.github/​workflows/​daily-agent-of-the-day-blog-writer.lock.yml Removes generated concurrency.
.github/​workflows/​copilot-session-insights.lock.yml Removes generated concurrency.
.github/​workflows/​copilot-pr-prompt-analysis.lock.yml Removes generated concurrency.
.github/​workflows/​copilot-pr-nlp-analysis.lock.yml Removes generated concurrency.
.github/​workflows/​copilot-cli-deep-research.lock.yml Removes generated concurrency.
.github/​workflows/​copilot-centralization-optimizer.lock.yml Removes generated concurrency.
.github/​workflows/​copilot-agent-analysis.lock.yml Removes generated concurrency.
.github/​workflows/​audit-workflows.lock.yml Removes generated concurrency.
.github/​workflows/​agentic-token-optimizer.lock.yml Removes generated concurrency.
.github/​workflows/​agentic-token-audit.lock.yml Removes generated concurrency.
.github/​workflows/​agent-performance-analyzer.lock.yml Removes generated concurrency.
.changeset/​push-repo-memory-fanout.md Records the patch release.

Comment thread actions/setup/js/push_repo_memory.cjs Outdated
Comment on lines +744 to +745
const ceiling = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
const delay = Math.floor(Math.random() * ceiling) + 1;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 78c70da and hardened in d0abddc. Retry recovery now fetches the refreshed head and rebases this run’s commit(s) onto it with the JSONL union policy, so the next signed push sees linear history; if reconciliation fails, the job fails instead of retrying against an unreconciled tree.

Comment thread actions/setup/js/push_repo_memory.cjs Outdated
Comment on lines 743 to 746
if (attempt < MAX_RETRIES) {
const delay = BASE_DELAY_MS * Math.pow(2, attempt);
const ceiling = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
const delay = Math.floor(Math.random() * ceiling) + 1;
core.warning(`Push failed (attempt ${attempt + 1}/${MAX_RETRIES + 1}), retrying in ${delay}ms: ${errMsg}`);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 78c70da. ERR_VALIDATION push failures are now treated as deterministic and fail immediately instead of consuming the extended retry/backoff budget.

Comment on lines +1913 to +1920
const maxRetriesMatch = scriptContent.match(/const MAX_RETRIES = (\d+);/);
expect(maxRetriesMatch).not.toBeNull();
expect(Number(maxRetriesMatch[1])).toBeGreaterThanOrEqual(10);

// Backoff must be capped and jittered so retries of many runs spread out.
expect(scriptContent).toContain("const MAX_DELAY_MS");
expect(scriptContent).toContain("Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt))");
expect(scriptContent).toContain("Math.random() * ceiling");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 78c70da and d0abddc. Added behavioral vitest coverage that simulates a stale-head/CAS loss, controls jitter via the injected sleep/random seams, verifies retry uses the refreshed base, and checks the rebased JSONL file preserves both writers’ rows.

@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

❌ Ponytail Reviewer failed. Please review the logs for details.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Generated by Ponytail Reviewer for #62414

@github-actions

Copy link
Copy Markdown
Contributor

🔬 Test Quality Sentinel is analyzing test quality on this pull request...

@github-actions

Copy link
Copy Markdown
Contributor

🔎 PR Code Quality Reviewer is reviewing code quality for this pull request...

@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Design Decision Gate 🏗️. Review the logs for details.

No ADR enforcement needed: PR does not have the implementation label and has <=100 new lines of code in business logic directories.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Impeccable review — harden + audit (bug_fix)

The core change (dropping the per-branch concurrency group from push_repo_memory and relying on the existing compare-and-swap + retry loop) is correct and well-justified: GitHub Actions only queues one pending job per group, so the old group silently dropped fan-out writes. The Go and doc changes are clean; no dead references to the removed buildPushRepoMemoryConcurrencyGroup/encodeConcurrencyKeyPart remain, and the new TestPushRepoMemoryJobHasNoConcurrencyGroup test correctly asserts the field is empty.

Independently reviewing actions/setup/js/push_repo_memory.cjs, I confirm the three issues already raised in existing review comments are valid and worth addressing before merge:

  1. Retrying permanent errors wastes time (line ~746): ERR_VALIDATION-prefixed errors (unsupported commit shapes, policy violations) from pushSignedCommits/push_signed_commits.cjs are deterministic and will fail identically on every attempt, so retrying them up to 10 times just delays the inevitable setFailed by up to ~2 minutes.
  2. Merge-commit fallback loses signing on retry (line ~745): when the optimistic push loses the race, the recovery path does git pull --no-rebase -X ours, which can create a two-parent merge commit; pushSignedCommits's pre-flight explicitly refuses to sign merge commits, so a retry after a real race can fall back to an unsigned push under repos with signed-commit rulesets.
  3. New vitest guard is source-text-only (line ~1920): it doesn't exercise the actual convergence path (no stubbed failing push / mocked Math.random / fake timers), so a regression in the retry logic itself wouldn't be caught.

No new blocking issues found beyond these. Given they're already tracked in open review threads, I'm not duplicating inline comments.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet50 · 53.6 AIC · ⌖ 13.2 AIC · ⊞ 8.1K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd. The root-cause analysis (GitHub Actions cancels all but one pending job per concurrency group under fan-out) is well-diagnosed and clearly documented in the code comment and PR description, with quantified before/after evidence (11/50 → 50/50 transactions persisted). Requesting changes on a couple of gaps in the follow-through.

📋 Key Themes & Highlights

Key Themes

  • No job timeout set on push_repo_memory (pkg/workflow/repo_memory.go): the retry loop's new worst case is ~2 minutes of jittered backoff across 10 attempts, up from the old fixed 3-retry bound. Worth an explicit timeout-minutes so the new worst case is visible rather than implicit.
  • Test coverage is source-pattern-based, not behavioral (push_repo_memory.test.cjs new guard, and the Go TestPushRepoMemoryJobHasNoConcurrencyGroup): both are solid regression guards for the specific values changed (MAX_RETRIES, MAX_DELAY_MS, absence of Concurrency), but neither exercises the actual convergence path (a stubbed compare-and-swap loss driving a real retry+merge). This matches a pre-existing review comment on line 1920 of the test file that I'd endorse addressing.
  • Docs: the "one file per writer" mitigation is now the primary defense against lossy merges under heavy fan-out and would benefit from a concrete example rather than a single prose sentence.
  • Two existing unaddressed review comments (merge-commit-on-retry risk, and retrying permanent ERR_VALIDATION failures with the same 11-attempt budget) remain relevant and should be resolved before merge — they identify real correctness/efficiency gaps in the new retry path, not just style nits.

Positive Highlights

  • ✅ Clear, precise root-cause comment left at the call site explaining the GitHub Actions semantics and the convergence strategy — this is exactly the kind of instrumentation /diagnosing-bugs calls for.
  • ✅ Full-jitter capped backoff is the right primitive for spreading out simultaneous finishers, and the retry budget increase is justified by the fan-out math in the PR description.
  • ✅ Dead code (buildPushRepoMemoryConcurrencyGroup, encodeConcurrencyKeyPart, unused sort import) was cleanly removed alongside the behavioral change.
  • ✅ Docs, changeset, and lock-file regeneration are all consistent with the code change — no stray diffs.

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 112.6 AIC · ⌖ 13.7 AIC · ⊞ 10.1K
Comment /matt to run again

Comment thread pkg/workflow/repo_memory.go Outdated
return &Job{
Name: pushRepoMemoryJobName,
DisplayName: "",
RunsOn: c.formatFrameworkJobRunsOn(data),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] No timeout-minutes is set on this job, and the retry loop's worst case is ~2 minutes of jittered backoff across 10 attempts plus network/API latency per attempt. If the default runner/job timeout is generous this is fine, but worth confirming explicitly rather than relying on an implicit ceiling, since a stuck push (e.g. persistent GraphQL rate-limiting) could now run far longer than the old 3-retry version before failing.

💡 Why this matters

The PR's own root-cause analysis is precise (GH keeps 1 running + 1 pending per concurrency group), which is the right level of rigor for a /diagnosing-bugs-style fix. The remaining risk is the inverse failure mode: previously a stuck job failed fast (3 retries, fixed backoff); now it can legitimately spend minutes retrying against transient conditions before giving up. A short comment or explicit timeout-minutes on this job would make the new worst-case bound visible in the compiled workflow rather than left to GHA/runner defaults.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 78c70da. push_repo_memory now emits timeout-minutes: 10, and the workflow locks were recompiled so the retry ceiling is explicit in generated jobs.

// declare a concurrency group. GitHub Actions only queues one pending job per group, so a
// shared group silently cancels memory writes when many runs finish at once; the push script
// converges with a compare-and-swap retry loop instead.
func TestPushRepoMemoryJobHasNoConcurrencyGroup(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] This new test only asserts pushJob.Concurrency is empty — a fine regression guard, but it doesn't cover the behavior the PR claims to fix (that fan-out writes now converge). Consider a companion test simulating N concurrent buildPushRepoMemoryJob calls plus a JS-side integration test that stubs multiple retries racing on the same branch, so the "50/50 persisted" claim in the PR description has automated coverage, not just a manual re-run.

💡 Suggested direction

Go-side: this test can stay as-is (compiler output shape). The stronger coverage belongs in push_repo_memory.test.cjs, exercising the retry loop with a stubbed pushSignedCommits that fails N times with a stale-head error and succeeds on retry after refreshing baseRef — verifying the loop actually converges rather than only checking source-text patterns (as flagged by the existing review comment on line 1920 of that file).

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 78c70da and d0abddc. Kept the Go compiler-output guard and added JS behavioral retry tests for stale-head convergence and reconciliation failure handling.

- **Validation or patch-size failures**: Keep changes within `file-glob`, `max-file-size` (100KB default), `max-file-count` (100 default), and `max-patch-size` (10KB default).
- **Changes not persisting**: Confirm the directory path, let the workflow finish, and check the logs for push errors.
- **Merge conflicts**: Concurrent pushes are replayed onto the latest remote state, so your file changes win.
- **Merge conflicts**: Concurrent pushes are replayed onto the latest remote state, so your file changes win. `.jsonl` files are merged with `merge=union`, so rows written by concurrent runs are all kept; other file types keep the local version.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/grill-with-docs] Good addition, but consider making the "one file per writer" recommendation more concrete (e.g. a short example of naming a memory file per worker/matrix-index) since this is now the primary mitigation for lossy merges in high fan-out scenarios, not just a nice-to-have. As written it's a single prose sentence without an example, while the rest of this troubleshooting section leans on concrete configuration values (100KB, 100 files, 10KB).

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 78c70da. The repo-memory troubleshooting docs now include concrete per-writer file examples such as runs/${{ matrix.worker }}.jsonl and targets/${{ matrix.target }}.jsonl.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

pr-sous-chef
@copilot This PR still needs forward progress before a maintainer can merge it.

Please address the open review follow-ups below, refresh the branch if needed, and then run the pr-finisher skill.

Open review follow-ups (newest first):

Failed checks:

  • None currently; the branch-refresh and review follow-ups are the remaining actionable items.

I also requested a branch refresh for this PR.
Run: https://github.com/github/gh-aw/actions/runs/35640699353

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "github.com"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 33.5 AIC · ⊞ 9.4K · ◷
Comment /souschef to run again

Copilot AI and others added 2 commits September 21, 2026 19:11
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

pr-sous-chef
@copilot This PR still needs forward progress before a maintainer can merge it.
...

Addressed in 78c70da and d0abddc. The retry path now rebases onto refreshed heads without merge commits, deterministic ERR_VALIDATION failures fail immediately, behavioral retry coverage was added, push_repo_memory has an explicit timeout, docs include concrete per-writer examples, review threads were replied to, and the final local validation gate passed.

Copilot AI requested a review from gh-aw-bot September 21, 2026 19:23
@pelikhan
pelikhan merged commit 471e084 into main Sep 21, 2026
45 checks passed
@pelikhan
pelikhan deleted the copilot/fix-push-repo-memory-issue branch September 21, 2026 21:50
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.89.20

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Concurrency group on push_repo_memory silently drops memory writes under fan-out

4 participants