Documentation
¶
Overview ¶
Package rerun classifies the non-success jobs of a GitHub Actions workflow-run attempt as infrastructure zombies or real failures, and turns that classification into a rerun verdict. It backs the `go tool mage ci:classifyInfraFailures` and `ci:rerunInfraFailures` targets used by .github/workflows/rerun-infra-failures.yml (via .github/actions/classify-infra-failures), which re-runs a `Tests` run only when nothing in it genuinely failed. See docs/fixes/2026-09-03-rerun-infra-cancelled-ci-runs.md.
classify.go is deliberately HTTP-free: Classify/Verdict/WriteTSV/ WriteMarkdownSummary are pure functions of a decoded job list, which keeps the classification rules unit-testable against recorded API payloads (testdata/run-*.json) with no network or mocking involved. The network calls this package's callers need - listing a run attempt's jobs, reading a pull request's head SHA, and requesting a rerun - live in fetch.go and actions.go behind the small RESTClient interface, so those too are testable without a live GitHub API.
Package rerun is a generated GoMock package.
Index ¶
- Constants
- func PRHeadSHA(ctx context.Context, client RESTClient, repo string, number int) (string, error)
- func RerunFailedJobs(ctx context.Context, client RESTClient, repo, runID string) (stillRunning bool, err error)
- func RunAttempt(ctx context.Context, client RESTClient, repo, runID string) (int, error)
- func WriteMarkdownSummary(w io.Writer, run RunRef, classified []Classified, outcome Outcome, ...) error
- func WriteTSV(w io.Writer, classified []Classified) error
- type Class
- type Classified
- type Job
- type MockRESTClient
- type MockRESTClientMockRecorder
- type Options
- type Outcome
- type RESTClient
- type RunRef
- type Step
Constants ¶
const DefaultStuckGap = 5 * time.Minute
DefaultStuckGap is the minimum delay between a cancelled job's last step and its job-level completion for it to count as runner-stuck-after-complete. Ordinary cancellations land within seconds; reaped zombies take 30 minutes or more.
Variables ¶
This section is empty.
Functions ¶
func PRHeadSHA ¶
PRHeadSHA returns the current head commit SHA of pull request number in repo (owner/name form).
func RerunFailedJobs ¶
func RerunFailedJobs(ctx context.Context, client RESTClient, repo, runID string) (stillRunning bool, err error)
RerunFailedJobs requests a rerun of every non-success job in runID (repos/{repo}/actions/runs/{runID}/rerun-failed-jobs), mirroring `gh run rerun <runID> --failed`. When the API rejects the request because the run has not finished yet, it reports stillRunning=true instead of returning an error.
func RunAttempt ¶
RunAttempt returns runID's current attempt number (repos/{repo}/actions/runs/{id}). Callers gating on an attempt-count cap should call this immediately before acting on it rather than trusting an attempt number from an earlier event payload: `gh run rerun`/rerun-failed-jobs creates a new attempt from the run's latest execution regardless of which attempt triggered the caller, so a stale count can under-count how many attempts already exist.
func WriteMarkdownSummary ¶
func WriteMarkdownSummary(w io.Writer, run RunRef, classified []Classified, outcome Outcome, reason string) error
WriteMarkdownSummary renders a classification as a GitHub Actions step-summary block: a header linking to the run, a Markdown table of non-success jobs, and the verdict line.
Types ¶
type Class ¶
type Class string
Class is the verdict for one non-success job.
const ( // ClassRunnerStuckAfterComplete is a cancelled job whose every step // succeeded (or was skipped) but whose job-level completion landed at // least Options.StuckGap after its last step: the runner finished all its // work, never reported completion, and GitHub reaped it. On Windows this // is harden-runner's post step killing its agent mid DNS-restore. ClassRunnerStuckAfterComplete Class = "runner-stuck-after-complete" // ClassRunnerLost is a failed job with no failed step: the runner vanished. ClassRunnerLost Class = "runner-lost" // ClassCheckCascade is a failed job whose only failed steps are aggregator // steps named in checkResultStepNames (test.yml's test-required, k3s and // terraform-registry-cache verdict jobs). Those steps only inspect other // jobs' conclusions and never run tests, so they fail as a consequence of // a zombie upstream job. Coupled to test.yml's step-naming convention. ClassCheckCascade Class = "check-cascade" // ClassSuperseded is any other cancellation: a step was interrupted or the // job never started, because a newer run replaced it or a human cancelled. ClassSuperseded Class = "superseded" // ClassRealFailure is everything else: a step genuinely failed. ClassRealFailure Class = "real-failure" )
type Classified ¶
Classified is the class assigned to one non-success job.
func Classify ¶
func Classify(jobs []Job, opts Options) []Classified
Classify returns one Classified entry per job whose conclusion is not success, skipped or empty (still running), in input order.
type Job ¶
type Job struct {
Name string `json:"name"`
Status string `json:"status"`
Conclusion string `json:"conclusion"`
CompletedAt *time.Time `json:"completed_at"`
Steps []Step `json:"steps"`
}
Job is one job of a workflow-run attempt as returned by the GitHub jobs API.
func DecodeJobs ¶
DecodeJobs reads the jobs of a run attempt from r. It accepts the raw `{"jobs":[...]}` response, several such pages concatenated (the shape of `gh api ... --paginate` output) or a bare array of jobs.
func FetchJobs ¶
func FetchJobs(ctx context.Context, client RESTClient, repo, runID, runAttempt string) ([]Job, error)
FetchJobs pages through repos/{repo}/actions/runs/{runID}/attempts/{runAttempt}/jobs (per_page=100), following the Link header until it stops offering a "next" page - the same request `gh api ... --paginate` makes. It is the only exported function in this package that talks to the network on the read path; classify.go's Classify/Verdict/etc. remain pure.
type MockRESTClient ¶
type MockRESTClient struct {
// contains filtered or unexported fields
}
MockRESTClient is a mock of RESTClient interface.
func NewMockRESTClient ¶
func NewMockRESTClient(ctrl *gomock.Controller) *MockRESTClient
NewMockRESTClient creates a new mock instance.
func (*MockRESTClient) EXPECT ¶
func (m *MockRESTClient) EXPECT() *MockRESTClientMockRecorder
EXPECT returns an object that allows the caller to indicate expected use.
type MockRESTClientMockRecorder ¶
type MockRESTClientMockRecorder struct {
// contains filtered or unexported fields
}
MockRESTClientMockRecorder is the mock recorder for MockRESTClient.
func (*MockRESTClientMockRecorder) RequestWithContext ¶
func (mr *MockRESTClientMockRecorder) RequestWithContext(ctx, method, path, body any) *gomock.Call
RequestWithContext indicates an expected call of RequestWithContext.
type Outcome ¶
type Outcome string
Outcome is the rerun verdict for a whole run attempt.
const ( // OutcomeRerun means every non-success job is a zombie or a cascade of one. OutcomeRerun Outcome = "rerun" // OutcomeNoRerun means at least one job vetoes a rerun, or nothing is a zombie. OutcomeNoRerun Outcome = "no-rerun" // OutcomeNoJobs means the attempt has no non-success jobs at all. OutcomeNoJobs Outcome = "no-jobs" )
func Verdict ¶
func Verdict(classified []Classified) (Outcome, string)
Verdict decides whether a run attempt should be rerun: only when at least one job is runner-stuck-after-complete or runner-lost and every other one is check-cascade. Any real-failure or superseded job vetoes the rerun. The reason is a short human-readable justification for the log.
type RESTClient ¶
type RESTClient interface {
RequestWithContext(ctx context.Context, method, path string, body io.Reader) (*http.Response, error)
}
RESTClient is the slice of *api.RESTClient's (github.com/cli/go-gh/v2/pkg/api) method set that this package needs: one authenticated request, returning the raw response so callers can read pagination headers. Its narrowness lets tests inject a fake instead of a live GitHub API.
type Step ¶
type Step struct {
Name string `json:"name"`
Status string `json:"status"`
Conclusion string `json:"conclusion"`
// CompletedAt is nil when the API returns null. encoding/json parses
// RFC 3339 timestamps with or without fractional seconds.
CompletedAt *time.Time `json:"completed_at"`
}
Step is one step of a job as returned by the GitHub jobs API.