From 8a35cbe1bcd2db4b5468cab88e2c5441d7dc23f2 Mon Sep 17 00:00:00 2001 From: Anvil Date: Thu, 30 Apr 2026 21:28:14 +0000 Subject: [PATCH] =?UTF-8?q?fix(mail):=20gh-as=20=E2=86=92=20gh=20fallback?= =?UTF-8?q?=20+=20jq=20array-shape=20+=20exec=20env=20(ops-9oye-followup)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three coupled fixes to make the CI-gate library actually work end-to-end on hosts without gh-as installed (notably tps-anvil where it ships): 1. gh-as → gh fallback: detect gh-as availability; fall back to plain gh when not installed. Same JSON shape from either tool; preserves per-agent attribution when gh-as is present. 2. Fix jq parse: `gh pr view --json statusCheckRollup` returns an ARRAY of check objects, not an object with a `.state` field. Original code `jq .statusCheckRollup.state` errored on every real invocation. Replaced with: "all conclusions == SUCCESS → SUCCESS, else PENDING_OR_FAILURE" + per-check failing-name diagnostics on refusal. 3. Fix exec line: `exec VAR=value cmd` is NOT bash variable assignment — bash treats `VAR=value` as the command name. Replaced with plain `exec bun run ...` since TPS_VAULT_KEY + TPS_AGENT_ID inherit from the calling shell's environment via exec automatically. Verified locally on tps-anvil (no gh-as installed): Test 1 (non-existent PR): gate correctly rejected (exit 1) Test 2 (PR #271, green): gate correctly allowed (exit 0, mail queued) Test 3 (non-DONE body): gate correctly passed (exit 0, mail queued) All tests passed! Together: the gate library shipped in PR #272 was non-functional at runtime on any host. CI on PR #272 didn't exercise the shell script test, so the bugs landed unnoticed. This PR makes the gate actually do what its spec says. --- scripts/mail-send-ci-gate.sh | 60 +++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/scripts/mail-send-ci-gate.sh b/scripts/mail-send-ci-gate.sh index a22c74f..7640f35 100755 --- a/scripts/mail-send-ci-gate.sh +++ b/scripts/mail-send-ci-gate.sh @@ -83,15 +83,18 @@ if echo "$BODY" | grep -q -E 'DONE[[:space:]]+https?://[^[:space:]]+'; then die "Could not extract repo path from URL: $PR_URL" fi - # Now run gh-as. - # We'll use the JSON output and check the statusCheckRollup. - # We'll look for any check that is not SUCCESS. - # We'll use jq to parse the JSON. - - # We'll run the command and capture the output. - # We'll set a timeout for the gh-as command. - CHECK_OUTPUT=$(gh-as "$AGENT_ID" pr view "$PR_NUMBER" --repo "$REPO_PATH" --json statusCheckRollup 2>/dev/null) || \ - die "Failed to fetch PR status for $PR_URL using gh-as" + # Prefer gh-as for per-agent attribution (so the check shows the + # invoking agent's identity in audit logs); fall back to plain gh on + # hosts where gh-as isn't installed (e.g. tps-anvil currently). + # Either tool returns the same JSON shape for `pr view --json statusCheckRollup`. + if command -v gh-as >/dev/null 2>&1; then + GH_CMD=(gh-as "$AGENT_ID") + else + echo "gh-as not found; falling back to gh (assuming current user)" >&2 + GH_CMD=(gh) + fi + CHECK_OUTPUT=$("${GH_CMD[@]}" pr view "$PR_NUMBER" --repo "$REPO_PATH" --json statusCheckRollup 2>/dev/null) || \ + die "Failed to fetch PR status for $PR_URL" # Now parse the JSON to see if all checks are SUCCESS. # We'll use jq to check the statusCheckRollup. We need to look at the checks array. @@ -111,17 +114,27 @@ if echo "$BODY" | grep -q -E 'DONE[[:space:]]+https?://[^[:space:]]+'; then # We'll also check if there are any pending checks? The spec says: if any check is FAILURE/PENDING, refuse. # So we should also reject if the state is PENDING. - # We'll extract the state from the statusCheckRollup. - STATE=$(echo "$CHECK_OUTPUT" | jq -r '.statusCheckRollup.state // empty') + # `gh pr view --json statusCheckRollup` returns an ARRAY of check + # objects ({name, conclusion, status, ...}), not an object with a + # top-level `state` field. Compute SUCCESS only when all conclusions + # are SUCCESS; refuse on any FAILURE / PENDING / SKIPPED / NEUTRAL / + # missing conclusion. + STATE=$(echo "$CHECK_OUTPUT" | jq -r ' + if (.statusCheckRollup | length) == 0 then "NO_CHECKS" + elif all(.statusCheckRollup[]; .conclusion == "SUCCESS") then "SUCCESS" + else "PENDING_OR_FAILURE" + end + ') if [ -z "$STATE" ]; then - die "Could not determine CI state for PR $PR_URL" + die "Could not parse CI state for PR $PR_URL" fi echo "CI state for PR $PR_URL: $STATE" >&2 if [ "$STATE" != "SUCCESS" ]; then - # Refuse to send. - echo "CI not green for PR $PR_URL (state: $STATE). Refusing to send DONE mail." >&2 + # Surface failing/pending check names for actionable diagnostics. + FAILING=$(echo "$CHECK_OUTPUT" | jq -r '[.statusCheckRollup[] | select(.conclusion != "SUCCESS") | (.name + ":" + (.conclusion // "PENDING"))] | join(", ")' 2>/dev/null || echo "(unparseable)") + echo "CI not green for PR $PR_URL (state: $STATE). Failing/pending: $FAILING. Refusing to send DONE mail." >&2 exit 1 fi @@ -133,17 +146,8 @@ else echo "No DONE PR URL found in mail body. Sending without CI check." >&2 fi -# If we reach here, we are allowed to send the mail. -# We'll send the mail using the TPS CLI. -# We'll use the TPS_VAULT_KEY and TPS_AGENT_ID from the environment. -# We'll run the command from the tps repo root. - -# We are already in the tps repo root. -# We'll use the bun command to run the TPS CLI. -# We'll send the mail. - -# We'll use the same environment variables that we have. -# We'll run: TPS_VAULT_KEY=$TPS_VAULT_KEY TPS_AGENT_ID=$TPS_AGENT_ID bun run packages/cli/dist/bin/tps.js mail send "$RECIPIENT" "$BODY" - -# We'll execute the command. -exec TPS_VAULT_KEY="$TPS_VAULT_KEY" TPS_AGENT_ID="$TPS_AGENT_ID" bun run packages/cli/dist/bin/tps.js mail send "$RECIPIENT" "$BODY" +# Gate passed (or no DONE PR detected). Send the mail via the TPS CLI. +# TPS_VAULT_KEY + TPS_AGENT_ID inherit from the caller's environment via exec — +# no need to re-export inline (the previous `exec VAR=val cmd` form is invalid +# bash: `exec` treats `VAR=val` as a command name, not an assignment). +exec bun run packages/cli/dist/bin/tps.js mail send "$RECIPIENT" "$BODY"