psql exits 0 after an error. It finished the file.
The common read: A statement in my script errored, so the process exits nonzero and the runner that gates on exit codes catches it.
The exit code reports on the psql process, not on your statements. Postgres documents four values and each one names a condition of the client process: 0 if it finishes normally, 1 if a fatal error of its own occurs such as out of memory or file not found, 2 if the connection to the server fails and the session is not interactive, and 3 if an error occurs in a script and the variable ON_ERROR_STOP is set.
Read that list for what is missing. A statement the server rejected appears on it only when ON_ERROR_STOP is set, and the setting is off by default. Finishing normally means reaching the end of the input, because the documented default is that command processing continues after an error.
So the transcript below shows a rejected UPDATE, a statement that ran after it, and a process that returned 0 to the shell. The account that should have moved to pro stays on standard, and nothing re-runs that statement, because the job's run is recorded as a success. A month later, finance asks about one invoice and files the question against the billing report, although the report is correct. psql printed the error, but the job runner reads only the exit code.

