Skip to content

Remove debug failed CI PR comment workflow #2

Remove debug failed CI PR comment workflow

Remove debug failed CI PR comment workflow #2

name: AI agents in CI
on:
workflow_call:
inputs:
event_name:
description: "Event name from the caller workflow"
required: true
type: string
event_payload:
description: "JSON payload captured from the caller event"
required: true
type: string
repository:
description: "Full repository name of the caller (owner/repo)"
required: true
type: string
ref:
description: "Git ref from the caller"
required: true
type: string
sha:
description: "Git SHA from the caller"
required: true
type: string
actor:
description: "Actor that triggered the caller workflow"
required: true
type: string
failed_run_id:
description: "Workflow run ID to debug when event_name is workflow_run"
required: false
type: number
default: 0
team:
description: "Team name"
required: false
type: string
debug_prompt:
description: "Detailed prompt for the debug ci failures jobs"
required: false
type: string
debugging_model:
description: "LLM model to use for debugging failed CI runs"
required: false
type: string
generic_model:
description: "LLM model to use for general purpose tasks"
required: false
type: string
jobs:
work-on-issues-prs:
if: |
inputs.event_name == 'issues' ||
inputs.event_name == 'issue_comment' ||
inputs.event_name == 'pull_request_review_comment' ||
inputs.event_name == 'pull_request_review'
runs-on: gha-production-medium
container: ci-images-release.arti.tw.ee/actions_java_17_and_21
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: "Add repo as safe directory"
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Sync caller event context
shell: bash
env:
CALLER_EVENT_PAYLOAD: ${{ inputs.event_payload }}
run: |
event_file="$RUNNER_TEMP/original_event.json"
printf '%s' "$CALLER_EVENT_PAYLOAD" > "$event_file"
{
echo "GITHUB_EVENT_PATH=$event_file"
echo "GITHUB_EVENT_NAME=${{ inputs.event_name }}"
echo "GITHUB_REPOSITORY=${{ inputs.repository }}"
echo "GITHUB_REF=${{ inputs.ref }}"
echo "GITHUB_SHA=${{ inputs.sha }}"
echo "GITHUB_ACTOR=${{ inputs.actor }}"
} >> "$GITHUB_ENV"
- name: Run AI Agents for Issues and PRs
uses: transferwise/claude-code-action@main
with:
trigger_phrase: "/run-ci-ai-agents"
anthropic_bedrock_base_url: ${{ secrets.ANTHROPIC_BEDROCK_BASE_URL }}
anthropic_default_sonnet_model: ${{ vars.ANTHROPIC_DEFAULT_SONNET_MODEL }}
anthropic_default_haiku_model: ${{ vars.ANTHROPIC_DEFAULT_HAIKU_MODEL }}
anthropic_default_opus_model: ${{ vars.ANTHROPIC_DEFAULT_OPUS_MODEL }}
x-wise-llm-gateway-team: ${{ inputs.team }}
x-wise-llm-gateway-lite-auth-caller-id: ${{ inputs.actor }}
experimental_allowed_domains: |
github.com
api.github.com
${{ secrets.ANTHROPIC_BEDROCK_BASE_URL }}
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment"
--model ${{ inputs.generic_model != '' && inputs.generic_model || vars.ANTHROPIC_DEFAULT_HAIKU_MODEL }}
debug-failed-ci-runs:
if: inputs.event_name == 'workflow_run'
runs-on: gha-production-medium
container: ci-images-release.arti.tw.ee/actions_java_17_and_21
permissions:
contents: read
actions: read
pull-requests: write
id-token: write # Required for OIDC token exchange
steps:
- name: Get CI failure details
id: failure_details
uses: actions/github-script@v7
with:
script: |
const zlib = require('zlib');
const runId = ${{ inputs.failed_run_id }};
const run = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: runId,
});
const jobs = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: runId,
});
const failedJobs = jobs.data.jobs.filter(job => job.conclusion === 'failure');
let errorLogs = [];
for (const job of failedJobs) {
const logs = await github.rest.actions.downloadJobLogsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
job_id: job.id
});
let logText;
try {
const toBuffer = (value) => {
if (Buffer.isBuffer(value)) {
return value;
}
if (value instanceof Uint8Array) {
return Buffer.from(value);
}
if (ArrayBuffer.isView?.(value)) {
return Buffer.from(value.buffer);
}
if (value instanceof ArrayBuffer) {
return Buffer.from(value);
}
return null;
};
const bufferData = toBuffer(logs.data);
if (bufferData) {
try {
logText = zlib.gunzipSync(bufferData).toString('utf8');
} catch (gzipError) {
logText = bufferData.toString('utf8');
}
} else if (typeof logs.data === 'string') {
logText = logs.data;
} else {
logText = JSON.stringify(logs.data);
}
} catch (error) {
logText = `Failed to unzip logs: ${error.message}`;
}
const errorLines = logText
.split(/\r?\n/)
.filter(line => /^(\d{4}-\d{2}-\d{2}T).*##\[error\]/.test(line));
const filteredLog = errorLines.length > 0
? errorLines.join("\n")
: "No lines containing ##[error] were found in the job logs.";
errorLogs.push({
jobName: job.name,
logs: filteredLog,
});
}
let prNumber = run.data.pull_requests?.[0]?.number;
if (!prNumber && run.data.head_branch) {
const prs = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${run.data.head_branch}`,
});
if (prs.data.length > 0) {
prNumber = prs.data[0].number;
}
}
return {
runUrl: run.data.html_url,
workflowName: run.data.name,
failedJobs: failedJobs.map(j => j.name),
errorLogs: errorLogs,
headBranch: run.data.head_branch,
headSha: run.data.head_sha,
prNumber: prNumber ?? null,
};
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{ fromJSON(steps.failure_details.outputs.result).headBranch }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: "Add repo as safe directory"
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Create placeholder PR comment
if: ${{ fromJSON(steps.failure_details.outputs.result).prNumber != null }}
id: create_ci_debug_comment
uses: actions/github-script@v7
with:
script: |
const failureDetails = ${{ steps.failure_details.outputs.result }};
const body = `## 🤖 CI Failure Analysis
> Analysing failed workflow run...

Check failure on line 248 in .github/workflows/reusable-workflow-ci-ai-agents.yaml

View workflow run for this annotation

GitHub Actions / .github/workflows/reusable-workflow-ci-ai-agents.yaml

Invalid workflow file

You have an error in your yaml syntax on line 248
🔗 [View Failed Run](${failureDetails.runUrl})
_Analysis in progress..._`;
const { data: comment } = await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: failureDetails.prNumber,
body,
});
core.setOutput('comment_id', comment.id);
return comment.id;
- name: Resolve debug prompt
id: resolve_prompt
uses: actions/github-script@v7
env:
DEBUG_PROMPT: ${{ inputs.debug_prompt }}
FAILURE_DETAILS: ${{ steps.failure_details.outputs.result }}
COMMENT_ID: ${{ steps.create_ci_debug_comment.outputs.comment_id }}
with:
script: |
const debugPrompt = process.env.DEBUG_PROMPT;
const failureDetails = JSON.parse(process.env.FAILURE_DETAILS || '{}');
const commentId = process.env.COMMENT_ID;
const failedJobs = (failureDetails.failedJobs || []).join(', ');
const errorLogs = JSON.stringify(failureDetails.errorLogs || [], null, 2);
const basePrompt = debugPrompt && debugPrompt.trim()
? debugPrompt.trim()
: `Analyse the logs of the failed job and explain why it failed.
Summarise the root cause and provide specific recommendations to unblock engineers.
Do not modify repository files or run git commands.
Failed CI Run: ${failureDetails.runUrl}
Failed Jobs: ${failedJobs || 'None captured'}
PR Number: ${failureDetails.prNumber ?? 'N/A'}
Base Branch: ${failureDetails.headBranch}
Repository: ${{ github.repository }}
Error logs:
${errorLogs}`;
const commentInstructions = commentId
? `IMPORTANT: Update the existing PR comment (comment ID: ${commentId}) with your analysis.
Use the GitHub CLI to overwrite the comment body by running:
gh api repos/${{ github.repository }}/issues/comments/${commentId} -X PATCH -f body='YOUR_MARKDOWN_ANALYSIS'
Format the analysis in Markdown with clear sections for:
- 🔴 Failed Jobs: list the failed jobs.
- 📋 Root Cause Analysis: explain why the failure happened.
- 💡 Recommendations: actionable steps to unblock engineers.
- 🔗 Links: include a link to ${failureDetails.runUrl}.
`
: `Append your findings to the GitHub Actions step summary by writing to the file referenced in the $GITHUB_STEP_SUMMARY environment variable (for example by running: printf '...' >> "$GITHUB_STEP_SUMMARY").`;
return `${basePrompt}
${commentInstructions}`;
- name: Run AI Agent to debug failed CI run
uses: transferwise/claude-code-action@main
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
anthropic_bedrock_base_url: ${{ secrets.ANTHROPIC_BEDROCK_BASE_URL }}
anthropic_default_sonnet_model: ${{ vars.ANTHROPIC_DEFAULT_SONNET_MODEL }}
anthropic_default_haiku_model: ${{ vars.ANTHROPIC_DEFAULT_HAIKU_MODEL }}
anthropic_default_opus_model: ${{ vars.ANTHROPIC_DEFAULT_OPUS_MODEL }}
x-wise-llm-gateway-team: ${{ inputs.team }}
x-wise-llm-gateway-lite-auth-caller-id: ${{ github.actor }}
github_token: ${{ secrets.GITHUB_TOKEN }}
prompt: ${{ steps.resolve_prompt.outputs.result }}
experimental_allowed_domains: |
github.com
api.github.com
${{ secrets.ANTHROPIC_BEDROCK_BASE_URL }}
claude_args: |
--model ${{ vars.ANTHROPIC_DEFAULT_HAIKU_MODEL }}
--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash,Bash(gh api*)'