Skip to content

gitproto: match CommandStatusErr by value so target-ref moves classify#72

Closed
nodo wants to merge 1 commit into
mainfrom
errors/ref-rejected-value-target
Closed

gitproto: match CommandStatusErr by value so target-ref moves classify#72
nodo wants to merge 1 commit into
mainfrom
errors/ref-rejected-value-target

Conversation

@nodo

@nodo nodo commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Problem

ErrTargetRefMoved (PR #71, unreleased) never attaches on the real push path. asRefRejectedError and annotateLeaseFailure match go-git's CommandStatusErr with a pointer target:

var cs *packp.CommandStatusErr
if !errors.As(err, &cs) { return err }

but go-git returns CommandStatusErr by value from ReportStatus.Error() (value receiver Error(), constructed by value in report_status.go). errors.As against a **CommandStatusErr target never matches a value-typed CommandStatusErr in the chain, so:

  • asRefRejectedError falls through → no *RefRejectedError, moved never set → errors.Is(err, ErrTargetRefMoved) is always false on a live receive-pack rejection.
  • annotateLeaseFailure falls through → the lease-failure hint never fires.

Net effect: every concurrent target-ref rejection (remote ref has changed) passes through unclassified. The downstream mirror-pipeline worker that relies on errors.Is(err, ErrTargetRefMoved) to classify benign races sees nothing — the typed-rejection feature is inert in production.

The existing tests masked this by feeding &packp.CommandStatusErr{} (a pointer) as input, which the pointer target does match — but that is not how go-git hands the error over.

Fix

  • Both functions now use a value errors.As target (var cs packp.CommandStatusErr), matching go-git's actual return type. go-git's own tests confirm a value target is correct (s.ErrorAs(rs.Error(), &CommandStatusErr{})).
  • The table tests now build their input via go-git's real (&packp.CommandStatus{...}).Error() (value), and the underlying-error assertions use value targets.
  • New TestAsRefRejectedError_RealReportStatusPath drives a real ReportStatus.Error() end to end through the production wrap — it fails on the pointer target and passes on the value target, so a pointer-vs-value relapse fails CI.

Verification

go test ./..., go vet, gofmt all clean. Confirmed the new test fails on the pre-fix pointer target and passes after.

Bug was in the unreleased typed-rejection feature (#71); never shipped in a tagged release.

🤖 Generated with Claude Code


Note

Low Risk
Narrow error-wrapping fix in push status handling with stronger tests; behavior change is enabling the intended unreleased API rather than altering unrelated push logic.

Overview
Fixes typed push-rejection handling on the live receive-pack path so errors.As into *RefRejectedError and errors.Is for ErrTargetRefMoved actually work.

annotateLeaseFailure and asRefRejectedError now use a value packp.CommandStatusErr in errors.As, matching go-git’s value-typed ReportStatus.Error() output; the previous pointer target never matched, so lease hints and concurrent-move classification were effectively no-ops in production.

Tests build inputs via CommandStatus.Error() (not hand-built pointers), assert unwrap with value CommandStatusErr, and add TestAsRefRejectedError_RealReportStatusPath to guard against pointer-vs-value regressions. CHANGELOG documents the fix and clarifies that embedders should use a value errors.As target for the underlying go-git error.

Reviewed by Cursor Bugbot for commit 84f71be. Configure here.

asRefRejectedError and annotateLeaseFailure used errors.As against a
*packp.CommandStatusErr (pointer) target, but go-git returns CommandStatusErr
BY VALUE from ReportStatus.Error() (value receiver, constructed by value in
report_status.go). A pointer target never matches a value in the error chain,
so on the real receive-pack path the errors.As fell through, no *RefRejectedError
was built, moved was never set, and errors.Is(err, ErrTargetRefMoved) was always
false. Every live concurrent target-ref rejection ("remote ref has changed")
passed through unclassified — the typed-rejection feature was inert in
production. The same bug silently disabled the lease-failure hint.

The existing unit tests masked it by feeding &packp.CommandStatusErr{} (a
pointer) as input, which the pointer target does match — but that is not how
go-git hands the error over. Switch both functions to a value target, make the
table tests construct their input via go-git's real (&CommandStatus{}).Error()
(value), and add TestAsRefRejectedError_RealReportStatusPath which drives a real
ReportStatus.Error() end to end so a pointer-vs-value relapse fails CI.

Bug was in the unreleased typed-rejection feature (#71); never shipped tagged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nodo

nodo commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #73 — same fix, recreated with the Entire session checkpoint trailer attached.

@nodo nodo closed this Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant