SC2155: Declare and Assign Separately to Preserve Exit Codes

Avoid masking command failures in bash by separating local variable declaration from assignment. Fix SC2155 for reliable error detection.

Error Handling

Detailed Explanation

The Hidden Bug in local var=$(command)

When you combine local (or export, declare) with a command substitution, the exit status of the command is replaced by the exit status of the local keyword — which is almost always 0 (success).

The Problem

my_function() {
  local result=$(failing_command)  # Exit code is MASKED
  echo "Result: $result"           # Runs even if command failed!
}

Even with set -e, the script will NOT exit because local returns 0:

set -e
my_function() {
  local output=$(false)  # false returns 1, but local returns 0
  echo "This runs!"      # Still executes
}
my_function

The Fix

Declare and assign on separate lines:

my_function() {
  local result
  result=$(failing_command)  # Exit code is preserved
  echo "Result: $result"     # Only runs on success (with set -e)
}

Same Issue with export and declare

# BAD: exit code masked
export PATH=$(build_path)
declare -r CONFIG=$(load_config)

# GOOD: separate declaration and assignment
export PATH
PATH=$(build_path)

declare -r CONFIG
CONFIG=$(load_config)

When It Doesn't Matter

If the command cannot fail, or you don't care about its exit status, combining is acceptable (though the linter will still flag it):

local timestamp=$(date +%s)  # date rarely fails
local hostname=$(hostname)   # also unlikely to fail

Best Practice

Always separate declaration and assignment in functions. It costs nothing and prevents a class of hard-to-debug issues where failures are silently ignored.

Use Case

Bash functions that call external commands and need reliable error detection. Common in library scripts, error-handling wrappers, and any function where the return value of a command determines control flow.

Try It — Shell Script Linter

Open full tool