From 1ad1818cc76da9fdb0fbb4ad310de82fd4c2f0f1 Mon Sep 17 00:00:00 2001 From: LucaLin <78164141+LucaLin233@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:58:32 +0000 Subject: [PATCH 1/8] test(push): capture bounded HUP publication diagnostics --- tests/test-push-worker-registration.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test-push-worker-registration.sh b/tests/test-push-worker-registration.sh index db007b5..0801658 100755 --- a/tests/test-push-worker-registration.sh +++ b/tests/test-push-worker-registration.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash set -euo pipefail +# Safe runtime identity only: no environment dump, credentials, or xtrace. +printf "DIAG: bash=%s kernel=%s\n" "$BASH_VERSION" "$(uname -r)" +dpkg-query -W bash libc6 2>/dev/null || true ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) readonly ROOT_DIR @@ -1453,6 +1456,8 @@ run_state_publication_signal_case() ( [[ "$worker_pid" =~ ^[1-9][0-9]*$ && "$worker_start" =~ ^[1-9][0-9]*$ && "$managed_sid" =~ ^[1-9][0-9]*$ ]] || fail "$state/$phase/$signal_name marker identity malformed" wait_test_process_identity_present "$worker_pid" "$worker_start" || { cat "$marker" >&2; fail "$state/$phase/$signal_name worker identity missing at hook"; } test_watchdog_process 30 "$root/watchdog-timeout" "$main_pid" & watchdog=$!; watchdog_start=$(wait_test_process_start "$watchdog") + printf "DIAG: publication state=%s phase=%s signal=%s main=%s/%s worker=%s/%s sid=%s\n" \ + "$state" "$phase" "$signal_name" "$main_pid" "$main_start" "$worker_pid" "$worker_start" "$managed_sid" kill "-$signal_name" "$main_pid"; wait "$main_pid" || rc=$? kill -TERM "$watchdog" 2>/dev/null || true; wait "$watchdog" 2>/dev/null || true [[ ! -e "$root/watchdog-timeout" ]] || fail "$state/$phase/$signal_name watchdog fired" @@ -1490,6 +1495,15 @@ for publication_state in $publication_states; do done ( + # Three bounded extra probes; preserve original matrix and stop at first failure. + for diagnostic_round in 1 2 3; do + TEST_DIR="$TEST_DIR/hup-probe-$diagnostic_round" + mkdir -m 0700 "$TEST_DIR" + cp "${TEST_DIR%/hup-probe-*}/state-publication-child.sh" "$TEST_DIR/state-publication-child.sh" + printf "DIAG: extra HUP probe=%s/3\n" "$diagnostic_round" + run_state_publication_signal_case cleanup_failed after-rename HUP 129 + TEST_DIR=${TEST_DIR%/hup-probe-*} + done root="$TEST_DIR/normal-parallel"; setup_fixture "$root" : > "$root/capture/current"; : > "$root/capture/max" push_to_server() { From d66d727b3cc2581233b0b825bb61887f2c060c13 Mon Sep 17 00:00:00 2001 From: LucaLin <78164141+LucaLin233@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:59:08 +0000 Subject: [PATCH 2/8] test(push): isolate repeat fixtures without mutating readonly root --- tests/test-push-worker-registration.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/test-push-worker-registration.sh b/tests/test-push-worker-registration.sh index 0801658..69fa826 100755 --- a/tests/test-push-worker-registration.sh +++ b/tests/test-push-worker-registration.sh @@ -1438,7 +1438,7 @@ run_state_publication_signal_case() ( } trap cleanup_publication_fixture EXIT mode=active; [[ "$state" == cleanup_failed ]] && mode=cleanup - root="$TEST_DIR/state-publish-$state-$phase-$signal_name"; CURRENT_FIXTURE_ROOT=$root + root="$TEST_DIR/state-publish-$state-$phase-$signal_name${5:-}"; CURRENT_FIXTURE_ROOT=$root write_active_grace_fixture "$root" mkdir -m 0700 "$root/capture" marker="$root/capture/publish-marker" @@ -1497,12 +1497,8 @@ done ( # Three bounded extra probes; preserve original matrix and stop at first failure. for diagnostic_round in 1 2 3; do - TEST_DIR="$TEST_DIR/hup-probe-$diagnostic_round" - mkdir -m 0700 "$TEST_DIR" - cp "${TEST_DIR%/hup-probe-*}/state-publication-child.sh" "$TEST_DIR/state-publication-child.sh" printf "DIAG: extra HUP probe=%s/3\n" "$diagnostic_round" - run_state_publication_signal_case cleanup_failed after-rename HUP 129 - TEST_DIR=${TEST_DIR%/hup-probe-*} + run_state_publication_signal_case cleanup_failed after-rename HUP 129 "-probe-$diagnostic_round" done root="$TEST_DIR/normal-parallel"; setup_fixture "$root" : > "$root/capture/current"; : > "$root/capture/max" From 76c205cc872f786e109298cba9895745028967ca Mon Sep 17 00:00:00 2001 From: LucaLin <78164141+LucaLin233@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:30:31 +0000 Subject: [PATCH 3/8] test(push): add bounded isolated HUP parser controls --- tests/test-hup-parser.sh | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/test-hup-parser.sh diff --git a/tests/test-hup-parser.sh b/tests/test-hup-parser.sh new file mode 100644 index 0000000..486d943 --- /dev/null +++ b/tests/test-hup-parser.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -euo pipefail +# Inert parser controls only; no production code, network or real credentials. +unset GH_TOKEN GITHUB_TOKEN SSH_PRIVATE_KEY_B64 +root=$(mktemp -d) +cleanup() { rm -rf -- "$root"; } +trap cleanup EXIT +command -v timeout >/dev/null +printf "DIAG: parser controls bash=%s\n" "$BASH_VERSION" +cat > "$root/child.sh" < "$ready" +end=$((SECONDS + 2)) +case "$mode" in + plain) while (( SECONDS < end )); do :; done ;; + command) while (( SECONDS < end )); do value=$(printf x); [[ $value == x ]]; done ;; + process) while (( SECONDS < end )); do while IFS= read -r value; do :; done < <(jobs -pr); done ;; + *) exit 90 ;; +esac +# Reaching this point means no HUP was handled; never count it as success. +exit 91 +CHILD +cat > "$root/controller.sh" < Date: Tue, 8 Sep 2026 12:30:47 +0000 Subject: [PATCH 4/8] test(push): keep generated parser fixtures literal --- tests/test-hup-parser.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test-hup-parser.sh b/tests/test-hup-parser.sh index 486d943..d853df5 100644 --- a/tests/test-hup-parser.sh +++ b/tests/test-hup-parser.sh @@ -7,7 +7,7 @@ cleanup() { rm -rf -- "$root"; } trap cleanup EXIT command -v timeout >/dev/null printf "DIAG: parser controls bash=%s\n" "$BASH_VERSION" -cat > "$root/child.sh" < "$root/child.sh" <<\CHILD #!/usr/bin/env bash set -euo pipefail handler() { exit 129; } @@ -25,7 +25,7 @@ esac # Reaching this point means no HUP was handled; never count it as success. exit 91 CHILD -cat > "$root/controller.sh" < "$root/controller.sh" <<\CONTROL #!/usr/bin/env bash set -euo pipefail root=$1 mode=$2 round=$3 delay=$4 From 3123e17235cdebfc72bf3561d969b3f63c177c4f Mon Sep 17 00:00:00 2001 From: LucaLin <78164141+LucaLin233@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:31:19 +0000 Subject: [PATCH 5/8] test(push): require handler evidence rather than signal exit alone --- tests/test-hup-parser.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test-hup-parser.sh b/tests/test-hup-parser.sh index d853df5..9f972a0 100644 --- a/tests/test-hup-parser.sh +++ b/tests/test-hup-parser.sh @@ -10,10 +10,10 @@ printf "DIAG: parser controls bash=%s\n" "$BASH_VERSION" cat > "$root/child.sh" <<\CHILD #!/usr/bin/env bash set -euo pipefail -handler() { exit 129; } -trap handler HUP +handler() { : > "$ready.handled"; exit 129; } mode=$1 ready=$2 +trap handler HUP : > "$ready" end=$((SECONDS + 2)) case "$mode" in @@ -30,7 +30,7 @@ cat > "$root/controller.sh" <<\CONTROL set -euo pipefail root=$1 mode=$2 round=$3 delay=$4 ready="$root/ready-$mode-$round" -# Direct child remains unreaped until wait; its PID cannot be reused meanwhile. +# Signal only the short-lived direct fixture child after its readiness marker. env --default-signal=HUP,INT,TERM bash "$root/child.sh" "$mode" "$ready" & child=$! end=$((SECONDS + 2)) @@ -44,7 +44,7 @@ kill -HUP "$child" status=0 wait "$child" || status=$? printf "DIAG: mode=%s round=%s delay=%s exit=%s\n" "$mode" "$round" "$delay" "$status" -[[ $status == 129 ]] +[[ $status == 129 && -f $ready.handled ]] CONTROL # 3 modes x 6 cases, at most 5 seconds per case (including forced teardown). # Do not retry failures. Delay varies delivery, not a claim of exact parser timing. From 2a3ee86e6ce5ed1477a879624c540e24774c29a5 Mon Sep 17 00:00:00 2001 From: LucaLin <78164141+LucaLin233@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:44:17 +0000 Subject: [PATCH 6/8] fix(push): avoid signal-sensitive process substitution in job polling --- tools/push.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/push.sh b/tools/push.sh index 3a4ac17..b578441 100755 --- a/tools/push.sh +++ b/tools/push.sh @@ -941,10 +941,13 @@ process_identity_matches() { } job_is_active() { - local expected="$1" pid + local expected="$1" pid active_jobs + # Bash 5.2 can report a trap parser EOF when HUP interrupts process substitution. + # Snapshot the inherited job table without process substitution or word splitting. + active_jobs=$(jobs -pr) || return 1 while IFS= read -r pid; do [[ "$pid" == "$expected" ]] && return 0 - done < <(jobs -pr) + done <<< "$active_jobs" return 1 } From 89c2bb1306cd07066cfa3e3f01ca55da02658f17 Mon Sep 17 00:00:00 2001 From: LucaLin <78164141+LucaLin233@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:44:40 +0000 Subject: [PATCH 7/8] test(push): gate safe job snapshot and retain opt-in legacy reproducer --- tests/test-hup-parser.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test-hup-parser.sh b/tests/test-hup-parser.sh index 9f972a0..26b8f43 100644 --- a/tests/test-hup-parser.sh +++ b/tests/test-hup-parser.sh @@ -19,6 +19,7 @@ end=$((SECONDS + 2)) case "$mode" in plain) while (( SECONDS < end )); do :; done ;; command) while (( SECONDS < end )); do value=$(printf x); [[ $value == x ]]; done ;; + snapshot) while (( SECONDS < end )); do active_jobs=$(jobs -pr); while IFS= read -r value; do :; done <<< "$active_jobs"; done ;; process) while (( SECONDS < end )); do while IFS= read -r value; do :; done < <(jobs -pr); done ;; *) exit 90 ;; esac @@ -48,11 +49,15 @@ printf "DIAG: mode=%s round=%s delay=%s exit=%s\n" "$mode" "$round" "$delay" "$s CONTROL # 3 modes x 6 cases, at most 5 seconds per case (including forced teardown). # Do not retry failures. Delay varies delivery, not a claim of exact parser timing. -for mode in plain command process; do +# Legacy process mode already reproduced EOF in run 34226553908. +# Keep it available explicitly; the default gate validates the replacement. +modes="plain command snapshot" +if [[ ${HUP_PARSER_INCLUDE_LEGACY:-false} == true ]]; then modes="$modes process"; fi +for mode in $modes; do round=0 for delay in 0 0.001 0.005 0.01 0.02 0.05; do round=$((round + 1)) timeout --signal=TERM --kill-after=1s 4s bash "$root/controller.sh" "$root" "$mode" "$round" "$delay" done done -printf "PASS: all 18 bounded HUP parser controls\n" +printf "PASS: all selected bounded HUP parser controls\n" From bbccfb92fc6f324c8a0cb040945e92abca5826db Mon Sep 17 00:00:00 2001 From: LucaLin <78164141+LucaLin233@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:45:02 +0000 Subject: [PATCH 8/8] test(push): verify job snapshot recognizes and reaps direct children --- tests/test-push-worker-registration.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test-push-worker-registration.sh b/tests/test-push-worker-registration.sh index 69fa826..ee5bb3b 100755 --- a/tests/test-push-worker-registration.sh +++ b/tests/test-push-worker-registration.sh @@ -85,6 +85,19 @@ printf source-safe assert_eq source-safe "$source_result" "source keeps zero runtime side effects" # shellcheck source=../tools/push.sh source "$SCRIPT" +( + # Inert direct-child job table: no SSH or production services. + sleep 10 & snapshot_child=$! + cleanup_snapshot_child() { kill -TERM "$snapshot_child" 2>/dev/null || true; wait "$snapshot_child" 2>/dev/null || true; } + trap cleanup_snapshot_child EXIT + job_is_active "$snapshot_child" || fail "snapshot misses active direct child" + if job_is_active 0; then fail "snapshot accepts absent PID"; fi + kill -TERM "$snapshot_child" + wait "$snapshot_child" 2>/dev/null || true + if job_is_active "$snapshot_child"; then fail "snapshot retains reaped child"; fi + trap - EXIT + pass "job snapshot preserves direct-child matching and reap semantics" +) trap 'rm -rf "$TEST_DIR"' EXIT setup_fixture() {