Skip to content

feat: retry createCommitOnBranch on stale-head conflict#153

Closed
smitthakkar96 wants to merge 1 commit into
planetscale:mainfrom
dash0hq:feat/retry-on-conflict
Closed

feat: retry createCommitOnBranch on stale-head conflict#153
smitthakkar96 wants to merge 1 commit into
planetscale:mainfrom
dash0hq:feat/retry-on-conflict

Conversation

@smitthakkar96

Copy link
Copy Markdown

What & why

When a concurrent push moves the branch head while a commit is being prepared, createCommitOnBranch fails with a STALE_DATA conflict:

Expected branch to point to "" but it did not. Pull and try again.

This adds opt-in retry so the tool recovers instead of failing hard.

Changes

  • --retries / --retry-wait flags (GHCOMMIT_RETRIES / GHCOMMIT_RETRY_WAIT, defaults 0 / 2s). The single client.Mutate is wrapped in a bounded loop: on a stale-head conflict it re-reads the current branch head, sets ExpectedHeadOid, and re-applies the same file changes, up to --retries times with exponential backoff + jitter.
  • isStaleHeadConflict — small, unit-tested predicate (case-insensitive match on expected branch to point to / pull and try again). Unrelated errors never retry.
  • currentHeadOid — head refresh via a GraphQL query that reuses the already-configured client, so GITHUB_GRAPHQL_URL (GHES) is honored.

@smitthakkar96 smitthakkar96 requested a review from a team as a code owner July 14, 2026 15:35
@smitthakkar96 smitthakkar96 force-pushed the feat/retry-on-conflict branch 2 times, most recently from 1991054 to e76f548 Compare July 14, 2026 15:48
When a concurrent push moves the branch head while a commit is being
prepared, createCommitOnBranch fails with a STALE_DATA conflict
("Expected branch to point to ... Pull and try again."). Add opt-in
--retries / --retry-wait (GHCOMMIT_RETRIES / GHCOMMIT_RETRY_WAIT) so the
tool re-reads the current branch head and re-applies the same file
changes on top of it, retrying with exponential backoff and jitter via
github.com/cenkalti/backoff/v7.

The mutation only touches the paths being committed, so re-applying on the
refreshed head preserves files changed by the concurrent push and does
last-write-wins on the committed paths. --retries 0 (the default) makes a
single attempt whose error is unwrapped from the RetryError so its output
is identical to before; behavior is unchanged unless retry is explicitly
requested. Non-conflict errors are marked permanent and never retried.

The head refresh reuses the already-configured githubv4 client so the
GITHUB_GRAPHQL_URL (GHES) endpoint is honored. The retry orchestration is
extracted into commitWithRetry and covered by tests that drive a real
githubv4 client against a mocked GraphQL server (testify assertions):
conflict -> head refresh -> retry -> success, retry exhaustion, and
no-retry on non-conflict errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbmGosuc83bRrRXD1v5m8F
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant