Skip to content

Commit 2de308f

Browse files
feat(scripts): add Push-Main.ps1 to auto-retry GitLab creation-gated pipelines (#71)
GitLab.com kills fresh-push pipelines at creation when the namespace is over its compute-minutes quota (all jobs stuck in 'created'); a retry bypasses the gate. Push-Main.ps1 pushes then auto-retries such pipelines via glab for gitlab.com remotes; no-op for GitHub/ADO. Closes #70
1 parent 0e10a72 commit 2de308f

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

scripts/Push-Main.ps1

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#Requires -Version 7.0
2+
<#
3+
.SYNOPSIS
4+
Pushes the current branch and, for gitlab.com remotes, auto-retries a pipeline that was
5+
killed by GitLab.com's compute-minutes quota gate at creation time.
6+
7+
.DESCRIPTION
8+
GitLab.com evaluates the namespace compute-minutes quota when a pipeline is CREATED (on a
9+
fresh push). When the namespace is over quota, the pipeline is marked 'failed' within ~1s
10+
with every job stuck in 'created' (started_at is null) - even when every job is tagged for a
11+
self-hosted runner fleet that does not consume shared minutes. Retrying the pipeline skips
12+
that creation-time gate, so the retry runs normally on the fleet.
13+
14+
This wrapper performs a normal 'git push', then (only for gitlab.com remotes) waits for the
15+
resulting pipeline and, if it died at the creation gate, retries it automatically via glab.
16+
For non-gitlab.com remotes it just pushes and returns.
17+
18+
Permanent fixes (GitLab-side, not automatable here): add compute minutes to the namespace,
19+
or wait for the monthly quota reset.
20+
21+
.PARAMETER Remote
22+
Git remote to push to. Defaults to 'origin'.
23+
24+
.PARAMETER RefSpec
25+
Optional branch or refspec to push (e.g. 'main'). Defaults to the current branch.
26+
27+
.PARAMETER WaitSeconds
28+
Seconds to wait between polls while the pipeline is being created. Defaults to 8.
29+
30+
.PARAMETER PollAttempts
31+
Number of polls to locate the created pipeline. Defaults to 6.
32+
33+
.EXAMPLE
34+
pwsh -File scripts/Push-Main.ps1
35+
Pushes the current branch and auto-retries a creation-gated GitLab pipeline.
36+
37+
.EXAMPLE
38+
git pushm
39+
Same, via the optional alias: git config alias.pushm '!pwsh -File scripts/Push-Main.ps1'
40+
#>
41+
[CmdletBinding()]
42+
param(
43+
[string]$Remote = 'origin',
44+
[string]$RefSpec,
45+
[int]$WaitSeconds = 8,
46+
[int]$PollAttempts = 6
47+
)
48+
49+
Set-StrictMode -Version Latest
50+
$ErrorActionPreference = 'Stop'
51+
52+
$pushArgs = @('push', $Remote)
53+
if ($RefSpec) { $pushArgs += $RefSpec }
54+
& git @pushArgs
55+
if ($LASTEXITCODE -ne 0) { throw 'git push failed.' }
56+
57+
$remoteUrl = (& git remote get-url $Remote).Trim()
58+
if ($remoteUrl -notmatch 'gitlab\.com') {
59+
Write-Host "Remote '$Remote' is not gitlab.com; no pipeline retry workaround needed."
60+
return
61+
}
62+
63+
if (-not (Get-Command glab -ErrorAction SilentlyContinue)) {
64+
Write-Warning 'glab CLI not found; cannot auto-retry. If the pipeline died at creation, retry it manually.'
65+
return
66+
}
67+
68+
$sha = (& git rev-parse HEAD).Trim()
69+
$projectPath = ($remoteUrl -replace '^.*gitlab\.com[:/]', '') -replace '\.git$', ''
70+
$projectEnc = [uri]::EscapeDataString($projectPath)
71+
72+
$pipeline = $null
73+
for ($attempt = 1; $attempt -le $PollAttempts; $attempt++) {
74+
Start-Sleep -Seconds $WaitSeconds
75+
$pipes = @(glab api "projects/$projectEnc/pipelines?sha=$sha&per_page=1" | ConvertFrom-Json)
76+
if ($pipes.Count -gt 0) {
77+
$pipeline = $pipes[0]
78+
if ($pipeline.status -in @('failed', 'running', 'success', 'pending')) { break }
79+
}
80+
}
81+
82+
if (-not $pipeline) {
83+
Write-Warning "No pipeline found for commit $sha after $($PollAttempts * $WaitSeconds)s. Check GitLab manually."
84+
return
85+
}
86+
87+
if ($pipeline.status -ne 'failed') {
88+
Write-Host "Pipeline $($pipeline.id) status: $($pipeline.status). No creation-gate retry needed."
89+
return
90+
}
91+
92+
# Distinguish a creation-time quota kill (all jobs still 'created') from a real job failure.
93+
$jobs = @(glab api "projects/$projectEnc/pipelines/$($pipeline.id)/jobs?per_page=100" | ConvertFrom-Json)
94+
$startedJobs = @($jobs | Where-Object { $_.status -ne 'created' })
95+
if ($startedJobs.Count -gt 0) {
96+
Write-Warning "Pipeline $($pipeline.id) failed after jobs started - this is a real failure, not the creation gate. Inspect the jobs."
97+
return
98+
}
99+
100+
Write-Host "Pipeline $($pipeline.id) died at the GitLab.com creation-time quota gate; retrying on the fleet..."
101+
$retry = glab api --method POST "projects/$projectEnc/pipelines/$($pipeline.id)/retry" | ConvertFrom-Json
102+
Write-Host "Retried pipeline $($retry.id) - status: $($retry.status)."

0 commit comments

Comments
 (0)