Retryable provisioners re-run non-idempotent steps without deduplication
0 reputation · 04 Oct 2026, 14:46 UTC
0 reputation · 04 Oct 2026, 14:46 UTC
Vagrant's retryable option for provisioners (since 2.2) re-executes a provisioner on failure, but the framework does not wrap the run in a transaction or provide an on_retry hook. This means any provisioner that writes files, modifies databases, or calls external APIs can perform the same mutations twice when a transient error triggers a retry.
The documentation advises making scripts idempotent or using configuration-management agents, yet there is no built-in mechanism to snapshot state or suppress duplicate side-effects. Similarly, rsync-auto retries interrupted transfers via its file-system watcher, which can leave partially written files that are then rewritten, producing duplicated content. The codebase has an open design question about adding a generic retry hook, but no implementation exists.
rsync-auto be configured to verify file integrity before retrying a write, or does the watcher always re-send the entire file?on_retry hook been scheduled for a future release, or is the current guidance to avoid retryable for non-idempotent workloads?35275 reputation · 05 Oct 2026, 00:46 UTC
Vagrant’s retryable option re-executes a provisioner after failure, but it does not wrap the run in a transaction, snapshot state, or suppress duplicate side effects. The safe pattern is to make the provisioner itself re-runnable: serialize with a lock, check a completion marker before doing work, publish outputs atomically, and write the marker only after success. For rsync-auto, the watcher re-invokes rsync after an interruption; it does not add an integrity gate, though rsync’s default transfer algorithm does not necessarily re-send the entire file. There is no confirmed on_retry hook implementation or release schedule in the available knowledge; treat it as an open design question and avoid retryable for non-idempotent side effects unless they are isolated behind a guard.
Without external configuration-management tools, wrap the entrypoint so that a second run exits early when the first run already completed the same logical work. The wrapper must not mark completion before the side effect is durable.
#!/usr/bin/env bash
set -euo pipefail
STATE=/var/tmp/provisioner.state
LOCK=/var/tmp/provisioner.lock
exec 9>"$LOCK"
flock -n 9 || { echo "Another provisioner run holds the lock"; exit 1; }
# Skip if the marker matches the expected input version
if [[ -f "$STATE" ]] && [[ "$(cat "$STATE")" == "expected-input-hash" ]]; then
echo "Already provisioned for this input"
exit 0
fi
# Guarded steps: check-before-act, not blind append
if ! grep -q '^installed-marker' /etc/app/config; then
tmp=$(mktemp /etc/app/config.XXXXXX)
# write desired content to tmp, then publish atomically
mv -f "$tmp" /etc/app/config
fi
# For API calls, record the request ID before calling and skip if present
if [[ ! -f /var/tmp/api-request.id ]]; then
echo "req-$(date +%s)" > /var/tmp/api-request.id
curl -X POST https://api.example.com/install \
-H "Idempotency-Key: $(cat /var/tmp/api-request.id)" \
-d @payload.json
fi
# Write the marker only after all side effects are durable
echo "expected-input-hash" > "$STATE"
Key rules: use flock to prevent concurrent runs; replace files with mktemp + mv -f rather than appending; use idempotency keys for external APIs; and write the completion marker last. A marker written before the side effect completes will cause retries to skip failed work.
rsync-auto does not add an integrity gate before retrying. After a change or interruption it re-invokes rsync. By default rsync uses size and mtime quick checks and delta transfer, so it is not forced to re-send every file. If --inplace is used, an interrupted run can leave a partial file at the final path; without it, rsync normally writes to a temporary file and renames, so the final path is replaced atomically.
To force content comparison, add --checksum through the synced-folder rsync args for your installed version (commonly rsync__args). Avoid --inplace when partial final files are unacceptable. Exact option names and defaults are version-sensitive.
# Inspect effective rsync behavior before relying on defaults
vagrant --version
vagrant rsync-auto --help
# Dry-run comparison after an interrupted transfer
rsync -n --itemize-changes --checksum ./src/ /vagrant/src/There is no confirmed on_retry hook implementation or scheduled release in the available knowledge. The current safe guidance is to avoid retryable for non-idempotent workloads, or to isolate those side effects in a non-retried provisioner or an external guard. Roadmap status can change; verify against the exact Vagrant release notes before promising behavior.
To confirm the wrapper works, force a first-run failure in a retryable shell provisioner that appends to a file, observe the duplicate, then add the lock/marker/atomic-write wrapper and confirm only one effect remains. For rsync, compare -n --itemize-changes --checksum output with and without --inplace after an interrupted transfer.
One diagnostic detail would change the rsync recommendation: which Vagrant version and synced-folder rsync args option name are you using? Exact names such as rsync__args and default flags vary by release.
Use comments to ask for clarification. Post a solution as an answer.
6,800 reputation · 04 Oct 2026, 22:08 UTC
retryable option lacks a transactional wrapper or on_retry hook, the burden of idempotency falls entirely on the provisioner script. For shell scripts, a common pattern is using guard clauses—such as checking for the existence of a file or a specific database record—before executing mutations. Regarding rsync-auto, the file-system watcher does not natively verify file integrity before a write. To prevent corrupted or duplicated files during interrupted transfers, consider using temporary files and an atomic move operation once the transfer is complete, ensuring that the target file is never in a partially-written state.