Skip to content

Open PRs Digest

Open PRs Digest #22

name: Open PRs Digest
on:
schedule:
- cron: '0 9 * * 1-5'
workflow_dispatch:
jobs:
open-prs:
runs-on: ubuntu-latest
timeout-minutes: 15
concurrency:
group: open-prs-digest
cancel-in-progress: true
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- name: Install and configure jira-cli
run: |
set -euo pipefail
JIRA_CLI_VERSION="1.5.2"
JIRA_CLI_SHA="a0b3dbfd1eb64e217a2a74a3f79179328b5debf61d6be8a3ed0d0014bd51512a"
JIRA_CLI_TAR="jira_${JIRA_CLI_VERSION}_linux_x86_64.tar.gz"
curl -sSfL "https://github.com/ankitpokhrel/jira-cli/releases/download/v${JIRA_CLI_VERSION}/${JIRA_CLI_TAR}" \
-o "/tmp/${JIRA_CLI_TAR}"
echo "${JIRA_CLI_SHA} /tmp/${JIRA_CLI_TAR}" | sha256sum -c -
tar xz -C /usr/local/bin --strip-components=2 -f "/tmp/${JIRA_CLI_TAR}" "jira_${JIRA_CLI_VERSION}_linux_x86_64/bin/jira"
chmod +x /usr/local/bin/jira
rm "/tmp/${JIRA_CLI_TAR}"
echo "/usr/local/bin" >> "$GITHUB_PATH"
mkdir -p ~/.config/.jira
cat > ~/.config/.jira/.config.yml << EOF
auth_type: basic
server: https://redhat.atlassian.net
login: ${JIRA_AUTH_LOGIN}
project:
key: HYPERFLEET
board:
id: 1013
type: scrum
installation: Cloud
EOF
env:
JIRA_AUTH_LOGIN: ${{ secrets.JIRA_AUTH_LOGIN }}
- name: Verify jira-cli setup
run: |
set -euo pipefail
echo "=== jira binary ==="
command -v jira && jira version
echo "=== config location ==="
ls -la ~/.config/.jira/.config.yml
echo "=== config content (redacted) ==="
sed 's/login:.*/login: [REDACTED]/' ~/.config/.jira/.config.yml
echo "=== test JIRA query ==="
jira issue view HYPERFLEET-1030 --raw 2>&1 | head -5 || echo "JIRA QUERY FAILED with exit code $?"
env:
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
- name: Authenticate to GCP
uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Resolve dynamic context in SKILL.md
run: |
set -euo pipefail
SKILL="./hyperfleet-work-triage/skills/open-prs/SKILL.md"
RESOLVED="${SKILL}.resolved"
CURRENT_DATE="$(date -u '+%Y-%m-%d %H:%M UTC')"
SKILL_DIR="./hyperfleet-work-triage/skills/open-prs"
awk -v dt="$CURRENT_DATE" -v skilldir="$SKILL_DIR" '
/^## Dynamic context/ {
print
print ""
print "- gh CLI: available"
print "- gh auth: authenticated"
print "- jira CLI: available"
print "- jq: available"
print "- Current date: " dt
skip=1; next
}
skip && /^## / { skip=0 }
skip { next }
{ gsub(/\$\{CLAUDE_SKILL_DIR\}/, skilldir); print }
' "$SKILL" > "$RESOLVED"
- name: Run /open-prs via Claude
uses: anthropics/claude-code-action@537ffff2eff706bd7e3e1c3daf2d4b39067a9f85 # v1
id: claude
with:
use_vertex: "true"
prompt: |
Run the /open-prs skill with --slack flag.
Follow SKILL.md instructions: run collect-data.sh, pipe through score.jq,
classify Factor 4 for each PR, then pipe through format-output.jq with --arg mode slack.
Output ONLY the final Slack mrkdwn content — no surrounding explanation or code blocks.
claude_args: >-
--append-system-prompt-file ./hyperfleet-work-triage/skills/open-prs/SKILL.md.resolved
--allowedTools Bash,Read
env:
ANTHROPIC_VERTEX_PROJECT_ID: ${{ secrets.ANTHROPIC_VERTEX_PROJECT_ID }}
CLOUD_ML_REGION: global
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN_ORG_READ }}
- name: Post to Slack
env:
OUTPUT_FILE: ${{ steps.claude.outputs.execution_file }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
if [ ! -f "$OUTPUT_FILE" ] || [ ! -s "$OUTPUT_FILE" ]; then
echo "::error::Claude output file is missing or empty"
exit 1
fi
CONTENT="$(jq -r 'map(select(.type == "result")) | last | .result // empty' "$OUTPUT_FILE" 2>/dev/null)"
if [ -z "$CONTENT" ]; then
CONTENT="$(jq -r '[.[] | select(.type == "assistant")] | last | .message.content | map(select(.type == "text")) | last | .text // empty' "$OUTPUT_FILE" 2>/dev/null)"
fi
if [ -z "$CONTENT" ]; then
echo "::error::Could not extract result from Claude output"
exit 1
fi
PAYLOAD="$(printf '%s' "$CONTENT" | sed '/^---$/d' | jq -Rs '
split("\n") as $lines |
reduce $lines[] as $line (
[{text: ""}];
if (.[-1].text | length) + ($line | length) + 1 > 2900 then
. + [{text: $line}]
else
.[-1].text += (if .[-1].text == "" then "" else "\n" end) + $line |
.
end
) |
{blocks: [.[] | select(.text != "") | {type: "section", text: {type: "mrkdwn", text: .text}}]}
')"
HTTP_CODE="$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "$PAYLOAD")"
if [ "$HTTP_CODE" -ne 200 ]; then
echo "::error::Slack webhook returned HTTP $HTTP_CODE"
exit 1
fi
echo "Posted to Slack successfully"
- name: Notify Slack on failure
if: failure()
env:
SLACK_WEBHOOK_URL_ERRORS: ${{ secrets.SLACK_WEBHOOK_URL_ERRORS }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
MESSAGE=$(printf '🔴 *Open PRs Digest failed*\n_%s_\n\nWorkflow run: %s' \
"$(date -u '+%Y-%m-%d %H:%M UTC')" "$RUN_URL")
HTTP_CODE="$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "$SLACK_WEBHOOK_URL_ERRORS" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg text "$MESSAGE" '{"text": $text}')")"
if [ "$HTTP_CODE" -ne 200 ]; then
echo "::error::Failure Slack webhook returned HTTP $HTTP_CODE"
exit 1
fi