Idempotent Installation Patterns¶
Context: Installation scripts must be re-runnable to add new components (plugins, packages, themes) without breaking existing installations.
The Problem¶
Installation scripts that exit early when the main binary is installed will skip sub-components (plugins, flavors, packages), creating silent failures and preventing updates:
# BAD: Exits early, skips plugins forever
if command -v yazi >/dev/null 2>&1; then
echo "Yazi already installed"
exit 0 # PROBLEM: Never installs plugins/flavors
fi
# Install plugins...
ya pkg add some-plugin
Symptoms:
- Initial installation fails silently (plugin install error hidden)
- Re-running install doesn't fix missing plugins
- Adding new plugins to script has no effect
- "Already installed" message but components missing
Root Causes¶
1. Early Exit in Scripts Scripts check for binary and exit before installing sub-components
2. Taskfile status: Checks
Task's status: field prevents task from running if binary exists
3. Combination of Both Redundant checks create double-skip behavior
The Solution¶
For installations with sub-components (yazi, npm, cargo):
- Remove
status:check from Taskfile - Always run the script - Script checks binary, continues to components - Don't exit early
- Make component installation idempotent - Safe to run multiple times
# GOOD: No status check, always runs
install-yazi:
desc: Install yazi terminal file manager with flavors and plugins
cmds:
- bash management/common/install/yazi.sh
# Note: No status check - always run to ensure plugins/flavors are up to date
# GOOD: Checks binary, but continues to plugins
if ! command -v yazi >/dev/null 2>&1; then
echo "Installing yazi binary..."
# Install binary
else
echo "Yazi binary already installed"
fi
# ALWAYS run plugin installation (ya pkg add is idempotent)
echo "Installing yazi plugins..."
ya pkg add AnirudhG07/nbpreview
ya pkg add pirafrank/what-size
For simple binary installations (yq, lazygit, uv):
- Keep
status:check in Taskfile - Skip if installed - Remove redundant
if command -v... exit 0- Task handles this
# GOOD: Task's status check is sufficient
install-lazygit:
desc: Install lazygit from GitHub releases
cmds:
- |
echo "Installing lazygit..."
# Download and install
status:
- command -v lazygit >/dev/null 2>&1
For packages with individual components (npm, cargo):
Use the check-then-install pattern for each package:
# GOOD: Check each package individually
install_if_missing() {
local package=$1
local command_name=${2:-$package}
if command -v "$command_name" >/dev/null 2>&1; then
echo " $package already installed, skipping"
else
echo " Installing $package..."
npm install -g "$package"
fi
}
install_if_missing typescript-language-server
install_if_missing bash-language-server
Key Learnings¶
- Installation scripts must be re-runnable - Adding new components should work
- Don't hide failures with early exits - Silent failures are landmines
- Task's
status:vs script checks - Understand which layer handles skipping - Idempotent operations are safe -
ya pkg addwon't reinstall existing plugins - Never use
|| echo "Failed (continuing)"- Masks real errors
Testing¶
After fixing, verify:
- Re-running install adds new plugins/packages
- Existing installations aren't broken
- Errors stop execution immediately
- "Already installed" messages are accurate
Related Files¶
management/common/install/yazi.sh- Yazi with plugins/flavorsmanagement/common/install/npm-globals.sh- Good pattern example- Installation scripts in
management/{macos,wsl,arch}/install/