Skip to main content

recur

recur is a command-line tool that runs a single command repeatedly until it succeeds or allowed attempts run out. It implements optional exponential backoff with configurable jitter. It lets you define the success condition.

Requirements

Python 3.9 or later, PyPI package simpleeval (installed automatically with recur-command).

Installation

The recommended way to install recur is from PyPI with pipx.

pipx install recur-command
# or
pip install --user recur-command

recur is also available for download as a single-file Python ZIP application or "zipapp" with its dependencies included. A regular Python interpreter can run zipapps. Zipapps are attached to GitLab releases as .pyz files. They are automatically built for commits.

Usage

usage: recur [-h] [-V] [-b BACKOFF] [-c COND] [-d DELAY] [-j JITTER] [-m MAX]
             [-t TRIES] [-v]
             command ...

Retry a command with exponential backoff and jitter.

positional arguments:
  command               command to run
  args                  arguments

options:
  -h, --help            show this help message and exit
  -V, --version         show program's version number and exit
  -b BACKOFF, --backoff BACKOFF
                        multiplier applied to delay on every attempt (default:
                        1, no backoff)
  -c COND, --condition COND
                        success condition (simpleeval expression, default:
                        "code == 0")
  -d DELAY, --delay DELAY
                        constant or initial exponential delay (seconds,
                        default: 0)
  -j JITTER, --jitter JITTER
                        additional random delay (maximum seconds or "min,max"
                        seconds, default: "0,0")
  -m MAX, --max-delay MAX
                        maximum delay (seconds, default: 3600)
  -t TRIES, --tries TRIES
                        maximum number of attempts (negative for infinite,
                        default: 5)
  -v, --verbose         announce exit code and attempt number; adds debug
                        information for errors if used twice

recur exits with the last command's exit code, unless the user overrides this in the condition. When the command is not found during the last attempt, recur exits with the code 255.

The CLI options are modeled after the parameters of the retry decorator, which Python programmers may recognize. However, recur does not use retry. The jitter (random delay) behavior is different. Jitter is applied starting with the first retry, not the second. I think this is what the user expects. A single-number jitter argument results in random, not constant, jitter.

Conditions

recur supports a limited form of scripting. It allows you to set the success condition using the simpleeval expression language, which is a subset of Python. The default condition is code == 0. It means recur will stop retrying when the exit code of the command is zero.

You can use the following variables in the condition expression:

  • attempt: int — the number of the current attempt, starting with one. Combine with --tries -1 to use the condition instead of the built-in attempt counter.
  • code: int | None — the exit code of the last command. code is None when the command was not found.
  • command_found: bool — whether the last command was found.
  • time: float — the time the most recent attempt took, in seconds.
  • total_time: float — the time between the start of the first attempt and the end of the most recent, again in seconds.
  • max_tries: int — the value of the option --tries.

recur defines one custom function:

  • exit(code: int | None) -> None — exit with the exit code. If code is None, exit with the exit code for a missing command (255).

This function allows you to override the default behavior of returning the last command's exit code. For example, you can make recur exit with success when the command fails.

recur --condition 'code != 0 and exit(0)' sh -c 'exit 1'
# or
recur --condition 'exit(0) if code != 0 else False' sh -c 'exit 1'

In the following example we stop early and do not retry when the command's exit code indicates incorrect usage or a problem with the installation.

recur --condition 'code == 0 or (code in {1, 2, 3, 4} and exit(code))' curl "$url"

License

MIT.

Alternatives

recur was inspired by retry-cli. I wanted something like retry-cli, but without the Node.js dependency. There are other tools like this.

Metadata

Release files for recur-command 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for recur-command 0.5.0
File Size Uploaded Provenance
recur_command-0.5.0.tar.gz 23.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for recur-command 0.5.0
File Interpreter ABI Platform
recur_command-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 32.4 kB

Release files / recur_command-0.5.0.tar.gz

Download URL recur_command-0.5.0.tar.gz
Size 23.7 kB
Tags Source
SHA-256 checksum
How to use checksums
c389a5202261292e5127b2acb3c66e2698c9fcd99f4ebf08f2c13cc5314f1c46
BLAKE2b-256 checksum
How to use checksums
c6597c04e141d5afd85b860f4b304e47ccf09022b634690cbd5b001c4fa847e8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.4.20

Release files / recur_command-0.5.0-py3-none-any.whl

Download URL recur_command-0.5.0-py3-none-any.whl
Size 8.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3144954f5dc35275a12773e4536690709a095d1d4efa2f43b9470031bb7a91f7
BLAKE2b-256 checksum
How to use checksums
ce1c2e996724492697a1a3beb05f3da2b3b268b3242c9d3f94344ceb4a984efc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.4.20

Security for recur-command 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page