Polls /api/v1/repos/<owner>/<repo>/releases and matches each tag_name against a shell-style glob (e.g. v1.2.3.*). Default 600s timeout, 5s interval, both overridable. Exits 0 with the matched tag on stdout, 1 on timeout, 2 on usage error, 3 on terminal API error (401/403/404). Intended to run in the background of a Claude Code session while CI produces the release. Includes spec and 42-assertion test using a PATH-shadowed mock curl. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.4 KiB
4.4 KiB
wait-for-release
Purpose
Poll a Gitea repository's releases endpoint until a release whose tag_name matches a shell-style glob pattern is published, or a timeout elapses. Designed to run in the background of a Claude Code session while a CI pipeline produces the release.
Usage
wait-for-release [OPTIONS] REPO PATTERN
Arguments
| Argument | Description |
|---|---|
REPO |
Gitea repo in owner/name form (e.g. skynet/myapp). |
PATTERN |
Shell-style glob matched against each release's tag_name (e.g. v1.2.3.*). Quote it to prevent the caller's shell from expanding it. |
Flags
| Flag | Description |
|---|---|
-t, --token TOKEN |
Gitea access token. Fallback order: $GITEA_TOKEN, $GITHUB_TOKEN. Omitted → unauthenticated request. |
--timeout SECONDS |
Max total wait time (default: 600). 0 means poll once then give up. |
--interval SECONDS |
Polling interval (default: 5; min: 1). |
--server URL |
Gitea base URL. Fallback order: $GITHUB_SERVER_URL, https://gitea.oreillyit.nz. |
-v, --verbose |
Emit polling progress to stderr. |
-n, --dryrun |
Print resolved configuration and exit; make no API calls. |
-h, --help |
Show usage. |
Behaviour
- Validate inputs: REPO must be
owner/name;--timeoutis a non-negative integer;--intervalis an integer ≥ 1. - Compute
API_URL = <server>/api/v1/repos/<owner>/<name>/releases?limit=50. - Compute
DEADLINE = now + timeout. - Loop:
GET API_URLwith a 30-second per-request timeout. AddAuthorization: token <TOKEN>when a token is available.- On HTTP 200: extract
tag_namevalues from the JSON body (viajqwhen installed, otherwise a simple regex). For each tag, test[[ "$tag" == $PATTERN ]](bash glob). On the first match, print the tag to stdout and exit 0. - On HTTP 401 or 403: print auth error to stderr, exit 3.
- On HTTP 404: print not-found error to stderr, exit 3.
- On any other HTTP code or network failure: keep retrying (log to stderr when
--verbose).
- Between polls, sleep for
--intervalseconds, capped so the sleep never overshoots the deadline. - When the deadline has passed without a match, print a timeout error to stderr and exit 1.
Dryrun Behaviour
When --dryrun is passed, no HTTP requests are made. Output (to stdout):
[dryrun] Server: https://gitea.oreillyit.nz
[dryrun] Repo: skynet/myapp
[dryrun] Pattern: v1.2.3.*
[dryrun] Timeout: 600s
[dryrun] Interval: 5s
[dryrun] Auth: yes
[dryrun] API URL: https://gitea.oreillyit.nz/api/v1/repos/skynet/myapp/releases?limit=50
Auth: yes when a token was supplied or present in the environment; otherwise Auth: no.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | A release matching PATTERN was found; its tag_name is on stdout. |
| 1 | Timeout elapsed with no match. |
| 2 | Usage error (missing / invalid arguments). |
| 3 | Terminal API error (401, 403, 404). |
Edge Cases
| Case | Handling |
|---|---|
| REPO missing the slash | Error, exit 2. |
| PATTERN unquoted by caller and expanded by their shell | Caller's responsibility; the spec requires quoting. |
| No token for a private repo | API returns 401/403 → exit 3. |
| Invalid token | HTTP 401 → exit 3. |
| Repo doesn't exist / hidden | HTTP 404 → exit 3. |
| Transient 5xx or network error | Retry each --interval seconds until timeout. |
| Multiple releases match PATTERN | The first match in API response order (most recent first) wins. |
| PATTERN matches an already-published release | Returns on the first poll. |
--timeout 0 |
Exactly one poll, then timeout if no match. |
--interval 0 |
Error, exit 2. |
Examples
# Wait for any build of a specific semver
wait-for-release skynet/myapp 'v1.2.3.*'
# Exact build
wait-for-release skynet/myapp 'v1.2.3.45'
# Any v1.x release, 20-minute window
wait-for-release --timeout 1200 skynet/myapp 'v1.*'
# With explicit token for a private repo
wait-for-release -t "$MY_TOKEN" skynet/private 'v1.2.*'
# Dryrun preview
wait-for-release --dryrun skynet/myapp 'v1.*'
In a Claude Code session
Invoke via the Bash tool with run_in_background=true. The session is notified when the script exits. On success stdout holds the matched tag; the exit code signals the outcome.
wait-for-release skynet/myapp "v${VERSION}.*"