verifyfirst

A command's return status · status-consumed-by-the-declaring-builtin · documented

A declaration builtin consumes the exit status of the substitution it assigns

NS-081 documentedstatus-consumed-by-the-declaring-builtin

A declaration builtin consumes the exit status of the substitution it assigns

reads as
`local token=$(fetch_token)` is followed by a status check, and the check passes. Conclusion drawn: fetch_token succeeded and token holds a token.
actually
bash(1) explains the ordinary case: if no command name results and one of the expansions contained a command substitution, the exit status of the command is the exit status of the last command substitution performed. Putting `local`, `declare`, `export` or `readonly` in front supplies a command name, so the status becomes that builtin's instead, and the return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable. The substitution's failure is discarded and the variable holds an empty string.
blind because
One line performs two operations and reports on the outer one. The status is a true statement about whether a variable was declared, offered where a statement about whether a value was obtained is expected.
the check
Separate the declaration from the assignment and compare the two forms: `local token; token=$(fetch_token)`. Observed on bash 5.2.21: `f(){ local out; out=$(false); echo $?; }` printed 1, `g(){ local out=$(false); echo $?; }` printed 0, and `h(){ export OUT=$(false); echo $?; }` printed 0. Under `set -e` the split form aborted the shell and the combined form ran on to completion returning 0.
cost of missing
An empty credential, empty version string or empty path is carried forward and fails somewhere far from its origin, usually as an authentication error or a path that resolves to the filesystem root.
mitigation
Declare on one line and assign on the next wherever the command's outcome matters; `set -e` only helps once the two are separated.
generalises to
Every wrapper that reports on itself rather than on what it invoked: shell builtins in front of assignments, test harnesses swallowing setup failures, entrypoints exiting on the shell's status rather than the program's.
source
man7.org

Reported as

Others this instrument misses

plain text · full registry