From ed40ed61951fb77f078dc5b1bbba2ddd133b910c Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 14 Oct 2026 18:39:43 +0000 Subject: [PATCH 1/4] ci(audit): let the dependency audit be re-run by hand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reconciler is what closes an issue once its findings are gone, so the gate needed a way to be run outside the Monday schedule: after a remediation lands, and to prove the reconcile path itself works without waiting for a cron tick. workflow_dispatch is inert everywhere else — the reconcile step already treats anything that is not a pull request the same way, so a manual run opens, updates or closes exactly as a scheduled one does. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- .github/workflows/cargo-audit.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml index cfc8b27..850a882 100644 --- a/.github/workflows/cargo-audit.yml +++ b/.github/workflows/cargo-audit.yml @@ -21,6 +21,9 @@ on: - '**/Cargo.lock' schedule: - cron: '0 6 * * 1' # Weekly on Monday + workflow_dispatch: # re-run by hand: after remediating a finding, to turn the + # issue green without waiting for the schedule, and to prove the reconcile + # path itself works permissions: contents: read jobs: @@ -64,4 +67,6 @@ jobs: --summary "$RUNNER_TEMP/cargo-audit.txt" \ --run "$RUN_URL" \ --event "${{ github.event_name }}" \ - --repo "${{ github.repository }}" + --repo "${{ github.repository }}" \ + --label security \ + --label dependencies From 41b0256bbba0fdf4506d16f1fb31254f1bcaa654 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:49:13 +0100 Subject: [PATCH 2/4] Update .github/workflows/cargo-audit.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> --- .github/workflows/cargo-audit.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml index 850a882..e9edea3 100644 --- a/.github/workflows/cargo-audit.yml +++ b/.github/workflows/cargo-audit.yml @@ -24,6 +24,9 @@ on: workflow_dispatch: # re-run by hand: after remediating a finding, to turn the # issue green without waiting for the schedule, and to prove the reconcile # path itself works +concurrency: + group: cargo-audit-${{ github.repository }} + cancel-in-progress: false permissions: contents: read jobs: From 2c851fa684a19164746d5f7bd2498c77844c6e3c Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:04:23 +0000 Subject: [PATCH 3/4] fix(ci): reconcile audit issues regardless of labels and restore missing labels Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- scripts/reconcile-audit-issue.sh | 25 +++++-- scripts/tests/reconcile-audit-issue-test.sh | 73 +++++++++++++++++++-- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/scripts/reconcile-audit-issue.sh b/scripts/reconcile-audit-issue.sh index d204dd3..6432478 100755 --- a/scripts/reconcile-audit-issue.sh +++ b/scripts/reconcile-audit-issue.sh @@ -92,8 +92,8 @@ label_args=() for label in "${LABELS[@]}"; do label_args+=(--label "$label"); done if [ "$DRY_RUN" -eq 0 ]; then - if ! gh issue list --repo "$REPO" --state open "${label_args[@]}" --limit 100 \ - --json number,title,body > "$TMP/open.json" 2> "$TMP/open.err"; then + if ! gh issue list --repo "$REPO" --state open --limit 100 \ + --json number,title,body,labels > "$TMP/open.json" 2> "$TMP/open.err"; then printf 'reconcile-audit-issue: cannot list issues: %s\n' "$(head -1 "$TMP/open.err")" >&2 exit 1 fi @@ -104,12 +104,23 @@ fi number="$(jq -r --arg m "$MARKER" '[.[] | select(.body | contains($m))][0].number // ""' "$TMP/open.json")" adopted="" if [ -z "$number" ]; then - # Adopt an issue created by the previous, markerless publisher: same title, - # same labels, no findings. Otherwise the old issue would never close. + # Adopt an issue created by the previous, markerless publisher by its title. + # Labels may have changed or been removed; the old issue must still close. number="$(jq -r --arg t "$TITLE" '[.[] | select(.title == $t)][0].number // ""' "$TMP/open.json")" [ -n "$number" ] && adopted=yes fi +# Repair missing labels on the matched issue in either audit path. +edit_label_args=() +if [ -n "$number" ]; then + for label in "${LABELS[@]}"; do + if ! jq -e --argjson n "$number" --arg l "$label" \ + '.[] | select(.number == $n) | any(.labels[]?; .name == $l)' "$TMP/open.json" >/dev/null; then + edit_label_args+=(--add-label "$label") + fi + done +fi + # ------------------------------------------------------------- body assembly findings="" if [ -n "$SUMMARY" ] && [ -f "$SUMMARY" ]; then @@ -165,7 +176,7 @@ if [ "$STATUS" -eq 0 ]; then # The body is rewritten as well, so a future failure finds this issue by its # marker rather than by its title, and the record of why it closed lives in # the issue rather than only in a comment. - gh_issue edit "$number" --repo "$REPO" --body-file "$TMP/body.md" + gh_issue edit "$number" --repo "$REPO" "${edit_label_args[@]}" --body-file "$TMP/body.md" gh_issue close "$number" --repo "$REPO" \ --comment "Verified fixed: $RUN_URL — \`cargo audit --deny warnings\` and \`cargo audit --deny unmaintained\` both exited 0 on a run of the default branch." [ "$FAILED" -eq 0 ] || exit 1 @@ -186,10 +197,10 @@ else | sed -n 's/.*.*/\1/p' | head -1)" if [ "$last_run" = "$RUN_URL" ]; then say "audit failed: #$number already records this run; updating the report only" - gh_issue edit "$number" --repo "$REPO" --body-file "$TMP/body.md" + gh_issue edit "$number" --repo "$REPO" "${edit_label_args[@]}" --body-file "$TMP/body.md" else say "audit failed: updating #$number with the new findings" - gh_issue edit "$number" --repo "$REPO" --body-file "$TMP/body.md" + gh_issue edit "$number" --repo "$REPO" "${edit_label_args[@]}" --body-file "$TMP/body.md" gh_issue comment "$number" --repo "$REPO" --body "New failing audit run: $RUN_URL" fi fi diff --git a/scripts/tests/reconcile-audit-issue-test.sh b/scripts/tests/reconcile-audit-issue-test.sh index cfd3171..00666b6 100755 --- a/scripts/tests/reconcile-audit-issue-test.sh +++ b/scripts/tests/reconcile-audit-issue-test.sh @@ -17,7 +17,15 @@ cat > "$scratch/bin/gh" <<'STUB' # copies any --body-file it is handed so the tests can inspect the report. printf '%s\n' "$*" >> "$GH_STUB_LOG" if [ "$1 $2" = "issue list" ]; then - cat "$GH_STUB_ISSUES" + issues="$(cat "$GH_STUB_ISSUES")" + prev="" + for arg in "$@"; do + if [ "$prev" = "--label" ]; then + issues="$(jq --arg l "$arg" '[.[] | select(any(.labels[]?; .name == $l))]' <<< "$issues")" + fi + prev="$arg" + done + printf '%s\n' "$issues" exit 0 fi prev="" @@ -40,8 +48,9 @@ RUN2="https://github.com/hyperpolymath/rpa-elysium/actions/runs/456" reset_log() { : > "$GH_STUB_LOG"; } run_reconcile() { # status event - bash "$SCRIPT" --status "$1" --event "$2" --run "$RUN" \ - --summary "$scratch/summary.txt" --repo hyperpolymath/rpa-elysium + local status="$1" event="$2"; shift 2 + bash "$SCRIPT" --status "$status" --event "$event" --run "$RUN" \ + --summary "$scratch/summary.txt" --repo hyperpolymath/rpa-elysium "$@" } expect_calls() { # description expected-substring... @@ -116,6 +125,8 @@ jq -n --arg m "$MARKER" --arg r "$RUN" \ reset_log run_reconcile 1 push > /dev/null expect_calls 'the same failing run does not duplicate the comment' 'issue list' 'issue edit 143' +grep -q -- '--add-label security --add-label dependencies' "$GH_STUB_LOG" || { + echo "FAIL: the same failing run did not restore missing labels"; exit 1; } # 7. Pull-request events never touch issues. printf '%s' '[]' > "$GH_STUB_ISSUES" @@ -148,4 +159,58 @@ grep -q 'cargo audit --deny warnings' "$WF" || { echo "FAIL: the workflow lost t grep -q 'exit "\$status"' "$WF" || { echo "FAIL: the audit step no longer propagates its failure"; exit 1; } echo "PASS: the workflow calls the reconciler and stays blocking" -echo '10 audit-issue reconciliation controls passed.' +# 11. Both audit outcomes find marked and legacy issues regardless of labels, +# adding only missing labels to the same issue without opening a duplicate. +for status in 0 1; do + for match in marked legacy; do + for labels in none security dependencies both; do + jq -n --arg m "$MARKER" --arg r "$RUN2" --arg match "$match" --arg labels "$labels" \ + '[{number: 143, + title: (if $match == "legacy" then "Security: Dependency vulnerabilities detected" else "Renamed audit issue" end), + body: (if $match == "marked" then $m + " " else "Legacy audit report" end), + labels: (if $labels == "both" then [{name: "security"}, {name: "dependencies"}] + elif $labels == "none" then [] else [{name: $labels}] end)}]' > "$GH_STUB_ISSUES" + reset_log + run_reconcile "$status" push > /dev/null + action='issue comment 143' + [ "$status" -ne 0 ] || action='issue close 143' + expect_calls "status=$status $match issue with $labels labels is reconciled" \ + 'issue list' 'issue edit 143' "$action" + if head -1 "$GH_STUB_LOG" | grep -q -- '--label'; then + echo "FAIL: issue lookup filters by labels"; exit 1 + fi + for label in security dependencies; do + if [ "$labels" = both ] || [ "$labels" = "$label" ]; then + if grep -q -- "--add-label $label" "$GH_STUB_LOG"; then + echo "FAIL: an existing $label label was added again"; exit 1 + fi + else + grep -q -- "--add-label $label" "$GH_STUB_LOG" || { + echo "FAIL: missing $label label was not restored"; exit 1; } + fi + done + done + done +done + +# 12. Explicit labels are restored on the selected marker match, even when a +# legacy title match appears first; unrelated existing labels are kept. +for status in 0 1; do + jq -n --arg m "$MARKER" --arg r "$RUN2" \ + '[{number: 113, title: "Security: Dependency vulnerabilities detected", body: "Legacy audit report", labels: []}, + {number: 143, title: "Renamed audit issue", body: ($m + " "), + labels: [{name: "security"}, {name: "dependencies"}]}]' > "$GH_STUB_ISSUES" + reset_log + run_reconcile "$status" push --label security --label triage > /dev/null + action='issue comment 143' + [ "$status" -ne 0 ] || action='issue close 143' + expect_calls "status=$status restores custom labels on the marker match" \ + 'issue list' 'issue edit 143' "$action" + grep -q -- '--add-label triage' "$GH_STUB_LOG" || { + echo "FAIL: missing custom label was not restored"; exit 1; } + if grep -Eq -- '--add-label (security|dependencies)|--remove-label' "$GH_STUB_LOG"; then + echo "FAIL: labels already on the selected issue were changed"; exit 1 + fi +done + +echo '12 audit-issue reconciliation controls passed.' From 30bb5712f8b07750bf43e59e3437c1df847be059 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:05:43 +0000 Subject: [PATCH 4/4] docs(ci): record the second cause of startup_failure with zero jobs A head commit created by the CodeRabbit app produced startup_failure with no jobs for all eight workflows while the workflow tree was byte-identical to a head whose workflows all ran to success. The playbook now says to check the run's actor before chasing YAML, and gives the command. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- docs/CI-CD-REPAIR.adoc | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/CI-CD-REPAIR.adoc b/docs/CI-CD-REPAIR.adoc index 2555e5e..da3b9fa 100644 --- a/docs/CI-CD-REPAIR.adoc +++ b/docs/CI-CD-REPAIR.adoc @@ -62,6 +62,35 @@ cannot go unrecorded again: link:CI-RED-DETERMINATIONS.adoc[CI red-check determinations] and link:audits/ci-check-ledger.json[the ledger] (triage issue #147). +== A red that is fiction, second mechanism: the push actor + +`startup_failure` with zero jobs is usually read as a workflow-file problem. It +has a second cause, measured on this repository on 2026-10-14: + +A pull request's head commit was created by the CodeRabbit app (its `Commit +suggestion` flow). GitHub created a run for every workflow on that head and +started none of them — `startup_failure`, no jobs, nothing to read — while the +`.github/workflows` tree at that commit was **byte-identical** to the preceding +head whose eight workflows all ran to success. Re-creating the same tree as a +commit authored by the repository owner started all eight normally. + +Evidence: head `626d9df` (`actor=coderabbitai[bot]`, eight runs, all +`startup_failure`, zero jobs) versus head `2c851fa` (same tree, `actor` the owner, +all runs started). `gh run rerun` refuses: "cannot be rerun; its workflow file +may be broken." + +So, before chasing YAML: if every workflow on one head failed to start and the +workflow tree is unchanged from a head that worked, check the run's actor + +[source,bash] +---- +gh api "repos/OWNER/REPO/actions/runs?head_sha=HEAD&per_page=10" \ + --jq '.workflow_runs[] | "\(.event) \(.actor.login) \(.conclusion)"' +---- + +and re-push the identical tree as a commit the repository's own identity +created. The repair is not to the workflows; it is to the head. + == Preventing recurrence `check-lock-sync.sh` reads YAML through mikefarah/yq v4 and compares structured