Bobby Ho d210b311dc ci(.github): retry build-tool downloads in Windows signing jobs (#27664)
## Problem

The Windows code-signing path downloads two build tools with bare `wget`
and no retry, in both `ci.yaml` (`build` job) and `release.yaml`
(`release` job):

- `rcodesign` from GitHub releases
- `jsign-6.0.jar` from GitHub releases

A single transient network failure on either fetch fails the whole job.
In `ci.yaml` that turns `main` red via the `required` aggregator; in
`release.yaml` it fails a release.

This has happened. `Install rcodesign` failed on **2026-02-25** (in the
since-deleted `build-dylib` job), **2026-03-04**, and **2026-04-30**.

## Root cause

Two parts, one structural and one local.

**Structural:** GitHub Actions has no per-step retry. This repo already
knows toolchain provisioning is network-flaky and has
`.github/scripts/retry.sh` (3 attempts, 2s/4s/8s backoff), applied in
roughly 20 places. But `retry.sh` is a shell wrapper, so it can only
wrap `run:` steps. These four downloads are `run:` steps that were
simply never wrapped.

**Local:** the failing step's body, under `set -euo pipefail`, is
exactly three commands:

```sh
wget -O /tmp/rcodesign.tar.gz https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/...
sudo tar -xzf /tmp/rcodesign.tar.gz -C /usr/bin --strip-components=1 ...
rm /tmp/rcodesign.tar.gz
```

`tar` and `rm` operate on a file that was just written, so they are
deterministic. The only nondeterministic command in the step is the
network fetch, and a truncated download surfaces as a `tar` failure
whose cause is still the network.

### How we know

Enumerated failed runs through the GitHub Actions API and extracted, per
run, every failed job together with the names of its failed steps.

| Scan | Scope | Runs |
|---|---|---|
| `ci.yaml`, `main` | 2025-08-01 to 2026-07-29 | 934 |
| `ci.yaml`, all branches | most recent failures | 150 |
| `release.yaml` | all recorded failures | 22 |

The 934 is effectively the complete set; the API reports 923 failed
`main` runs over that period and the scans overlap slightly.

`Install rcodesign` appears **3 times on 3 separate dates**. Being
spread across dates rather than clustered, these behave as
**independent** events. That distinction is what selects the remedy, and
it is why this change is retry rather than removal.

For contrast, the `Setup Java` failures in the same jobs are **4
failures inside a single 90-minute window** on 2026-05-28, all from an
`api.azul.com` edge failure. That is a correlated outage, where every
attempt shares the same degraded dependency and retry provably cannot
help. **That defect is not addressed here** and needs a different fix;
see "Not addressed" below.

### Limits of the evidence

Stating these plainly so a reviewer can weigh them:

- **Cause is not directly confirmed.** Logs for all three `rcodesign`
failures are past GitHub's 90-day retention. The inference from the step
body above is strong but circumstantial.
- **Step-level attribution only reaches back about five months.** GitHub
prunes per-step detail from the jobs API while keeping job-level
conclusions. Probed directly: runs from 2026-03-01 onward return
populated `steps` arrays; runs from 2026-02-05 and earlier return empty
ones. So the true count over the full period could be higher; it cannot
be lower.
- **Impact is small.** This whole class of failure is 10 of 800
attributed non-`required` job failures, about **1.25%** of measured
`main` CI failure volume. This is not a significant reliability
improvement and should not be reviewed as one. The Postgres-backed Go
tests alone are over 40%.

## Solution

Wrap all four downloads in the existing retry helper:

```yaml
- ./.github/scripts/retry.sh -- wget -O /tmp/rcodesign.tar.gz https://...
```

Four lines changed, one per site: `ci.yaml:1287`, `ci.yaml:1321`,
`release.yaml:199`, `release.yaml:225`.

**How it works.** `retry.sh` runs the command, and on non-zero exit
sleeps 2s, 4s, then 8s before re-attempting, up to 3 attempts, then
fails with the original command in the error message. On success the
first time, behavior is unchanged.

**Why it works for these failures.** They are independent events, so
each attempt is a fresh trial with an independent chance of success. A
GitHub releases CDN blip on one run says nothing about the next 2
seconds. This is exactly the regime retry is for.

**Why retry rather than deletion.** These artifacts genuinely are not
present on the runner, so the network call is unavoidable. It can only
be made survivable. (Where a dependency *is* avoidable, deletion is the
better answer, which is the shape the `setup-java` fix will take.)

**Why `wget -O` is safe to retry.** `-O` truncates its output file on
each attempt, so a partial download from a failed attempt is overwritten
rather than appended to. No corruption path.

## Risks

Low, and worth naming precisely.

| Risk | Assessment |
|---|---|
| Behavior change on the success path | None. `retry.sh` execs the
command directly; a first-attempt success is identical to today. |
| A persistently broken URL now takes longer to fail | Yes, by up to 14s
of backoff, then it fails exactly as it does today. Negligible against a
job that takes tens of minutes. |
| `retry.sh` mangling `wget`'s own flags | `retry.sh` parses its own
options with `getopt`, so this was the main correctness concern.
Verified explicitly, both argument orders used in these workflows. See
Verification. |
| Relative path `./.github/scripts/retry.sh` resolving wrongly | These
steps set no `working-directory`, so cwd is the repo root. Deliberately
**excluded** the third `wget` at `release.yaml:713`
(`publish-homebrew`), which runs after `cd "$temp_dir"` where a
repo-relative path would break. |
| Retry masking a real regression | Bounded to 3 attempts over 14s. This
is not job-level auto-retry, which would hide regressions and is
explicitly not proposed. |

### Verification gap a reviewer should know about

**The changed steps do not run on PR CI.** `ci.yaml`'s `build` job is
gated on `github.ref == 'refs/heads/main' || startsWith(github.ref,
'refs/heads/release/')`, and `release.yaml` runs only on release. So
these four steps will execute for the first time on merge to `main`.
Verification below is therefore local plus static analysis, not a live
run of the modified steps.

## Verification

`retry.sh` argument passing, using a stub that prints what it received,
for both argument orders present in these workflows:

```
--- form A: -O before URL (rcodesign style) ---
argc=3
  arg1=[-O]
  arg2=[/tmp/rcodesign.tar.gz]
  arg3=[https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/apple-codesign-0.22.0-x86_64-unknown-linux-musl.tar.gz]
--- form B: URL before -O (jsign style) ---
argc=3
  arg1=[https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar]
  arg2=[-O]
  arg3=[/tmp/jsign-6.0.jar]
```

Order preserved and the `%2F` encoding in the rcodesign URL intact,
which was the specific failure mode to rule out.

`make lint/actions` (actionlint plus zizmor security audit):

```
✓ lint/actions/actionlint
No findings to report. Good job! (29 ignored, 102 suppressed)
```

`make pre-commit-light`:

```
✓ fmt/shfmt        ✓ lint/markdown    ✓ lint/actions/actionlint
✓ fmt/terraform    ✓ lint/shellcheck  ✓ lint/helm
✓ fmt/markdown     ✓ lint/bootstrap   ✓ lint/emdash
                   ✓ lint/migrations  ✓ lint/typos
                                      ✓ lint/mise-versions
✓ pre-commit-light passed (14s)
```

## Not addressed

Deliberately out of scope, listed so the remaining exposure is visible:

- **`actions/setup-java` with `distribution: "zulu"`** in both files.
This resolves a JDK from `api.azul.com` and downloads it from
`cdn.azul.com` on **every** run, confirmed from a successful `main`
build's log, because a `Java_Zulu_jdk` tool-cache lookup can never hit
the runner's cache. This is the correlated-outage defect from 2026-05-28
and retry cannot fix it.

The probe on this branch ([run
30492093096](https://github.com/coder/coder/actions/runs/30492093096))
has now answered what the fix should be. `depot-ubuntu-22.04-8` ships:

  ```
  RESULT: java found at /usr/bin/java
  OpenJDK Runtime Environment Temurin-11.0.31+11 (build 11.0.31+11)

  JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64
  JAVA_HOME_8_X64 / _11_X64 / _17_X64 / _21_X64 / _25_X64  (all present)
  tool cache: Java_Temurin-Hotspot_jdk
  ```

So the job downloads **Zulu 11.0.32+9 over two third-party hosts while
Temurin 11.0.31+11 is already on the runner's `PATH`**. The follow-up PR
will point `JAVA_HOME` at `$JAVA_HOME_11_X64` and drop the action, which
removes both Azul hosts while keeping the Java 11 pin rather than
inheriting whatever the image default becomes.
- **`storybook`'s `pnpm/action-setup`**, the last direct use in the repo
and the same unguarded `registry.npmjs.org` dependency originally
reported on the issue. Note `cache: true` does **not** mitigate it: per
the action's own `action.yml`, `cache` caches "the pnpm store
directory", not the pnpm binary.

## Scope

This PR is now a **single commit** (`673162b84e`) containing only the
four-line retry change.

A throwaway probe workflow briefly lived on this branch to answer the
JDK question above. It has served its purpose and the commit was
dropped, so nothing diagnostic remains here to review. Its result is
quoted in "Not addressed" and will be carried into the follow-up PR.

Refs coder/internal#929
2026-07-29 17:05:22 -07:00
2022-04-04 11:55:06 -05:00
2026-07-15 14:57:23 +01:00

Coder Logo Light Coder Logo Dark

Self-Hosted Cloud Development Environments and AI Agents

Coder Banner Light Coder Banner Dark

Quickstart | Docs | Why Coder | Premium

discord release godoc Go Report Card OpenSSF Best Practices OpenSSF Scorecard license

Coder is a self-hosted platform for cloud development environments and AI coding agents. Workspaces are defined with Terraform, connected through a secure Wireguard® tunnel, and automatically shut down when not used. Coder Agents runs a native AI coding agent whose loop executes in the control plane on your infrastructure, with no API keys in workspaces.

  • Define cloud development environments in Terraform
    • EC2 VMs, Kubernetes Pods, Docker Containers, etc.
  • Automatically shutdown idle resources to save on costs
  • Onboard developers in seconds instead of days
  • Delegate coding work to AI agents on your infrastructure
    • Bring any model (Anthropic, OpenAI, Google, Bedrock, self-hosted)
    • No LLM credentials in workspaces, user identity on every action
    • Centralized model governance, cost tracking, and audit logging

Coder platform showing templates and a running workspace

Quickstart

The most convenient way to try Coder is to install it on your local machine and experiment with provisioning cloud development environments using Docker (works on Linux, macOS, and Windows).

# First, install Coder
curl -L https://coder.com/install.sh | sh

# Start the Coder server (caches data in ~/.cache/coder)
coder server

# Navigate to http://localhost:3000 to create your initial user,
# create a Docker template and provision a workspace

Install

The easiest way to install Coder is to use the install script for Linux and macOS. For Windows, use the latest ..._installer.exe file from GitHub Releases.

curl -L https://coder.com/install.sh | sh

You can run the install script with --dry-run to see the commands that will be used to install without executing them. Run the install script with --help for additional flags.

See install for additional methods.

Once installed, you can start a production deployment with a single command:

# Automatically sets up an external access URL on *.try.coder.app
coder server

# Requires a PostgreSQL instance (version 13 or higher) and external access URL
coder server --postgres-url <url> --access-url <url>

Use coder --help to get a list of flags and environment variables. See the install guides for a complete tutorial.

Documentation

Browse the documentation or visit a specific section below:

  • Workspaces: Workspaces contain the IDEs, dependencies, and configuration information needed for software development
  • Templates: Templates are written in Terraform and describe the infrastructure for workspaces
  • Coder Agents: Delegate coding work to AI agents running on your self-hosted infrastructure
  • Administration: Learn how to operate Coder
  • Premium: Learn about paid features built for large teams
  • IDEs: Connect your existing editor to a workspace

Support

Feel free to open an issue if you have questions, run into bugs, or have a feature request.

Join our Discord to provide feedback on in-progress features and chat with the community using Coder!

Integrations

New integrations are always in progress. Open an issue to request one. Contributions are welcome in any official or community repository.

Official

Community

Contributing

New contributors are always welcome. If you are new to the Coder codebase, see the contribution guide to get started.

Hiring

Apply on the careers page if you are interested in joining the team.

S
Description
Provision remote development environments via Terraform
Readme AGPL-3.0
908 MiB
Languages
Go 74.9%
TypeScript 23.1%
Shell 0.8%
HCL 0.3%
PLpgSQL 0.3%
Other 0.4%