Check the Exit Code of a Script in a Shell Test
Read a script's exit status with echo $? right after it runs, because the next command overwrites it. In a pipeline only PIPESTATUS keeps every stage's status.
A test needs to know the status a script returned, and the value is not what you expected.
cat > check.sh <<'EOF'
#!/bin/sh
[ -f "$1" ] || { echo "missing: $1" >&2; exit 2; }
echo ok
EOF
sh check.sh /etc/hostname
echo "status: $?"
sh check.sh nope
echo "first echo sees: $?"
echo "second echo sees: $?"
sh check.sh nope 2> /dev/null | cat
echo "pipeline status: $?, stages: ${PIPESTATUS[*]}"
sh check.sh nope 2> /dev/null | cat
echo "stages: ${PIPESTATUS[*]}"ok
status: 0
missing: nope
first echo sees: 2
second echo sees: 0
pipeline status: 0, stages: 2 0
stages: 2 0$? holds the status of the last command, so it must be read at once. The run shows the check script returning 2 for a missing file. The first echo printed 2, and the second printed 0, because the first echo had succeeded in between. With a pipeline the status is that of the last stage, so check.sh nope | cat reported 0 even though the script returned 2. Bash keeps each stage's status in PIPESTATUS, which printed 2 0. See Exit Status.
PIPESTATUS is Bash and Zsh only, and it is overwritten by the very next command, so copy it into a variable at once. In plain sh use set -o pipefail where the shell has it, or keep the result of the first stage in a temporary file. Statuses above 255 wrap around.