@plainconceptsplatform/workflows 1.6.10 → 1.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -132,6 +132,31 @@ runs:
132
132
  # both stalled on a human that way while CI was legitimately red and remediable.
133
133
  ci_conclusion="$CONCLUSION"
134
134
  ci_run_id="$CI_RUN_ID"
135
+
136
+ # A verdict from a CI run that judged an older head is not a verdict on this one. The
137
+ # router carries the run it saw, and the head can move before the gate starts: a repair
138
+ # pushed, a branch updated from main. Pliny-Bot #729 was blocked and sent to review on a
139
+ # red run of a commit that two later pushes had already fixed, and the gate's own report
140
+ # said so. When the run's commit is not the head, the carried verdict is dropped and read
141
+ # again from the head's own latest run; when that head has no finished run yet, there is
142
+ # nothing to judge, and the gate steps aside until CI finishes and the train hands the
143
+ # pull request back.
144
+ if [ -n "$ci_run_id" ]; then
145
+ run_sha="$(gh api "repos/$REPO/actions/runs/$ci_run_id" --jq '.head_sha' 2>/dev/null || true)"
146
+ current_sha="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')"
147
+ if [ -n "$run_sha" ] && [ "$run_sha" != "$current_sha" ]; then
148
+ echo "::notice::CI run $ci_run_id judged ${run_sha:0:7}, but the head is ${current_sha:0:7}; reading the verdict again from the head."
149
+ ci_conclusion=""
150
+ ci_run_id=""
151
+ fresh="$(gh api "repos/$REPO/actions/runs?head_sha=$current_sha&per_page=100" --jq '[.workflow_runs[] | select((.name | ascii_downcase) == "app: ci" or (.name | ascii_downcase) == "ci") | select(.status == "completed")] | sort_by(.created_at) | last')"
152
+ if [ -z "$fresh" ] || [ "$fresh" = "null" ]; then
153
+ none "CI has not finished on the current head of PR #$PR_NUMBER; the train hands it back when it has"
154
+ fi
155
+ ci_conclusion="$(jq -r '.conclusion // "pending"' <<<"$fresh")"
156
+ ci_run_id="$(jq -r '.id' <<<"$fresh")"
157
+ fi
158
+ fi
159
+
135
160
  if [ -z "$ci_conclusion" ] || [ -z "$ci_run_id" ]; then
136
161
  head_sha="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')"
137
162
  # A run keeps the name it had when it ran, so a repository whose CI was renamed still
@@ -38,6 +38,10 @@ set -euo pipefail
38
38
  # CI_STATUS none, or the latest CI run's status (queued, in_progress, completed, ...)
39
39
  # CI_CONCLUSION that run's conclusion when it completed
40
40
  # CI_STUCK true when that run has been pending longer than the train waits
41
+ # SAME_AS_MAIN true when every check that failed on the head also fails on the base branch
42
+ # tip: the failure is the base's, and repairing the pull request spends its
43
+ # attempts on code that is not at fault (Pliny-Bot, 2026-10-17: a red main
44
+ # cost every open pull request its repairs for seven hours)
41
45
  # BEHIND_BY commits on the base branch the head does not have
42
46
  # TRUSTED_AUTHOR true when a trusted bot opened it (only those runs are approved)
43
47
  train_move() {
@@ -70,7 +74,13 @@ train_move() {
70
74
  move=gate; reason="green and up to date"
71
75
  fi
72
76
  ;;
73
- failure) move=gate; reason="CI failed" ;;
77
+ failure)
78
+ if [ "${SAME_AS_MAIN:-false}" = true ]; then
79
+ move="wait"; reason="CI failed, but only on checks that fail on the base branch too; waiting for the base to be fixed"
80
+ else
81
+ move=gate; reason="CI failed"
82
+ fi
83
+ ;;
74
84
  action_required)
75
85
  if [ "${TRUSTED_AUTHOR:-false}" = true ]; then
76
86
  move=approve; reason="CI is waiting for approval"
@@ -403,12 +413,28 @@ for line in "${candidates[@]}"; do
403
413
  fi
404
414
  fi
405
415
 
416
+ # What failed on the head, compared with what is failing on the base branch tip. Names are
417
+ # compared after the last " / ": a check that a reusable workflow runs is "CI / backend" on the
418
+ # base branch and "backend" on a pull request. Only when every failed check is also failing on
419
+ # the base is the pull request left alone.
420
+ same_as_main=false
421
+ if [ "$ci_status" = completed ] && [ "$ci_conclusion" = failure ] && [ "$park" = none ]; then
422
+ failed_here=$(gh api "repos/$REPO/commits/$head_sha/check-runs?per_page=100" \
423
+ --jq '[.check_runs[] | select(.conclusion == "failure") | (.name | sub("^.* / "; ""))] | unique | .[]' 2>/dev/null || true)
424
+ failed_base=$(gh api "repos/$REPO/commits/$base_ref/check-runs?per_page=100" \
425
+ --jq '[.check_runs[] | select(.conclusion == "failure") | (.name | sub("^.* / "; ""))] | unique | .[]' 2>/dev/null || true)
426
+ if [ -n "$failed_here" ] && [ -n "$failed_base" ] &&
427
+ [ -z "$(comm -23 <(sort <<<"$failed_here") <(sort <<<"$failed_base"))" ]; then
428
+ same_as_main=true
429
+ fi
430
+ fi
431
+
406
432
  trusted_author=false
