fix(git): ci-queue-wait.sh — parse the payload it was handed, not stdin (#1019)
ci/woodpecker/pr/ci Pipeline was canceled

Both python sites piped a status payload into `python3 - <<'PY'`. The heredoc binds
stdin to the program text, so `json.load(sys.stdin)` saw EOF, the bare `except` fired,
and the parser returned "unknown" for every input — success, pending and failure alike.
"unknown" then reaches a silent `exit 0` arm.

Consequence: the gate-6 queue guard has never made a determination. It exits 0 on every
invocation, on both the gitea and github paths (one shared parser). The pending wait
loop, --require-status and the 124 timeout were all unreachable code. What it still
validated was connectivity — an unresolved token, head sha, or platform could exit 1.

Fix is the one already shipped in the sibling: capture the payload with `payload=$(cat)`
before invoking python, pass it by environment. pr-ci-wait.sh:38 has carried a comment
describing this exact bug — "yielding EOF and returning unknown every time" — along with
the remedy. It was never backported to the sibling the constitution makes mandatory
before every push and merge.

Adds test-ci-queue-wait-parse.sh, enumerated in test:framework-shell. Every assertion is
on the RETURNED STATE STRING; a suite asserting only rc=0 passes against the broken build,
which is how this survived. Verified by mutation: against the pre-fix wrapper the suite
fails 10 of 14, and the four that pass are the four for which passing is correct (the
undecodable-input control, the unknown-vocabulary case, and both needle halves).

The needle scans with heredoc semantics rather than matching text. `json.load(sys.stdin)`
is correct under `python3 -c`, where the program comes from argv and stdin really is the
payload — ci-queue-wait.sh uses that form legitimately in gitea_get_branch_head_sha. A
flat grep flags that innocent site; the scanner names only the two defective ones.

Out of scope, deliberately, and recorded on #1019: token-in-argv (ps-visible) and the
hardcoded BRANCH="main". Bundling them would make a safety-critical parse fix harder to
review.

Refs #1019
This commit is contained in:
Jason Woltje
2026-07-31 08:42:48 -05:00
parent 826a8b3b26
commit 505b6f799c
3 changed files with 221 additions and 7 deletions
@@ -34,12 +34,18 @@ EOF
# get_remote_host and get_gitea_token are provided by detect-platform.sh
get_state_from_status_json() {
python3 - <<'PY'
# Capture piped JSON BEFORE invoking `python3 - <<PY`. The heredoc binds
# stdin to the Python program text — so json.load(sys.stdin) inside would
# try to re-read stdin after `-` already consumed it for the program,
# yielding EOF and returning "unknown" every time. Pass payload via env.
local payload
payload=$(cat)
CI_QUEUE_STATUS_JSON="$payload" python3 - <<'PY'
import json
import sys
import os
try:
payload = json.load(sys.stdin)
payload = json.loads(os.environ.get("CI_QUEUE_STATUS_JSON", ""))
except Exception:
print("unknown")
raise SystemExit(0)
@@ -83,12 +89,15 @@ PY
}
print_pending_contexts() {
python3 - <<'PY'
# Same stdin hazard as get_state_from_status_json above — pass payload via env.
local payload
payload=$(cat)
CI_QUEUE_STATUS_JSON="$payload" python3 - <<'PY'
import json
import sys
import os
try:
payload = json.load(sys.stdin)
payload = json.loads(os.environ.get("CI_QUEUE_STATUS_JSON", ""))
except Exception:
print("[ci-queue-wait] unable to decode status payload")
raise SystemExit(0)