retry semantics when migrating pipelines to GitHub Actions. The migrated Unit Testing job originally failed because the Jenkins options { retry(2) } identifier had no transformer, and the migrated workflow ran npm test before installing dependencies—so Mocha wasn’t available.
This article covers:
- why the failure happened,
- how to implement a
retrytransformer that wraps test execution with a retry action, - how to avoid duplicate
shandjunitsteps, - and how to validate the migration with dry-runs.
- The Jenkinsfile contained
options { retry(2) }in the Unit Testing stage, but the importer did not include a transformer forretry, so that option was dropped (or commented out) with no equivalent in the generated workflow. - The migrated job executed
npm testwithout a precedingnpm installstep, so dependencies (Mocha) were missing and tests failed.
retry had no transformer)
- Add a transformer for the
retryidentifier soretry(2)becomes a set of GitHub Actions steps implementing retry semantics. - Ensure the repository is checked out and dependencies are installed before running tests.
- Prevent duplicate
shandjunitsteps generated by the importer for the same test command and results. - Use an existing retry action from the Marketplace (for example:
nick-fields/retry@v3) to implement retry behavior.
nick-fields/retry@v3) supports inputs like timeout_minutes, max_attempts, shell, and command.
Example usage:

retry around unit tests:
Creating a custom transformer (Ruby)
Add a transformer to your custom transformers file (e.g.,
ci-pipeline-transformer.rb) that maps retry into the sequence above. This implementation extracts max_attempts robustly and emits the steps as Ruby hashes which the generator will convert to YAML:
- This example hard-codes
npm testandtest-results.xmlbecause it targets a single Unit Testing retry usage. For broader usage, parse the corresponding Jenkinsshandjunititems to extract the command and report path dynamically. - Ensure numeric fields such as
max_attemptsare emitted as integers. If your generator quotes them as strings, adjust the generator to output raw integers.
helper_transformer.rb) that prints identifiers can help you confirm the shape of items the importer provides. Example log lines:
sh and junit steps
The importer may still generate sh (for npm test) and junit (for test-results.xml) steps. Add small transformers to return nil for those specific items so they are not emitted:
If your pipeline uses unverified actions (for example
EnricoMi/publish-unit-test-result-action@v2), either replace them with maintained verified alternatives (like actions/upload-artifact@v4.1.0) or run the importer with flags that allow unverified actions. Using unverified or deprecated actions can cause runtime failures.actions/upload-artifact@v4.1.0:
actions/checkout@v4- an
npm install --no-auditstep nick-fields/retry@v3that executesnpm testwithmax_attemptsfrom Jenkinsretryactions/upload-artifact@v4.1.0to uploadtest-results.xml

max_attempts. Observations:
- Retry attempts appear in Actions logs (
Attempt 1,Attempt 2, …). - If all attempts fail, the job fails after the final attempt and logs show “final attempt failed” or similar.

- Ensure essential setup steps (checkout, install dependencies) are present before running tests to avoid missing binaries like Mocha.
- Extract dynamic values (commands, file names, retry counts) from Jenkins identifiers whenever possible so transformers apply across multiple contexts.
- Prefer verified and actively maintained action versions; the importer can be configured to allow only verified actions so unverified steps are commented out or flagged.
- Use
gh actions-importerdry-run to iterate quickly and validate transformer output before migration.
- Retry Step Marketplace action: https://github.com/nick-fields/retry
- GitHub Actions: https://docs.github.com/actions
- Jenkins Pipeline Syntax: https://www.jenkins.io/doc/book/pipeline/syntax/
- gh actions-importer CLI: https://github.com/cli/gh-actions-importer (see repo for usage and flags)