407
433
  is_trusted_bot "$author" && trusted_author=true
408
434
 
409
435
  decision=$(HOLD="$hold" REVIEW_DECISION="$review_decision" PARK="$park" \
410
436
  ATTEMPTS="$attempts" MAX_ATTEMPTS="$MAX_ATTEMPTS" SILENT_GATE="$silent_gate" MERGEABLE="$mergeable" \
411
- CI_STATUS="$ci_status" CI_CONCLUSION="$ci_conclusion" CI_STUCK="$ci_stuck" \
437
+ CI_STATUS="$ci_status" CI_CONCLUSION="$ci_conclusion" CI_STUCK="$ci_stuck" SAME_AS_MAIN="$same_as_main" \
412
438
  BEHIND_BY="${behind_by:-0}" TRUSTED_AUTHOR="$trusted_author" train_move)
413
439
  move=$(sed -n 's/^move=//p' <<<"$decision")
414
440
  reason=$(sed -n 's/^reason=//p' <<<"$decision")
@@ -1979,6 +1979,11 @@ if worker_installed merge-gate; then
1979
1979
  assert "running CI is waited on" wait "$(move MERGEABLE=MERGEABLE CI_STATUS=in_progress)"
1980
1980
  assert "CI stuck past the limit is passed by" skip "$(move MERGEABLE=MERGEABLE CI_STATUS=queued CI_STUCK=true)"
1981
1981
  assert "red CI goes to the gate" gate "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=failure)"
1982
+ # A red base branch cost every open Pliny-Bot pull request its repairs for seven hours: the
1983
+ # failures were main's, and the gate spent attempts on code that was not at fault.
1984
+ assert "a failure that is main's too is waited out, not repaired" wait "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=true)"
1985
+ assert "a failure that is not main's still goes to the gate" gate "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=false)"
1986
+ assert "a conflict is still the gate's even when main is red" gate "$(move MERGEABLE=CONFLICTING CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=true)"
1982
1987
  assert "green but behind is brought up to date first" update "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=success BEHIND_BY=2)"
1983
1988
  assert "green and current goes to the gate" gate "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=success BEHIND_BY=0)"
1984
1989
  assert "no CI run is passed by" skip "$(move MERGEABLE=MERGEABLE CI_STATUS=none)"
@@ -2758,6 +2763,11 @@ if worker_installed merge-gate; then
2758
2763
  GATE_RUN_ID_OK=0
2759
2764
  echo "FAIL: identify-gate-subject resolves the CI run only when the conclusion is unknown; a router that passes both gets an empty run ID" >&2
2760
2765
  fi
2766
+ # A verdict from a run that judged an older head is not a verdict on this one (Pliny-Bot #729).
2767
+ grep -Fq '"$run_sha" != "$current_sha"' "$GATE_SUBJECT_ACTION" ||
2768
+ { GATE_RUN_ID_OK=0; echo "FAIL: identify-gate-subject trusts the carried CI run even when it judged an older head" >&2; }
2769
+ grep -Fq 'select(.status == "completed")' "$GATE_SUBJECT_ACTION" ||
2770
+ { GATE_RUN_ID_OK=0; echo "FAIL: identify-gate-subject re-reads a stale verdict without requiring a finished run on the head" >&2; }
2761
2771
  fi
2762
2772
  # Repeating a report the validator refused reproduces it, and every repeat is a fresh agent run
2763
2773
  # holding the repo-wide merge-belt slot. The worker gives that failure a smaller budget than a
@@ -594,6 +594,7 @@ jobs:
594
594
  body: |
595
595
  ${{ env.IMPLEMENT_MARKER }}
596
596
  ${{ env.INCOMPLETE_COMMENT }}
597
+ What ended: the agent job was **${{ needs.agent.result }}** after ${{ steps.decide.outputs.minutes }} minutes (-1 when that could not be read), and publishing its output was **${{ needs.safe_outputs.result }}**.
597
598
  [View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
598
599
  agent:
599
600
  needs: [eligibility]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainconceptsplatform/workflows",
3
- "version": "1.6.10",
3
+ "version": "1.6.11",
4
4
  "description": "Install and update Platform GitHub agentic workflows.",
5
5
  "keywords": [
6
6
  "github-actions",