From prow-job
Analyze failed Prow CI tests by inspecting test code, downloading artifacts, and optionally integrating must-gather cluster diagnostics for root cause analysis
How this skill is triggered — by the user, by Claude, or both
Slash command
/prow-job:prow-job-analyze-test-failureThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill analyzes test failures by downloading Prow CI artifacts, checking test logs, inspecting resources and events,
This skill analyzes test failures by downloading Prow CI artifacts, checking test logs, inspecting resources and events, analyzing test source code, and optionally integrating cluster diagnostics from must-gather data.
Identical with "Prow Job Analyze Resource" skill.
The user will provide:
Prow job URL - gcsweb URL containing test-platform-results/
https://gcsweb-ci.apps.ci.l2s4.p1.openshiftapps.com/gcs/test-platform-results/pr-logs/pull/openshift_hypershift/6731/pull-ci-openshift-hypershift-main-e2e-aws/1962527613477982208Test name - test name that failed
TestKarpenter/EnsureHostedCluster/ValidateMetricsAreExposedTestCreateClusterCustomConfigThe openshift-console downloads pods [apigroup:console.openshift.io] should be scheduled on different nodesOptional flags (optional):
--fast - Skip must-gather extraction and analysis (test-level analysis only)Use the "Parse and Validate URL" steps from "Prow Job Analyze Resource" skill
Check for existing artifacts first
.work/prow-job-analyze-test-failure/{build_id}/logs/ directory exists and has contentrm -rf .work/prow-job-analyze-test-failure/{build_id}/logs/rm -rf .work/prow-job-analyze-test-failure/{build_id}/tmp/Create directory structure
mkdir -p .work/prow-job-analyze-test-failure/{build_id}/logs
mkdir -p .work/prow-job-analyze-test-failure/{build_id}/tmp
.work/prow-job-analyze-test-failure/ as the base directory (already in .gitignore)logs/ subdirectory for all downloadstmp/ subdirectory for temporary files (intermediate JSON, etc.).work/prow-job-analyze-test-failure/{build_id}/Use the "Download and Validate prowjob.json" steps from "Prow Job Analyze Resource" skill.
gcloud storage cp gs://test-platform-results/{bucket-path}/build-log.txt .work/prow-job-analyze-test-failure/{build_id}/logs/build-log.txt --no-user-output-enabled
.work/prow-job-analyze-test-failure/{build_id}/logs/build-log.txtgcloud storage ls 'gs://test-platform-results/{bucket-path}/**/e2e-timelines_spyglass_*json'
gcloud storage cp gs://test-platform-results/{bucket-path}/**/e2e-timelines_spyglass_*.json .work/prow-job-analyze-test-failure/{build_id}/logs/ --no-user-output-enabled
source = "E2ETest" and message.annotations.status = "Failed"from and to timestamps on this interval - this indicates when the test was runninglevel = "Error" or level = "Warning"source = "OperatorState".work/prow-job-analyze-test-failure/{build_id}/tmp.work/prow-job-analyze-test-failure/{build_id}/logs/Check for --fast flag
--fast flag--fast flag present:
Extract actual test name from prowjob.json
The artifacts directory uses the test name from prowjob.json, NOT the full URL path.
# Extract test name from prowjob.json (e.g., "e2e-aws-operator-serial-ote")
TEST_NAME=$(jq -r '.spec.job' .work/prow-job-analyze-test-failure/{build_id}/prowjob.json)
# Note: For PR jobs, TARGET contains the full PR path like:
# pr-logs/pull/openshift_service-ca-operator/306/pull-ci-openshift-service-ca-operator-main-e2e-aws-operator-serial-ote
# But artifacts are stored under just the test name:
# e2e-aws-operator-serial-ote
Detect must-gather archive (only if --fast not present)
Use TEST_NAME (not TARGET) for artifact paths.
HyperShift jobs may have different must-gather patterns:
Pattern 1: Unified Archive (dump-management-cluster)
Pattern 2: Dual Archives (gather-must-gather + dump)
Pattern 3: Standard Only (gather-must-gather)
Detection logic:
# Check for Pattern 1: Unified archive (dump-management-cluster)
UNIFIED_DUMP=$(gcloud storage ls "gs://test-platform-results/{bucket-path}/artifacts/$TEST_NAME/dump-management-cluster/artifacts/artifacts.tar*" 2>/dev/null | head -1 || true)
# Check for Pattern 2/3: Standard must-gather
STANDARD_MG=$(gcloud storage ls "gs://test-platform-results/{bucket-path}/artifacts/$TEST_NAME/gather-must-gather/artifacts/must-gather.tar" 2>/dev/null || true)
# Check for Pattern 2: Additional hypershift-dump (multiple possible locations)
# Use wildcards to match all current and future HyperShift dump patterns:
# 1. **/artifacts/hypershift-dump.tar (covers dump/, hypershift-mce-dump/, etc.)
# 2. **/artifacts/**/hostedcluster.tar (covers all E2E test patterns)
HYPERSHIFT_DUMP=""
for pattern in \
"**/artifacts/hypershift-dump.tar" \
"**/artifacts/**/hostedcluster.tar"; do
FOUND=$(gcloud storage ls "gs://test-platform-results/{bucket-path}/artifacts/$TEST_NAME/$pattern" 2>/dev/null | head -1 || true)
if [ -n "$FOUND" ]; then
HYPERSHIFT_DUMP="$FOUND"
break
fi
done
# Determine pattern and check for hosted cluster data
if [ -n "$UNIFIED_DUMP" ]; then
# Pattern 1: Unified archive
PATTERN="unified"
# Download temporarily to check for hosted cluster data
TMP_CHECK="/tmp/check-unified-$$.tar"
gcloud storage cp "$UNIFIED_DUMP" "$TMP_CHECK" --no-user-output-enabled
# Check if archive contains hostedcluster-* directory
HAS_HOSTED_CLUSTER=$(tar -tf "$TMP_CHECK" 2>/dev/null | grep -q "hostedcluster-" && echo "true" || echo "false")
rm -f "$TMP_CHECK"
elif [ -n "$STANDARD_MG" ] && [ -n "$HYPERSHIFT_DUMP" ]; then
# Pattern 2: Dual archives
PATTERN="dual"
# Download hypershift-dump temporarily to check for hosted cluster
TMP_CHECK="/tmp/check-dump-$$.tar"
gcloud storage cp "$HYPERSHIFT_DUMP" "$TMP_CHECK" --no-user-output-enabled
# Check if hypershift-dump contains hostedcluster-* directory
HAS_HOSTED_CLUSTER=$(tar -tf "$TMP_CHECK" 2>/dev/null | grep -q "hostedcluster-" && echo "true" || echo "false")
rm -f "$TMP_CHECK"
elif [ -n "$STANDARD_MG" ]; then
# Pattern 3: Standard must-gather only
PATTERN="standard"
HAS_HOSTED_CLUSTER=false
else
# No must-gather found
PATTERN="none"
HAS_HOSTED_CLUSTER=false
fi
Possible outcomes:
Important Pattern Notes:
logs/artifacts/output/, hosted at logs/artifacts/output/hostedcluster-{name}/Ask user if they want must-gather analysis
Only if user chose "Yes" in Step 4.5:
Determine extraction strategy
must-gather/logs/must-gather-mgmt/logs/must-gather-hosted/logs/Check for existing extraction
For single must-gather:
.work/prow-job-analyze-test-failure/{build_id}/must-gather/logs/ exists with contentFor dual must-gather (HyperShift):
.work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs/ exists with content.work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/logs/ exists with contentIf either exists:
rm -rf .work/prow-job-analyze-test-failure/{build_id}/must-gather*/Create must-gather directories
Based on PATTERN from Step 4.5.3:
For Pattern 3 (standard only):
mkdir -p .work/prow-job-analyze-test-failure/{build_id}/must-gather/logs
mkdir -p .work/prow-job-analyze-test-failure/{build_id}/must-gather/tmp
For Pattern 1 (unified) or Pattern 2 (dual):
mkdir -p .work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs
mkdir -p .work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/tmp
mkdir -p .work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/logs
mkdir -p .work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/tmp
Download must-gather archives
Use TEST_NAME (from Step 4.5.2) for artifact paths, not {target}:
For Pattern 3 (standard only):
gcloud storage cp "$STANDARD_MG" \
.work/prow-job-analyze-test-failure/{build_id}/must-gather/tmp/must-gather.tar \
--no-user-output-enabled
For Pattern 1 (unified):
# Download unified archive (contains both management and hosted cluster data)
gcloud storage cp "$UNIFIED_DUMP" \
.work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/tmp/unified-dump.tar \
--no-user-output-enabled
For Pattern 2 (dual):
# Download management cluster must-gather
gcloud storage cp "$STANDARD_MG" \
.work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/tmp/must-gather.tar \
--no-user-output-enabled
# Download hypershift-dump (may or may not contain hosted cluster)
gcloud storage cp "$HYPERSHIFT_DUMP" \
.work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/tmp/hypershift-dump.tar \
--no-user-output-enabled
Extract archives
For Pattern 3 (standard only):
# Use existing extract_archives.py script for standard must-gather
python3 plugins/prow-job/skills/prow-job-extract-must-gather/extract_archives.py \
.work/prow-job-analyze-test-failure/{build_id}/must-gather/tmp/must-gather.tar \
.work/prow-job-analyze-test-failure/{build_id}/must-gather/logs
For Pattern 1 (unified):
# Extract unified archive to temporary location
TMP_EXTRACT=".work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/tmp/extracted"
mkdir -p "$TMP_EXTRACT"
# Handle both .tar and .tar.gz
if [[ "$UNIFIED_DUMP" == *.tar.gz ]]; then
tar -xzf .work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/tmp/unified-dump.tar -C "$TMP_EXTRACT"
else
tar -xf .work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/tmp/unified-dump.tar -C "$TMP_EXTRACT"
fi
# Find the output directory (may be at logs/artifacts/output or just output)
OUTPUT_DIR=$(find "$TMP_EXTRACT" -type d -name "output" | head -1)
if [ -z "$OUTPUT_DIR" ]; then
echo "ERROR: Could not find output directory in unified dump"
# Skip to Step 5
fi
# Move management cluster data (root level in output/)
# Exclude hostedcluster-* directories
for item in "$OUTPUT_DIR"/*; do
if [ -e "$item" ] && [[ ! "$(basename "$item")" =~ ^hostedcluster- ]]; then
mv "$item" .work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs/
fi
done
# Move hosted cluster data (hostedcluster-* subdirectory)
if [ "$HAS_HOSTED_CLUSTER" = "true" ]; then
HOSTED_DIR=$(find "$OUTPUT_DIR" -maxdepth 1 -type d -name "hostedcluster-*" | head -1)
if [ -n "$HOSTED_DIR" ]; then
mv "$HOSTED_DIR"/* .work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/logs/
echo "✓ Hosted cluster data extracted from unified archive"
else
echo "WARNING: Expected hosted cluster data but hostedcluster-* directory not found"
fi
fi
# Cleanup temporary extraction directory
rm -rf "$TMP_EXTRACT"
For Pattern 2 (dual):
# Extract management cluster must-gather (standard format)
python3 plugins/prow-job/skills/prow-job-extract-must-gather/extract_archives.py \
.work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/tmp/must-gather.tar \
.work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs
# Extract hypershift-dump (may contain hosted cluster)
TMP_EXTRACT=".work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/tmp/extracted"
mkdir -p "$TMP_EXTRACT"
tar -xf .work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/tmp/hypershift-dump.tar -C "$TMP_EXTRACT"
if [ "$HAS_HOSTED_CLUSTER" = "true" ]; then
# Look for hostedcluster-* directory in dump
HOSTED_DIR=$(find "$TMP_EXTRACT" -maxdepth 2 -type d -name "hostedcluster-*" | head -1)
if [ -n "$HOSTED_DIR" ]; then
mv "$HOSTED_DIR"/* .work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/logs/
echo "✓ Hosted cluster data extracted from hypershift-dump"
else
echo "WARNING: HAS_HOSTED_CLUSTER=true but no hostedcluster-* directory found in dump"
echo "Hypershift-dump likely contains only management cluster data"
HAS_HOSTED_CLUSTER=false # Update flag since hosted cluster not actually present
fi
else
echo "INFO: Hypershift-dump does not contain hosted cluster data (management cluster only)"
fi
# Cleanup temporary extraction directory
rm -rf "$TMP_EXTRACT"
Locate and validate content directories
For Pattern 3 (standard only):
# Check for content/ directory first (renamed by extraction script)
if [ -d ".work/prow-job-analyze-test-failure/{build_id}/must-gather/logs/content" ]; then
MUST_GATHER_PATH=".work/prow-job-analyze-test-failure/{build_id}/must-gather/logs/content"
else
# Fall back to finding the directory containing -ci- (e.g., registry-build09-ci-...)
MUST_GATHER_PATH=$(find .work/prow-job-analyze-test-failure/{build_id}/must-gather/logs -maxdepth 1 -type d -name "*-ci-*" | head -1)
fi
# Validate MUST_GATHER_PATH is set and directory exists
if [ -z "$MUST_GATHER_PATH" ] || [ ! -d "$MUST_GATHER_PATH" ]; then
echo "ERROR: Must-gather content directory not found after extraction"
# Skip to Step 5 (continue with test-level analysis only)
elif [ -z "$(ls -A "$MUST_GATHER_PATH" 2>/dev/null)" ]; then
echo "ERROR: Must-gather content directory is empty"
# Skip to Step 5 (continue with test-level analysis only)
else
echo "✓ Must-gather content located at: $MUST_GATHER_PATH"
# Continue to Step 4.7 with MUST_GATHER_PATH set
fi
For Pattern 1 (unified) or Pattern 2 (dual):
# Management cluster validation
if [ "$PATTERN" = "unified" ]; then
# Pattern 1: Data extracted directly to logs/ directory
MUST_GATHER_MGMT_PATH=".work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs"
else
# Pattern 2: Standard must-gather extraction (look for content/ or hash directory)
if [ -d ".work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs/content" ]; then
MUST_GATHER_MGMT_PATH=".work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs/content"
else
MUST_GATHER_MGMT_PATH=$(find .work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs -maxdepth 1 -type d -name "*-ci-*" | head -1)
fi
fi
# Validate management cluster path
if [ -z "$MUST_GATHER_MGMT_PATH" ] || [ ! -d "$MUST_GATHER_MGMT_PATH" ]; then
echo "ERROR: Management cluster directory not found"
# Skip to Step 5 (continue with test-level analysis only)
elif [ -z "$(ls -A "$MUST_GATHER_MGMT_PATH" 2>/dev/null)" ]; then
echo "ERROR: Management cluster directory is empty"
# Skip to Step 5 (continue with test-level analysis only)
else
echo "✓ Management cluster data located at: $MUST_GATHER_MGMT_PATH"
fi
# Hosted cluster validation - only if HAS_HOSTED_CLUSTER is true
if [ "$HAS_HOSTED_CLUSTER" = "true" ]; then
MUST_GATHER_HOSTED_PATH=".work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/logs"
# Validate hosted cluster path
if [ ! -d "$MUST_GATHER_HOSTED_PATH" ]; then
echo "WARNING: Hosted cluster directory not found (expected based on archive detection)"
MUST_GATHER_HOSTED_PATH="" # Clear the path
elif [ -z "$(ls -A "$MUST_GATHER_HOSTED_PATH" 2>/dev/null)" ]; then
echo "WARNING: Hosted cluster directory is empty"
MUST_GATHER_HOSTED_PATH="" # Clear the path
else
echo "✓ Hosted cluster data located at: $MUST_GATHER_HOSTED_PATH"
fi
else
echo "✓ Management cluster must-gather located at: $MUST_GATHER_MGMT_PATH"
fi
# Only validate hosted cluster if HAS_HOSTED_CLUSTER is true
if [ "$HAS_HOSTED_CLUSTER" = "true" ]; then
if [ -z "$MUST_GATHER_HOSTED_PATH" ] || [ ! -d "$MUST_GATHER_HOSTED_PATH" ]; then
echo "ERROR: Hosted cluster must-gather content directory not found"
elif [ -z "$(ls -A "$MUST_GATHER_HOSTED_PATH" 2>/dev/null)" ]; then
echo "ERROR: Hosted cluster must-gather content directory is empty"
else
echo "✓ Hosted cluster must-gather located at: $MUST_GATHER_HOSTED_PATH"
echo "✓ Hosted cluster namespace: $HOSTED_NAMESPACE"
fi
fi
Only if Step 4.6 completed successfully:
Locate must-gather-analyzer scripts
The must-gather plugin provides analysis scripts. Locate the scripts directory:
# Try to find the must-gather-analyzer scripts in common locations
for SEARCH_PATH in \
"plugins/must-gather/skills/must-gather-analyzer/scripts" \
"~/.claude/plugins/cache/*/plugins/must-gather/skills/must-gather-analyzer/scripts" \
"$(find ~ -type d -path "*/must-gather/skills/must-gather-analyzer/scripts" 2>/dev/null | head -1)"; do
SCRIPTS_DIR=$(eval echo "$SEARCH_PATH")
if [ -d "$SCRIPTS_DIR" ] && [ -f "$SCRIPTS_DIR/analyze_clusteroperators.py" ]; then
break
fi
SCRIPTS_DIR=""
done
if [ -z "$SCRIPTS_DIR" ]; then
echo "WARNING: Must-gather analysis scripts not found."
echo "Install the must-gather plugin: /plugin install must-gather@ai-helpers"
# Continue to Step 5 without cluster analysis
fi
Run targeted cluster diagnostics
Focus on issues relevant to test failures (not full cluster analysis).
For single must-gather (standard OpenShift):
# Core diagnostics (only if scripts and path are available)
if [ -n "$SCRIPTS_DIR" ] && [ -n "$MUST_GATHER_PATH" ]; then
python3 "$SCRIPTS_DIR/analyze_clusteroperators.py" "$MUST_GATHER_PATH"
python3 "$SCRIPTS_DIR/analyze_pods.py" "$MUST_GATHER_PATH" --problems-only
python3 "$SCRIPTS_DIR/analyze_nodes.py" "$MUST_GATHER_PATH" --problems-only
python3 "$SCRIPTS_DIR/analyze_events.py" "$MUST_GATHER_PATH" --type Warning --count 50
else
echo "WARNING: Skipping must-gather analysis (scripts or path not available)"
fi
For dual must-gather (HyperShift):
# Management cluster diagnostics (only if scripts and path are available)
if [ -n "$SCRIPTS_DIR" ] && [ -n "$MUST_GATHER_MGMT_PATH" ]; then
echo "=== Analyzing Management Cluster ==="
python3 "$SCRIPTS_DIR/analyze_clusteroperators.py" "$MUST_GATHER_MGMT_PATH"
python3 "$SCRIPTS_DIR/analyze_pods.py" "$MUST_GATHER_MGMT_PATH" --problems-only
python3 "$SCRIPTS_DIR/analyze_nodes.py" "$MUST_GATHER_MGMT_PATH" --problems-only
python3 "$SCRIPTS_DIR/analyze_events.py" "$MUST_GATHER_MGMT_PATH" --type Warning --count 50
else
echo "WARNING: Skipping management cluster analysis (scripts or path not available)"
fi
# Hosted cluster diagnostics (only if scripts and path are available)
if [ -n "$SCRIPTS_DIR" ] && [ -n "$MUST_GATHER_HOSTED_PATH" ]; then
echo "=== Analyzing Hosted Cluster (Namespace: $HOSTED_NAMESPACE) ==="
python3 "$SCRIPTS_DIR/analyze_clusteroperators.py" "$MUST_GATHER_HOSTED_PATH"
python3 "$SCRIPTS_DIR/analyze_pods.py" "$MUST_GATHER_HOSTED_PATH" --problems-only
python3 "$SCRIPTS_DIR/analyze_nodes.py" "$MUST_GATHER_HOSTED_PATH" --problems-only
python3 "$SCRIPTS_DIR/analyze_events.py" "$MUST_GATHER_HOSTED_PATH" --type Warning --count 50
else
echo "INFO: Skipping hosted cluster analysis (scripts or path not available)"
fi
Run conditional diagnostics based on test context
# Network diagnostics (if test name suggests network issues)
if [[ "$TEST_NAME" =~ network|ovn|sdn|connectivity|route|ingress|egress ]]; then
if [ -n "$MUST_GATHER_PATH" ]; then
python3 "$SCRIPTS_DIR/analyze_network.py" "$MUST_GATHER_PATH"
fi
if [ -n "$MUST_GATHER_MGMT_PATH" ]; then
echo "=== Management Cluster Network ==="
python3 "$SCRIPTS_DIR/analyze_network.py" "$MUST_GATHER_MGMT_PATH"
fi
if [ -n "$MUST_GATHER_HOSTED_PATH" ]; then
echo "=== Hosted Cluster Network ==="
python3 "$SCRIPTS_DIR/analyze_network.py" "$MUST_GATHER_HOSTED_PATH"
fi
fi
# etcd diagnostics (if test name suggests control-plane issues)
if [[ "$TEST_NAME" =~ etcd|apiserver|control-plane|kube-apiserver ]]; then
if [ -n "$MUST_GATHER_PATH" ]; then
python3 "$SCRIPTS_DIR/analyze_etcd.py" "$MUST_GATHER_PATH"
fi
if [ -n "$MUST_GATHER_MGMT_PATH" ]; then
echo "=== Management Cluster etcd ==="
python3 "$SCRIPTS_DIR/analyze_etcd.py" "$MUST_GATHER_MGMT_PATH"
fi
if [ -n "$MUST_GATHER_HOSTED_PATH" ]; then
echo "=== Hosted Cluster etcd ==="
python3 "$SCRIPTS_DIR/analyze_etcd.py" "$MUST_GATHER_HOSTED_PATH"
fi
fi
See plugins/must-gather/skills/must-gather-analyzer/SKILL.md for all available analysis scripts.
Capture analysis output
Only if Step 4.7 completed:
Temporal correlation
For HyperShift (dual must-gather):
Component correlation
clusters-{namespace})For HyperShift (dual must-gather):
clusters-{namespace} namespace)Generate correlated insights
For HyperShift (dual must-gather):
Prefix correlations with cluster type:
Cross-cluster correlations:
Store these insights for inclusion in Step 4.9 root cause determination
Synthesize all gathered evidence to determine the most likely root cause for the test failure:
Analyze all available evidence
Prioritize evidence based on temporal proximity
Generate root cause hypothesis
For HyperShift jobs:
Formulate actionable recommendations
Display structured summary with enhanced formatting
For single must-gather or no must-gather:
# Test Failure Analysis Complete
## Job Information
- **Prow Job**: {prowjob-name}
- **Build ID**: {build_id}
- **Target**: {target}
- **Test**: {test_name}
## Test Failure Analysis
### Error
{error message from stack trace}
### Summary
{failure analysis from stack trace and code}
### Evidence
{evidence from build-log.txt and interval files}
### Additional Evidence
{additional evidence from logs/events}
---
## Cluster Diagnostics
*(Only if must-gather was analyzed)*
### Cluster Operators
{output from analyze_clusteroperators.py}
### Problematic Pods
{output from analyze_pods.py --problems-only}
### Node Issues
{output from analyze_nodes.py --problems-only}
### Recent Warning Events
{output from analyze_events.py}
### Network Analysis
*(Only if network-related test)*
{output from analyze_network.py}
### etcd Analysis
*(Only if etcd-related test)*
{output from analyze_etcd.py}
---
## Correlation
*(Only if must-gather was analyzed)*
### Timeline
- **Test started**: {from timestamp}
- **Test failed**: {to timestamp}
- **Cluster events during test**:
- {cluster-event} at {timestamp}
- {cluster-event} at {timestamp}
### Affected Components
- {affected operators/pods/nodes}
### Root Cause Hypothesis
{correlated analysis combining test-level and cluster-level evidence}
---
## Artifacts
- **Test artifacts**: `.work/prow-job-analyze-test-failure/{build_id}/logs/`
- **Must-gather**: `.work/prow-job-analyze-test-failure/{build_id}/must-gather/logs/` *(if extracted)*
For dual must-gather (HyperShift):
# Test Failure Analysis Complete (HyperShift)
## Job Information
- **Prow Job**: {prowjob-name}
- **Build ID**: {build_id}
- **Target**: {target}
- **Test**: {test_name}
- **Hosted Cluster Namespace**: {HOSTED_NAMESPACE}
## Test Failure Analysis
### Error
{error message from stack trace}
### Summary
{failure analysis from stack trace and code}
### Evidence
{evidence from build-log.txt and interval files}
### Additional Evidence
{additional evidence from logs/events}
---
## Management Cluster Diagnostics
### Cluster Operators
{output from analyze_clusteroperators.py for management cluster}
### Problematic Pods
{output from analyze_pods.py --problems-only for management cluster}
### Node Issues
{output from analyze_nodes.py --problems-only for management cluster}
### Recent Warning Events
{output from analyze_events.py for management cluster}
### Network Analysis
*(Only if network-related test)*
{output from analyze_network.py for management cluster}
### etcd Analysis
*(Only if etcd-related test)*
{output from analyze_etcd.py for management cluster}
---
## Hosted Cluster Diagnostics
**Namespace**: `{HOSTED_NAMESPACE}`
### Cluster Operators
{output from analyze_clusteroperators.py for hosted cluster}
### Problematic Pods
{output from analyze_pods.py --problems-only for hosted cluster}
### Node Issues
{output from analyze_nodes.py --problems-only for hosted cluster}
### Recent Warning Events
{output from analyze_events.py for hosted cluster}
### Network Analysis
*(Only if network-related test)*
{output from analyze_network.py for hosted cluster}
### etcd Analysis
*(Only if etcd-related test)*
{output from analyze_etcd.py for hosted cluster}
---
## Correlation
### Timeline
- **Test started**: {from timestamp}
- **Test failed**: {to timestamp}
- **Management cluster events during test**:
- {cluster-event} at {timestamp}
- **Hosted cluster events during test**:
- {cluster-event} at {timestamp}
### Affected Components
**Management Cluster**:
- {affected operators/pods/nodes}
**Hosted Cluster**:
- {affected operators/pods/nodes}
### Root Cause Hypothesis
{correlated analysis combining:
- Test-level evidence
- Management cluster diagnostics
- Hosted cluster diagnostics
- Cross-cluster interactions}
---
## Artifacts
- **Test artifacts**: `.work/prow-job-analyze-test-failure/{build_id}/logs/`
- **Management cluster must-gather**: `.work/prow-job-analyze-test-failure/{build_id}/must-gather-mgmt/logs/`
- **Hosted cluster must-gather**: `.work/prow-job-analyze-test-failure/{build_id}/must-gather-hosted/logs/`
Handle errors in the same way as "Error handling" in "Prow Job Analyze Resource" skill, with these additional must-gather-specific cases:
Must-gather not available
gcloud storage ls returns 404 for must-gather.tar, this is expected (not all jobs have must-gather)Must-gather extraction fails
Analysis scripts not found
find command returns empty (no scripts found), warn the userPartial analysis script failures
Empty analysis results
Follow the instructions in "Performance Considerations" in "Prow Job Analyze Resource" skill
npx claudepluginhub pavolloffay/ai-helpers --plugin prow-jobGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.