Fixing GitHub Actions Link Checker: KeyError 'details' Resolution
The Challenge: KeyError Crashes Link Checking Automation
During routine maintenance of the IT-Journey automated link checking system, we encountered a critical failure in our GitHub Actions workflow. The error was clear but puzzling:
KeyError: 'details'
File "analyze_links.py", line 25, in analyze_link_failures
'error': {'message': error['status']['details']},
~~~~~~~~~~~~~~~^^^^^^^^^^^
This error indicated that our Python script was trying to access a nested key structure (error['status']['details']
) that didn’t exist in the actual data from the Lychee link checker output.
AI-Assisted Development Process
Initial Analysis with AI Collaboration
Working with AI assistance, we approached this systematically:
- Error Pattern Recognition: The AI helped identify this as a classic defensive programming issue
- Root Cause Analysis: Together we traced the problem to assumptions about data structure
- Solution Design: Collaborative design of more robust error handling
- Implementation Strategy: Step-by-step defensive programming approach
Understanding the Problem Context
The workflow uses Lychee link checker to scan all markdown files in the IT-Journey repository, then processes the JSON results with a Python script. The error occurred when the script assumed a specific nested structure in error objects that wasn’t always present.
Step-by-Step Implementation
Problem: Rigid Data Structure Assumptions
Before (Problematic Code):
# Convert error map to individual result format
for file_path, errors in error_map.items():
for error in errors:
results.append({
'url': error['url'],
'status': 'Failed',
'error': {'message': error['status']['details']}, # ❌ KeyError here!
'file': file_path
})
This code assumed:
error['status']
always existserror['status']
is always a dictionaryerror['status']['details']
always exists
Solution: Defensive Programming Approach
After (Robust Code):
# Convert error map to individual result format
for file_path, errors in error_map.items():
for error in errors:
# Extract error message more defensively
error_msg = ''
if 'status' in error:
if isinstance(error['status'], dict):
error_msg = error['status'].get('details',
error['status'].get('message',
str(error['status'])))
else:
error_msg = str(error['status'])
else:
error_msg = error.get('message', 'Unknown error')
results.append({
'url': error['url'],
'status': 'Failed',
'error': {'message': error_msg},
'file': file_path
})
Enhanced Success Map Handling
We also improved the success map handling to be more defensive:
Before:
for success in successes:
results.append({
'url': success.get('url', ''), # Assumes success is always a dict
'status': 'Ok',
'file': file_path
})
After:
for success in successes:
# Handle both object and string URL formats
url = success.get('url', '') if isinstance(success, dict) else str(success)
results.append({
'url': url,
'status': 'Ok',
'file': file_path
})
Global Error Handling
Added comprehensive try-catch around the entire analysis:
try:
analysis = analyze_link_failures('link-check-results/results.json')
if analysis:
# Process successful analysis...
else:
print("Analysis failed - no data returned")
sys.exit(1)
except Exception as e:
print(f"Analysis failed with error: {e}")
print("Creating minimal analysis results for GitHub Actions...")
# Create minimal output so the workflow doesn't completely fail
with open('analysis_summary.txt', 'w') as f:
f.write("BROKEN_COUNT=0\n")
f.write("TOTAL_COUNT=0\n")
f.write("SUCCESS_RATE=0\n")
sys.exit(1)
Key Learnings and Insights
Defensive Programming Principles Applied
- Never Assume Data Structure: Always check if keys exist before accessing them
- Handle Type Variations: Data might be strings, objects, or other types
- Graceful Degradation: Provide fallbacks when expected data isn’t available
- Meaningful Error Messages: Help future debugging with descriptive error handling
AI Collaboration Benefits
- Pattern Recognition: AI quickly identified this as a common defensive programming issue
- Solution Completeness: Suggested checking both success and error paths
- Best Practices: Recommended comprehensive error handling patterns
- Code Review: Helped identify edge cases we might have missed
Real-World Application Insights
This fix demonstrates how external tools (like Lychee) can change their output format over time, breaking assumptions in consuming code. Defensive programming prevents these fragile integrations.
Troubleshooting and Error Resolution
Common Issues with External Tool Integration
- Version Changes: Tools update and change output formats
- Environment Differences: Different execution environments may produce different outputs
- Network Conditions: Timeouts and errors create unexpected data structures
- Data Volume: Large datasets might have different structures than small test cases
Prevention Strategies
- Schema Validation: Consider using JSON schema validation for critical integrations
- Comprehensive Testing: Test with various output formats and edge cases
- Logging: Add detailed logging to understand actual data structures
- Documentation: Document expected vs. actual data formats
Future Development Paths
Enhanced Error Handling
This fix opens paths for further improvements:
- JSON schema validation for Lychee outputs
- Structured error reporting with categories
- Better integration testing for external tools
- Automated testing of error handling paths
Monitoring and Alerting
- Track error patterns over time
- Alert on unusual failure rates
- Monitor changes in external tool outputs
- Performance metrics for link checking workflows
Integration with IT-Journey Ecosystem
This defensive programming approach will be applied to:
- Other GitHub Actions workflows
- External API integrations
- Data processing pipelines
- Automated testing frameworks
Conclusion
This debugging session exemplifies the power of AI-assisted development combined with solid defensive programming principles. By moving from rigid assumptions to flexible data handling, we’ve created a more robust automation system that can handle the inevitable changes in external tool outputs.
The key lesson: always assume external data might not match your expectations, and build resilience into your code from the start. This approach not only fixes immediate issues but prevents future problems and creates more maintainable systems.
This article demonstrates the IT-Journey approach of learning from failures, applying defensive programming principles, and leveraging AI assistance to create more robust automated systems. Each debugging session becomes an opportunity to strengthen the entire development ecosystem.