Skip to content

HYPERFLEET-1637: update to Claude Haiku 4.5 and add debug output #94

HYPERFLEET-1637: update to Claude Haiku 4.5 and add debug output

HYPERFLEET-1637: update to Claude Haiku 4.5 and add debug output #94

name: Open PRs Digest
on:
schedule:
- cron: '0 9 * * 1-5'
workflow_dispatch:
jobs:
open-prs:
if: github.repository == 'openshift-hyperfleet/hyperfleet-claude-plugins'
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@428971d2ecd6e3a7cb0ee0da2a3a8b33fdb3678d # v1
id: claude
with:

Check failure on line 98 in .github/workflows/open-prs-digest.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/open-prs-digest.yml

Invalid workflow file

You have an error in your yaml syntax on line 98
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,Glob,Grep --model claude-haiku-4-5@20251001"
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: Check Claude execution result
run: |
set -euo pipefail
OUTPUT_FILE="${{ steps.claude.outputs.execution_file }}"
echo "=== Claude Execution Output ==="
IS_ERROR="$(jq -r 'map(select(.type == "result")) | last | .is_error // false' "$OUTPUT_FILE" 2>/dev/null || echo 'unknown')"
MODEL_USED="$(jq -r 'map(select(.type == "system")) | last | .model // "unknown"' "$OUTPUT_FILE" 2>/dev/null || echo 'unknown')"
ERROR_MSG="$(jq -r 'map(select(.type == "assistant")) | last | .message.content[0].text // empty' "$OUTPUT_FILE" 2>/dev/null || echo '')"
echo "Is Error: $IS_ERROR"
echo "Model Used: $MODEL_USED"
if [ ! -z "$ERROR_MSG" ]; then
echo "Error Message: $ERROR_MSG"
fi
if [ "$IS_ERROR" = "true" ]; then
echo "::error::Claude execution failed. Check error message above"
exit 1
fi
- name: Post to Slack
env:
OUTPUT_FILE: ${{ steps.claude.outputs.execution_file }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
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
if [ ! -f "$OUTPUT_FILE" ] || [ ! -s "$OUTPUT_FILE" ]; then
echo "::error::Claude output file is missing or empty"
exit 1
fi
IS_ERROR="$(jq -r 'map(select(.type == "result")) | last | .is_error // false' "$OUTPUT_FILE" 2>/dev/null)"
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
# Detect error results: is_error flag, or missing expected Slack format markers
if [ "$IS_ERROR" = "true" ] || ! printf '%s' "$CONTENT" | grep -q 'Open PRs'; then
echo "::warning::Claude produced an error or unexpected output — routing to errors channel"
MESSAGE=$(printf '🔴 *Open PRs Digest — unexpected output*\n_%s_\n\n```\n%.500s\n```\n\nWorkflow run: %s' \
"$(date -u '+%Y-%m-%d %H:%M UTC')" "$CONTENT" "$RUN_URL")
HTTP_CODE="$(curl --connect-timeout 10 --max-time 30 --retry 2 --retry-delay 2 --retry-connrefused -sS -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::Error Slack webhook returned HTTP $HTTP_CODE"
fi
exit 1
fi
# Strip any LLM reasoning text before the actual Slack mrkdwn content.
# The formatted output always starts with an emoji header matching format-output.jq:
# 🚨 (Tier 1), 🟡 (Tier 2), 🟢 (Tier 3 / no PRs), 🔴 (all Tier 4), 🔵 (Tier 4 items).
CONTENT="$(printf '%s' "$CONTENT" | sed -n '/^[🟡🚨🟢🔵🔴]/,$p')"
if [ -z "$CONTENT" ]; then
echo "::error::Could not find Slack mrkdwn markers in 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