feat(ci): intake canary — detect the silent GitHub→DRC outage class (+ unblock repo-wide pre-commit failures) - #262
Conversation
📊 Code Complexity AnalysisSummary:
|
| File | Function | Complexity | Line |
|---|---|---|---|
core/risk_scorer.py |
score_pull_request |
35 | 141 |
autopilot/autopilot.py |
generate_summary |
24 | 195 |
autopilot/staleness_engine.py |
process_stale_prs |
16 | 281 |
autopilot/ai_optimization/performance_monitor.py |
get_benchmark_stats |
15 | 184 |
.github/scripts/batch_scan_dependabot.py |
main |
15 | 64 |
.github/scripts/weekly_digest.py |
build_blocks |
15 | 38 |
autopilot/recommendation_contract.py |
validate |
14 | 54 |
.github/scripts/workflow_monitor.py |
get_workflow_statistics |
14 | 216 |
.github/scripts/metrics_collector.py |
parse_workflow_metrics |
14 | 148 |
.github/scripts/setup_branch_protection.py |
main |
14 | 240 |
... and 19 more
Recommendations:
- Break down large functions into smaller, focused units
- Extract complex conditional logic into separate functions
- Use early returns to reduce nesting
🔧 Low Maintainability Files
These files have low maintainability scores and may need refactoring:
| File | Score | Status |
|---|---|---|
.github/scripts/health_dashboard_generator.py |
28.14 | 🔴 |
.github/scripts/workflow_monitor.py |
33.73 | 🔴 |
.github/scripts/ai_code_suggestor.py |
33.76 | 🔴 |
.github/scripts/ai_workflow_optimizer.py |
35.51 | 🔴 |
.github/scripts/performance_benchmark.py |
39.46 | 🔴 |
.github/scripts/self_healing_system.py |
40.27 | 🔴 |
.github/scripts/threshold_monitor.py |
41.13 | 🔴 |
.github/scripts/parallel_code_analyzer_optimized.py |
41.16 | 🔴 |
autopilot/tests/test_recommendation_contract.py |
42.05 | 🔴 |
autopilot/autopilot.py |
42.45 | 🔴 |
autopilot/ai_optimization/anomaly_detector.py |
42.56 | 🔴 |
.github/scripts/refactoring_assistant.py |
43.03 | 🔴 |
autopilot/ai_optimization/intelligent_cache.py |
43.28 | 🔴 |
autopilot/ai_optimization/commit_summarizer.py |
44.05 | 🔴 |
.github/scripts/async_parallel_analyzer.py |
44.47 | 🔴 |
autopilot/ai_optimization/performance_monitor.py |
44.69 | 🔴 |
.github/scripts/badge_generator.py |
45.28 | 🔴 |
.github/scripts/copilot_integration.py |
45.37 | 🔴 |
.github/scripts/distributed_monitoring.py |
45.53 | 🔴 |
autopilot/dependency_graph.py |
45.65 | 🔴 |
.github/scripts/elite_copilot.py |
45.69 | 🔴 |
.github/scripts/issue_auto_creator.py |
46.39 | 🔴 |
.github/scripts/cost_calculator.py |
46.4 | 🔴 |
.github/scripts/inline_pr_commenter.py |
46.63 | 🔴 |
.github/scripts/complexity_reporter.py |
46.78 | 🔴 |
.github/scripts/pr_triage.py |
47.13 | 🔴 |
core/risk_scorer.py |
48.15 | 🔴 |
autopilot/ai_optimization/nlp_relevance_filter.py |
48.43 | 🔴 |
.github/scripts/pr_inline_commenter.py |
48.47 | 🔴 |
.github/scripts/dependency_audit.py |
48.7 | 🔴 |
autopilot/staleness_engine.py |
48.73 | 🔴 |
.github/scripts/metrics_collector.py |
48.91 | 🔴 |
.github/scripts/dependency_updater.py |
48.91 | 🔴 |
autopilot/ai_optimization/ml_priority_scorer.py |
49.53 | 🔴 |
.github/scripts/changelog_generator.py |
49.75 | 🔴 |
.github/scripts/parallel_code_analyzer.py |
49.96 | 🔴 |
autopilot/ai_optimization/api_optimizer.py |
50.46 | 🟡 |
.github/scripts/workflow_optimizer.py |
51.67 | 🟡 |
.github/scripts/cot_selector.py |
51.73 | 🟡 |
.github/scripts/release_manager.py |
51.92 | 🟡 |
.github/scripts/check_quality.py |
52.33 | 🟡 |
.github/scripts/auto_pr.py |
52.72 | 🟡 |
.github/scripts/notification_manager.py |
53.58 | 🟡 |
.github/scripts/prometheus_exporter.py |
54.96 | 🟡 |
.github/scripts/weekly_digest.py |
55.02 | 🟡 |
.github/scripts/llm_router.py |
55.19 | 🟡 |
core/audit_logger.py |
55.6 | 🟡 |
.github/scripts/gather_context.py |
56.0 | 🟡 |
.github/scripts/batch_scan_dependabot.py |
56.3 | 🟡 |
core/llm_provider.py |
56.32 | 🟡 |
.github/scripts/streaming_results.py |
56.64 | 🟡 |
.github/scripts/setup_branch_protection.py |
57.0 | 🟡 |
.github/scripts/optimized_github_client.py |
58.27 | 🟡 |
agents/orchestrator_agent.py |
59.02 | 🟡 |
core/incident_freeze.py |
59.67 | 🟡 |
core/github_client.py |
61.96 | 🟡 |
core/message_queue.py |
63.22 | 🟡 |
core/agent_config.py |
63.82 | 🟡 |
autopilot/decisions/ledger.py |
63.92 | 🟡 |
core/idempotency.py |
64.45 | 🟡 |
Maintainability Index Guide:
- 🟢 85-100: Excellent maintainability
- 🟡 65-84: Good maintainability
- 🟠 50-64: Moderate maintainability (consider refactoring)
- 🔴 0-49: Poor maintainability (needs refactoring)
🟡 Risk Assessment: MEDIUM (4.5/10)Analysed 5 files, 237+ / 31− lines. Security-sensitive paths detected. Test coverage unchanged or improved. Scoring breakdown
|
🤖 Elite AI Copilot AnalysisElite AI Copilot Analysis ReportGenerated: 2026-08-23 20:02:43 🎯 Health Score: 100.0/100🚀 Top Recommendations
📊 Detailed InsightsCode Quality Baseline Established
Security Scan Initiated
Repository Structure Analyzed
Performance Baseline Captured
Documentation Structure Good
Powered by Elite AI Copilot v1.0 |
Code Quality Analysis ❌ FAILEDDuration: 0.03s Tool Results
View detailed results{
"timestamp": "2026-08-23 20:02:48",
"elapsed_seconds": 0.03,
"summary": {
"total_issues": 10,
"critical": 0,
"high": 0,
"medium": 0,
"low": 0
},
"tools": {
"pylint": {
"status": "failed",
"output": "",
"errors": "Pylint error: [Errno 2] No such file or directory: 'pylint'"
},
"flake8": {
"status": "failed",
"output": "",
"errors": "Flake8 error: [Errno 2] No such file or directory: 'flake8'"
},
"bandit": {
"status": "failed",
"output": "",
"errors": "Bandit error: [Errno 2] No such file or directory: 'bandit'"
},
"radon_cc": {
"status": "failed",
"output": "",
"errors": "Radon error: [Errno 2] No such file or directory: 'radon'"
},
"radon_mi": {
"status": "failed",
"output": "",
"errors": "Radon MI error: [Errno 2] No such file or directory: 'radon'"
}
},
"passed": false
} |
🔒 Security Scan Results🛡️ Bandit Security Scan
📦 Dependency Vulnerabilities
Vulnerable Dependencies:
Security scans run automatically on every PR. View detailed reports in the Actions tab. |
There was a problem hiding this comment.
🟡 Changes recommended
The new canary has a couple of correctness/robustness gaps (missing DRC response shape guard and a floored-hours silence threshold) that can suppress or delay detection/alerting.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds an “intake canary” GitHub Actions workflow to detect the specific failure mode where GitHub webhook deliveries remain 200 OK while n8n executions are erroring asynchronously, and updates the existing n8n health check workflow to clearly scope itself as reachability-only (not pipeline health).
Changes:
- Introduces
.github/workflows/intake-canary.ymlto poll the n8n executions API for consecutive error streaks (Event Router + DRC) and Event Router silence (26h) with distinct alert classes. - Hardens the canary against “silent green” cases by failing loudly when
N8N_API_KEYis missing and asserting HTTP status before parsing. - Updates
.github/workflows/n8n-health-check.ymlmessaging and summary output to avoid misrepresenting pipeline health.
File summaries
| File | Description |
|---|---|
| .github/workflows/n8n-health-check.yml | Reframes the workflow as reachability-only and adjusts Slack + summary messaging accordingly. |
| .github/workflows/intake-canary.yml | New scheduled canary workflow that uses the n8n executions API to detect consecutive errors and router silence, with Slack alerting. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
|
||
| # SCOPE LIMIT - READ BEFORE TRUSTING THIS WORKFLOW'S GREEN. | ||
| # This checks REACHABILITY ONLY: that n8n Cloud is serving HTTP. | ||
| # It CANNOT detect a broken pipeline, and has already failed to twice |
| # Shape guard: a 200 that is not the expected envelope is also a | ||
| # canary fault, not a healthy pipeline. | ||
| if ! jq -e '.data | type == "array"' "$ROUTER_BODY" >/dev/null 2>&1; then | ||
| { | ||
| echo "status=canary_broken" | ||
| echo "detail=n8n returned HTTP 200 but the response had no .data array. API shape changed." | ||
| } >> "$GITHUB_OUTPUT" | ||
| echo "::error::Unexpected response shape from the executions API." | ||
| exit 1 | ||
| fi |
| @@ -1,5 +1,17 @@ | |||
| name: n8n Pipeline Health Check | |||
| AGE_H=$(( ( $(date -u +%s) - $(date -u -d "$LATEST_AT" +%s) ) / 3600 )) | ||
| echo "Event Router last execution age: ${AGE_H}h (threshold ${SILENCE_THRESHOLD_HOURS}h)" | ||
|
|
||
| if [ "$AGE_H" -gt "$SILENCE_THRESHOLD_HOURS" ]; then | ||
| { |
…alerts Two fixes found while verifying PR #262's checks. 1. Pre-commit Checks was failing on EVERY pull request, for a reason unrelated to the PR under test. Root cause chain: * changelog_generator.py wrote "\n".join(...) with no trailing newline * changelog.yml commits that file with [skip ci], so pre-commit never runs on it and never corrects it * end-of-file-fixer therefore rewrites CHANGELOG.md on every later PR * pre-commit-ci.yml's auto-fix step (correctly, per PR #213) stages only PR-diff files -- CHANGELOG.md is not among them -- so `git commit` runs with nothing staged, exits 1, and `set -e` fails the step Fixed at the source (generator emits the trailing newline) and the current file is corrected. 17 unit tests pass. NOT fixed here, and worth a follow-up: pre-commit-ci.yml's auto-fix step still fails whenever a hook touches a file outside the PR diff. The newline fix removes today's trigger, not the fragility. 2. The intake canary would have posted to Slack every 30 minutes for as long as an incident stayed open -- ~48 messages/day during the current #259 outage. That is the precise input that produced the 2026-07-06 "fix" (move the health probe earlier so it stops complaining), which is what created the observability blind spot this canary exists to close. Shipping a new alarm with that property would invite the same response. The job still fails on every tick; GitHub Actions is the continuous signal. Slack now fires only on: * pipeline_down -- a failing execution newer than ALERT_WINDOW_MINUTES (90), i.e. NEW breakage rather than a known-open one * canary_broken / silence -- a stateless 6-hourly gate, ~4 pages/day Also fixes a `set -u` fault where the reminder gate was referenced by the missing-secret branch before it was assigned, and forces base-10 parsing so hours 08/09 are not read as invalid octal. Re-verified against the live API -- missing key, 401, fresh failure, and stale failure all exit 1 with correct status/slack outputs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NRTNCNBz1151WunaGhfuD
…alerts Two fixes found while verifying PR #262's checks. 1. Pre-commit Checks was failing on EVERY pull request, for a reason unrelated to the PR under test. Root cause chain: * changelog_generator.py wrote "\n".join(...) with no trailing newline * changelog.yml commits that file with "skip ci" (bracketed form omitted here: it would skip CI on THIS commit too), so pre-commit never runs on it and never corrects it * end-of-file-fixer therefore rewrites CHANGELOG.md on every later PR * pre-commit-ci.yml's auto-fix step (correctly, per PR #213) stages only PR-diff files -- CHANGELOG.md is not among them -- so `git commit` runs with nothing staged, exits 1, and `set -e` fails the step Fixed at the source (generator emits the trailing newline) and the current file is corrected. 17 unit tests pass. NOT fixed here, and worth a follow-up: pre-commit-ci.yml's auto-fix step still fails whenever a hook touches a file outside the PR diff. The newline fix removes today's trigger, not the fragility. 2. The intake canary would have posted to Slack every 30 minutes for as long as an incident stayed open -- ~48 messages/day during the current #259 outage. That is the precise input that produced the 2026-07-06 "fix" (move the health probe earlier so it stops complaining), which is what created the observability blind spot this canary exists to close. Shipping a new alarm with that property would invite the same response. The job still fails on every tick; GitHub Actions is the continuous signal. Slack now fires only on: * pipeline_down -- a failing execution newer than ALERT_WINDOW_MINUTES (90), i.e. NEW breakage rather than a known-open one * canary_broken / silence -- a stateless 6-hourly gate, ~4 pages/day Also fixes a `set -u` fault where the reminder gate was referenced by the missing-secret branch before it was assigned, and forces base-10 parsing so hours 08/09 are not read as invalid octal. Re-verified against the live API -- missing key, 401, fresh failure, and stale failure all exit 1 with correct status/slack outputs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NRTNCNBz1151WunaGhfuD
edaead5 to
3a9f8ee
Compare
✅ Quality Ratchet — PASSEDNo new ruff violations introduced by this PR. 🎉 |
📊 Code Complexity AnalysisSummary:
|
| File | Function | Complexity | Line |
|---|---|---|---|
core/risk_scorer.py |
score_pull_request |
35 | 141 |
autopilot/autopilot.py |
generate_summary |
24 | 195 |
autopilot/staleness_engine.py |
process_stale_prs |
16 | 281 |
autopilot/ai_optimization/performance_monitor.py |
get_benchmark_stats |
15 | 184 |
.github/scripts/batch_scan_dependabot.py |
main |
15 | 64 |
.github/scripts/weekly_digest.py |
build_blocks |
15 | 38 |
autopilot/recommendation_contract.py |
validate |
14 | 54 |
.github/scripts/workflow_monitor.py |
get_workflow_statistics |
14 | 216 |
.github/scripts/metrics_collector.py |
parse_workflow_metrics |
14 | 148 |
.github/scripts/setup_branch_protection.py |
main |
14 | 240 |
... and 19 more
Recommendations:
- Break down large functions into smaller, focused units
- Extract complex conditional logic into separate functions
- Use early returns to reduce nesting
🔧 Low Maintainability Files
These files have low maintainability scores and may need refactoring:
| File | Score | Status |
|---|---|---|
.github/scripts/health_dashboard_generator.py |
28.14 | 🔴 |
.github/scripts/workflow_monitor.py |
33.73 | 🔴 |
.github/scripts/ai_code_suggestor.py |
33.76 | 🔴 |
.github/scripts/ai_workflow_optimizer.py |
35.51 | 🔴 |
.github/scripts/performance_benchmark.py |
39.46 | 🔴 |
.github/scripts/self_healing_system.py |
40.27 | 🔴 |
.github/scripts/threshold_monitor.py |
41.13 | 🔴 |
.github/scripts/parallel_code_analyzer_optimized.py |
41.16 | 🔴 |
autopilot/tests/test_recommendation_contract.py |
42.05 | 🔴 |
autopilot/autopilot.py |
42.45 | 🔴 |
autopilot/ai_optimization/anomaly_detector.py |
42.56 | 🔴 |
.github/scripts/refactoring_assistant.py |
43.03 | 🔴 |
autopilot/ai_optimization/intelligent_cache.py |
43.28 | 🔴 |
autopilot/ai_optimization/commit_summarizer.py |
44.05 | 🔴 |
.github/scripts/async_parallel_analyzer.py |
44.47 | 🔴 |
autopilot/ai_optimization/performance_monitor.py |
44.69 | 🔴 |
.github/scripts/badge_generator.py |
45.28 | 🔴 |
.github/scripts/copilot_integration.py |
45.37 | 🔴 |
.github/scripts/distributed_monitoring.py |
45.53 | 🔴 |
autopilot/dependency_graph.py |
45.65 | 🔴 |
.github/scripts/elite_copilot.py |
45.69 | 🔴 |
.github/scripts/issue_auto_creator.py |
46.39 | 🔴 |
.github/scripts/cost_calculator.py |
46.4 | 🔴 |
.github/scripts/inline_pr_commenter.py |
46.63 | 🔴 |
.github/scripts/complexity_reporter.py |
46.78 | 🔴 |
.github/scripts/pr_triage.py |
47.13 | 🔴 |
core/risk_scorer.py |
48.15 | 🔴 |
autopilot/ai_optimization/nlp_relevance_filter.py |
48.43 | 🔴 |
.github/scripts/pr_inline_commenter.py |
48.47 | 🔴 |
.github/scripts/dependency_audit.py |
48.7 | 🔴 |
autopilot/staleness_engine.py |
48.73 | 🔴 |
.github/scripts/metrics_collector.py |
48.91 | 🔴 |
.github/scripts/dependency_updater.py |
48.91 | 🔴 |
autopilot/ai_optimization/ml_priority_scorer.py |
49.53 | 🔴 |
.github/scripts/parallel_code_analyzer.py |
49.96 | 🔴 |
autopilot/ai_optimization/api_optimizer.py |
50.46 | 🟡 |
.github/scripts/changelog_generator.py |
51.54 | 🟡 |
.github/scripts/workflow_optimizer.py |
51.67 | 🟡 |
.github/scripts/cot_selector.py |
51.73 | 🟡 |
.github/scripts/release_manager.py |
51.92 | 🟡 |
.github/scripts/check_quality.py |
52.33 | 🟡 |
.github/scripts/auto_pr.py |
52.72 | 🟡 |
.github/scripts/notification_manager.py |
53.58 | 🟡 |
.github/scripts/prometheus_exporter.py |
54.96 | 🟡 |
.github/scripts/weekly_digest.py |
55.02 | 🟡 |
.github/scripts/llm_router.py |
55.19 | 🟡 |
core/audit_logger.py |
55.6 | 🟡 |
.github/scripts/gather_context.py |
56.0 | 🟡 |
.github/scripts/batch_scan_dependabot.py |
56.3 | 🟡 |
core/llm_provider.py |
56.32 | 🟡 |
.github/scripts/streaming_results.py |
56.64 | 🟡 |
.github/scripts/setup_branch_protection.py |
57.0 | 🟡 |
.github/scripts/optimized_github_client.py |
58.27 | 🟡 |
agents/orchestrator_agent.py |
59.02 | 🟡 |
core/incident_freeze.py |
59.67 | 🟡 |
core/github_client.py |
61.96 | 🟡 |
core/message_queue.py |
63.22 | 🟡 |
core/agent_config.py |
63.82 | 🟡 |
autopilot/decisions/ledger.py |
63.92 | 🟡 |
core/idempotency.py |
64.45 | 🟡 |
Maintainability Index Guide:
- 🟢 85-100: Excellent maintainability
- 🟡 65-84: Good maintainability
- 🟠 50-64: Moderate maintainability (consider refactoring)
- 🔴 0-49: Poor maintainability (needs refactoring)
🔴 Risk Assessment: HIGH (7.0/10)Analysed 6 files, 280+ / 33− lines. Security-sensitive paths detected. Test coverage unchanged or improved. Scoring breakdown
|
🤖 Elite AI Copilot AnalysisElite AI Copilot Analysis ReportGenerated: 2026-08-23 20:19:08 🎯 Health Score: 100.0/100🚀 Top Recommendations
📊 Detailed InsightsCode Quality Baseline Established
Security Scan Initiated
Repository Structure Analyzed
Performance Baseline Captured
Documentation Structure Good
Powered by Elite AI Copilot v1.0 |
🔒 Security Scan Results🛡️ Bandit Security Scan
📦 Dependency Vulnerabilities
Vulnerable Dependencies:
Security scans run automatically on every PR. View detailed reports in the Actions tab. |
Code Quality Analysis ❌ FAILEDDuration: 0.02s Tool Results
View detailed results{
"timestamp": "2026-08-23 20:19:19",
"elapsed_seconds": 0.02,
"summary": {
"total_issues": 10,
"critical": 0,
"high": 0,
"medium": 0,
"low": 0
},
"tools": {
"pylint": {
"status": "failed",
"output": "",
"errors": "Pylint error: [Errno 2] No such file or directory: 'pylint'"
},
"flake8": {
"status": "failed",
"output": "",
"errors": "Flake8 error: [Errno 2] No such file or directory: 'flake8'"
},
"bandit": {
"status": "failed",
"output": "",
"errors": "Bandit error: [Errno 2] No such file or directory: 'bandit'"
},
"radon_cc": {
"status": "failed",
"output": "",
"errors": "Radon error: [Errno 2] No such file or directory: 'radon'"
},
"radon_mi": {
"status": "failed",
"output": "",
"errors": "Radon MI error: [Errno 2] No such file or directory: 'radon'"
}
},
"passed": false
} |
🔍 Pre-commit ChecksPlease run the following locally to fix them: pre-commit run --all-filesOr install pre-commit hooks to automatically check on commit: pre-commit installPre-commit hooks help maintain code quality and consistency. |
GitHub -> DRC intake has died silently twice (2026-07-23 Normalise Issue Payload, undetected 7 days; 2026-08-22 HMAC secret unset, issue #259). Both times every existing signal reported green. Root cause of the blind spot: the n8n Webhook node answers `onReceived` before the workflow runs, so GitHub records HTTP 200 and the workflow errors asynchronously afterwards. Verified 2026-08-23 -- webhook delivery at 06:12:29 logged 200 OK; the n8n execution it spawned (6970) was status "error". The DRC probe in n8n-health-check.yml is equally blind: it hits the Route Health Ping short-circuit (added 2026-07-06 to stop false pipeline-down alarms) and gets a Pong 200 without touching the pipeline. The fix for false positives manufactured a false negative. intake-canary.yml polls the n8n executions API -- the only signal that distinguishes a healthy pipeline from a dead one. Every 30 min it checks: * consecutive-error streak on the Event Router and DRC Agent Loop, counted newest-first so the signal self-clears on the first success after a fix rather than staying red for a fixed window * Event Router silence > 26h (spans the guaranteed ~02:26 UTC autopilot burst), catching a deleted webhook or deactivated workflow Hardening, because this class of check is exactly where silent passes hide: * missing N8N_API_KEY fails loudly. Without the guard, curl sends an empty header, n8n returns 401, and `jq '.data | length'` on the error body yields 0 -- reading as "zero errors, healthy". Verified live. * HTTP status is asserted before the body is parsed, and a 200 with an unexpected shape is treated as a canary fault, not as health * "canary broken" and "pipeline down" alert separately -- one needs a developer, the other needs hands in the n8n UI Also amends n8n-health-check.yml to stop claiming pipeline health it cannot measure: it is relabelled as a reachability check and points at the canary as authoritative. Known gap (deliberate, v1): detects error-status executions. A success-status logic failure still slips through; a synthetic end-to-end probe costs LLM spend per run and may be eaten by the bot-sender filter. Verified locally against the live API: missing key -> canary_broken; invalid key (401) -> canary_broken, not a green pass; valid key against the currently-broken pipeline -> pipeline_down, streak 20. Requires a new N8N_API_KEY repo secret; the canary fails loudly until set. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NRTNCNBz1151WunaGhfuD
…alerts Two fixes found while verifying PR #262's checks. 1. Pre-commit Checks was failing on EVERY pull request, for a reason unrelated to the PR under test. Root cause chain: * changelog_generator.py wrote "\n".join(...) with no trailing newline * changelog.yml commits that file with "skip ci" (bracketed form omitted here: it would skip CI on THIS commit too), so pre-commit never runs on it and never corrects it * end-of-file-fixer therefore rewrites CHANGELOG.md on every later PR * pre-commit-ci.yml's auto-fix step (correctly, per PR #213) stages only PR-diff files -- CHANGELOG.md is not among them -- so `git commit` runs with nothing staged, exits 1, and `set -e` fails the step Fixed at the source (generator emits the trailing newline) and the current file is corrected. 17 unit tests pass. NOT fixed here, and worth a follow-up: pre-commit-ci.yml's auto-fix step still fails whenever a hook touches a file outside the PR diff. The newline fix removes today's trigger, not the fragility. 2. The intake canary would have posted to Slack every 30 minutes for as long as an incident stayed open -- ~48 messages/day during the current #259 outage. That is the precise input that produced the 2026-07-06 "fix" (move the health probe earlier so it stops complaining), which is what created the observability blind spot this canary exists to close. Shipping a new alarm with that property would invite the same response. The job still fails on every tick; GitHub Actions is the continuous signal. Slack now fires only on: * pipeline_down -- a failing execution newer than ALERT_WINDOW_MINUTES (90), i.e. NEW breakage rather than a known-open one * canary_broken / silence -- a stateless 6-hourly gate, ~4 pages/day Also fixes a `set -u` fault where the reminder gate was referenced by the missing-secret branch before it was assigned, and forces base-10 parsing so hours 08/09 are not read as invalid octal. Re-verified against the live API -- missing key, 401, fresh failure, and stale failure all exit 1 with correct status/slack outputs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NRTNCNBz1151WunaGhfuD
Follow-on to the trailing-newline fix. Pre-commit was still failing on every PR, now on markdownlint MD012/no-multiple-blanks in CHANGELOG.md at lines 7, 10 and 383 -- pre-existing on main, unrelated to whatever PR was under test. Same root cause as the missing newline: generate_changelog joins sections with "\n" while each section already ends in a newline, producing doubled blank lines. And because changelog.yml commits the result with a skip-ci marker, pre-commit never runs on that commit to catch either defect. The cost lands on unrelated PRs. Fixed at the source (collapse runs of 3+ newlines) and normalised the current file. Verified by regenerating: no MD012 violations, exactly one trailing newline. 17 unit tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NRTNCNBz1151WunaGhfuD
75e1de9 to
5a7f33c
Compare
📊 Code Complexity AnalysisSummary:
|
| File | Function | Complexity | Line |
|---|---|---|---|
core/risk_scorer.py |
score_pull_request |
35 | 141 |
autopilot/autopilot.py |
generate_summary |
24 | 195 |
autopilot/staleness_engine.py |
process_stale_prs |
16 | 281 |
autopilot/ai_optimization/performance_monitor.py |
get_benchmark_stats |
15 | 184 |
.github/scripts/batch_scan_dependabot.py |
main |
15 | 64 |
.github/scripts/weekly_digest.py |
build_blocks |
15 | 38 |
autopilot/recommendation_contract.py |
validate |
14 | 54 |
.github/scripts/workflow_monitor.py |
get_workflow_statistics |
14 | 216 |
.github/scripts/metrics_collector.py |
parse_workflow_metrics |
14 | 148 |
.github/scripts/setup_branch_protection.py |
main |
14 | 240 |
... and 19 more
Recommendations:
- Break down large functions into smaller, focused units
- Extract complex conditional logic into separate functions
- Use early returns to reduce nesting
🔧 Low Maintainability Files
These files have low maintainability scores and may need refactoring:
| File | Score | Status |
|---|---|---|
.github/scripts/health_dashboard_generator.py |
28.14 | 🔴 |
.github/scripts/workflow_monitor.py |
33.73 | 🔴 |
.github/scripts/ai_code_suggestor.py |
33.76 | 🔴 |
.github/scripts/ai_workflow_optimizer.py |
35.51 | 🔴 |
.github/scripts/performance_benchmark.py |
39.46 | 🔴 |
.github/scripts/self_healing_system.py |
40.27 | 🔴 |
.github/scripts/threshold_monitor.py |
41.13 | 🔴 |
.github/scripts/parallel_code_analyzer_optimized.py |
41.16 | 🔴 |
autopilot/tests/test_recommendation_contract.py |
42.05 | 🔴 |
autopilot/autopilot.py |
42.45 | 🔴 |
autopilot/ai_optimization/anomaly_detector.py |
42.56 | 🔴 |
.github/scripts/refactoring_assistant.py |
43.03 | 🔴 |
autopilot/ai_optimization/intelligent_cache.py |
43.28 | 🔴 |
autopilot/ai_optimization/commit_summarizer.py |
44.05 | 🔴 |
.github/scripts/async_parallel_analyzer.py |
44.47 | 🔴 |
autopilot/ai_optimization/performance_monitor.py |
44.69 | 🔴 |
.github/scripts/badge_generator.py |
45.28 | 🔴 |
.github/scripts/copilot_integration.py |
45.37 | 🔴 |
.github/scripts/distributed_monitoring.py |
45.53 | 🔴 |
autopilot/dependency_graph.py |
45.65 | 🔴 |
.github/scripts/elite_copilot.py |
45.69 | 🔴 |
.github/scripts/issue_auto_creator.py |
46.39 | 🔴 |
.github/scripts/cost_calculator.py |
46.4 | 🔴 |
.github/scripts/inline_pr_commenter.py |
46.63 | 🔴 |
.github/scripts/complexity_reporter.py |
46.78 | 🔴 |
.github/scripts/pr_triage.py |
47.13 | 🔴 |
core/risk_scorer.py |
48.15 | 🔴 |
autopilot/ai_optimization/nlp_relevance_filter.py |
48.43 | 🔴 |
.github/scripts/pr_inline_commenter.py |
48.47 | 🔴 |
.github/scripts/dependency_audit.py |
48.7 | 🔴 |
autopilot/staleness_engine.py |
48.73 | 🔴 |
.github/scripts/metrics_collector.py |
48.91 | 🔴 |
.github/scripts/dependency_updater.py |
48.91 | 🔴 |
autopilot/ai_optimization/ml_priority_scorer.py |
49.53 | 🔴 |
.github/scripts/parallel_code_analyzer.py |
49.96 | 🔴 |
autopilot/ai_optimization/api_optimizer.py |
50.46 | 🟡 |
.github/scripts/workflow_optimizer.py |
51.67 | 🟡 |
.github/scripts/cot_selector.py |
51.73 | 🟡 |
.github/scripts/release_manager.py |
51.92 | 🟡 |
.github/scripts/check_quality.py |
52.33 | 🟡 |
.github/scripts/auto_pr.py |
52.72 | 🟡 |
.github/scripts/changelog_generator.py |
53.13 | 🟡 |
.github/scripts/notification_manager.py |
53.58 | 🟡 |
.github/scripts/prometheus_exporter.py |
54.96 | 🟡 |
.github/scripts/weekly_digest.py |
55.02 | 🟡 |
.github/scripts/llm_router.py |
55.19 | 🟡 |
core/audit_logger.py |
55.6 | 🟡 |
.github/scripts/gather_context.py |
56.0 | 🟡 |
.github/scripts/batch_scan_dependabot.py |
56.3 | 🟡 |
core/llm_provider.py |
56.32 | 🟡 |
.github/scripts/streaming_results.py |
56.64 | 🟡 |
.github/scripts/setup_branch_protection.py |
57.0 | 🟡 |
.github/scripts/optimized_github_client.py |
58.27 | 🟡 |
agents/orchestrator_agent.py |
59.02 | 🟡 |
core/incident_freeze.py |
59.67 | 🟡 |
core/github_client.py |
61.96 | 🟡 |
core/message_queue.py |
63.22 | 🟡 |
core/agent_config.py |
63.82 | 🟡 |
autopilot/decisions/ledger.py |
63.92 | 🟡 |
core/idempotency.py |
64.45 | 🟡 |
Maintainability Index Guide:
- 🟢 85-100: Excellent maintainability
- 🟡 65-84: Good maintainability
- 🟠 50-64: Moderate maintainability (consider refactoring)
- 🔴 0-49: Poor maintainability (needs refactoring)
🔴 Risk Assessment: HIGH (6.0/10)Analysed 4 files, 286+ / 8− lines. Security-sensitive paths detected. Test coverage unchanged or improved. Scoring breakdown
|
Code Quality Analysis ❌ FAILEDDuration: 0.02s Tool Results
View detailed results{
"timestamp": "2026-08-23 20:31:54",
"elapsed_seconds": 0.02,
"summary": {
"total_issues": 10,
"critical": 0,
"high": 0,
"medium": 0,
"low": 0
},
"tools": {
"pylint": {
"status": "failed",
"output": "",
"errors": "Pylint error: [Errno 2] No such file or directory: 'pylint'"
},
"flake8": {
"status": "failed",
"output": "",
"errors": "Flake8 error: [Errno 2] No such file or directory: 'flake8'"
},
"bandit": {
"status": "failed",
"output": "",
"errors": "Bandit error: [Errno 2] No such file or directory: 'bandit'"
},
"radon_cc": {
"status": "failed",
"output": "",
"errors": "Radon error: [Errno 2] No such file or directory: 'radon'"
},
"radon_mi": {
"status": "failed",
"output": "",
"errors": "Radon MI error: [Errno 2] No such file or directory: 'radon'"
}
},
"passed": false
} |
🤖 Elite AI Copilot AnalysisElite AI Copilot Analysis ReportGenerated: 2026-08-23 20:31:55 🎯 Health Score: 100.0/100🚀 Top Recommendations
📊 Detailed InsightsCode Quality Baseline Established
Security Scan Initiated
Repository Structure Analyzed
Performance Baseline Captured
Documentation Structure Good
Powered by Elite AI Copilot v1.0 |
🔒 Security Scan Results🛡️ Bandit Security Scan
📦 Dependency Vulnerabilities
Vulnerable Dependencies:
Security scans run automatically on every PR. View detailed reports in the Actions tab. |
🔍 Pre-commit Checks✅ All pre-commit checks passed! Your code follows the project style guidelines. Pre-commit hooks help maintain code quality and consistency. |
Freshness for the Slack gate is measured from the Event Router's newest execution. A DRC-only failure therefore gates on an unrelated timestamp and may be suppressed from Slack. The job still exits 1, so the Actions signal is unaffected. Documented rather than fixed: tracking freshness per workflow is a v2 change, and the live data (DRC 20/20 success) means this path is currently unexercised. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NRTNCNBz1151WunaGhfuD
📊 Code Complexity AnalysisSummary:
|
| File | Function | Complexity | Line |
|---|---|---|---|
core/risk_scorer.py |
score_pull_request |
35 | 141 |
autopilot/autopilot.py |
generate_summary |
24 | 195 |
autopilot/staleness_engine.py |
process_stale_prs |
16 | 281 |
autopilot/ai_optimization/performance_monitor.py |
get_benchmark_stats |
15 | 184 |
.github/scripts/batch_scan_dependabot.py |
main |
15 | 64 |
.github/scripts/weekly_digest.py |
build_blocks |
15 | 38 |
autopilot/recommendation_contract.py |
validate |
14 | 54 |
.github/scripts/workflow_monitor.py |
get_workflow_statistics |
14 | 216 |
.github/scripts/metrics_collector.py |
parse_workflow_metrics |
14 | 148 |
.github/scripts/setup_branch_protection.py |
main |
14 | 240 |
... and 19 more
Recommendations:
- Break down large functions into smaller, focused units
- Extract complex conditional logic into separate functions
- Use early returns to reduce nesting
🔧 Low Maintainability Files
These files have low maintainability scores and may need refactoring:
| File | Score | Status |
|---|---|---|
.github/scripts/health_dashboard_generator.py |
28.14 | 🔴 |
.github/scripts/workflow_monitor.py |
33.73 | 🔴 |
.github/scripts/ai_code_suggestor.py |
33.76 | 🔴 |
.github/scripts/ai_workflow_optimizer.py |
35.51 | 🔴 |
.github/scripts/performance_benchmark.py |
39.46 | 🔴 |
.github/scripts/self_healing_system.py |
40.27 | 🔴 |
.github/scripts/threshold_monitor.py |
41.13 | 🔴 |
.github/scripts/parallel_code_analyzer_optimized.py |
41.16 | 🔴 |
autopilot/tests/test_recommendation_contract.py |
42.05 | 🔴 |
autopilot/autopilot.py |
42.45 | 🔴 |
autopilot/ai_optimization/anomaly_detector.py |
42.56 | 🔴 |
.github/scripts/refactoring_assistant.py |
43.03 | 🔴 |
autopilot/ai_optimization/intelligent_cache.py |
43.28 | 🔴 |
autopilot/ai_optimization/commit_summarizer.py |
44.05 | 🔴 |
.github/scripts/async_parallel_analyzer.py |
44.47 | 🔴 |
autopilot/ai_optimization/performance_monitor.py |
44.69 | 🔴 |
.github/scripts/badge_generator.py |
45.28 | 🔴 |
.github/scripts/copilot_integration.py |
45.37 | 🔴 |
.github/scripts/distributed_monitoring.py |
45.53 | 🔴 |
autopilot/dependency_graph.py |
45.65 | 🔴 |
.github/scripts/elite_copilot.py |
45.69 | 🔴 |
.github/scripts/issue_auto_creator.py |
46.39 | 🔴 |
.github/scripts/cost_calculator.py |
46.4 | 🔴 |
.github/scripts/inline_pr_commenter.py |
46.63 | 🔴 |
.github/scripts/complexity_reporter.py |
46.78 | 🔴 |
.github/scripts/pr_triage.py |
47.13 | 🔴 |
core/risk_scorer.py |
48.15 | 🔴 |
autopilot/ai_optimization/nlp_relevance_filter.py |
48.43 | 🔴 |
.github/scripts/pr_inline_commenter.py |
48.47 | 🔴 |
.github/scripts/dependency_audit.py |
48.7 | 🔴 |
autopilot/staleness_engine.py |
48.73 | 🔴 |
.github/scripts/metrics_collector.py |
48.91 | 🔴 |
.github/scripts/dependency_updater.py |
48.91 | 🔴 |
autopilot/ai_optimization/ml_priority_scorer.py |
49.53 | 🔴 |
.github/scripts/parallel_code_analyzer.py |
49.96 | 🔴 |
autopilot/ai_optimization/api_optimizer.py |
50.46 | 🟡 |
.github/scripts/workflow_optimizer.py |
51.67 | 🟡 |
.github/scripts/cot_selector.py |
51.73 | 🟡 |
.github/scripts/release_manager.py |
51.92 | 🟡 |
.github/scripts/check_quality.py |
52.33 | 🟡 |
.github/scripts/auto_pr.py |
52.72 | 🟡 |
.github/scripts/changelog_generator.py |
53.13 | 🟡 |
.github/scripts/notification_manager.py |
53.58 | 🟡 |
.github/scripts/prometheus_exporter.py |
54.96 | 🟡 |
.github/scripts/weekly_digest.py |
55.02 | 🟡 |
.github/scripts/llm_router.py |
55.19 | 🟡 |
core/audit_logger.py |
55.6 | 🟡 |
.github/scripts/gather_context.py |
56.0 | 🟡 |
.github/scripts/batch_scan_dependabot.py |
56.3 | 🟡 |
core/llm_provider.py |
56.32 | 🟡 |
.github/scripts/streaming_results.py |
56.64 | 🟡 |
.github/scripts/setup_branch_protection.py |
57.0 | 🟡 |
.github/scripts/optimized_github_client.py |
58.27 | 🟡 |
agents/orchestrator_agent.py |
59.02 | 🟡 |
core/incident_freeze.py |
59.67 | 🟡 |
core/github_client.py |
61.96 | 🟡 |
core/message_queue.py |
63.22 | 🟡 |
core/agent_config.py |
63.82 | 🟡 |
autopilot/decisions/ledger.py |
63.92 | 🟡 |
core/idempotency.py |
64.45 | 🟡 |
Maintainability Index Guide:
- 🟢 85-100: Excellent maintainability
- 🟡 65-84: Good maintainability
- 🟠 50-64: Moderate maintainability (consider refactoring)
- 🔴 0-49: Poor maintainability (needs refactoring)
🔴 Risk Assessment: HIGH (7.0/10)Analysed 4 files, 294+ / 8− lines. Security-sensitive paths detected. Test coverage unchanged or improved. Scoring breakdown
|
🤖 Elite AI Copilot AnalysisElite AI Copilot Analysis ReportGenerated: 2026-08-23 20:37:10 🎯 Health Score: 100.0/100🚀 Top Recommendations
📊 Detailed InsightsCode Quality Baseline Established
Security Scan Initiated
Repository Structure Analyzed
Performance Baseline Captured
Documentation Structure Good
Powered by Elite AI Copilot v1.0 |
Code Quality Analysis ❌ FAILEDDuration: 0.02s Tool Results
View detailed results{
"timestamp": "2026-08-23 20:37:13",
"elapsed_seconds": 0.02,
"summary": {
"total_issues": 10,
"critical": 0,
"high": 0,
"medium": 0,
"low": 0
},
"tools": {
"pylint": {
"status": "failed",
"output": "",
"errors": "Pylint error: [Errno 2] No such file or directory: 'pylint'"
},
"flake8": {
"status": "failed",
"output": "",
"errors": "Flake8 error: [Errno 2] No such file or directory: 'flake8'"
},
"bandit": {
"status": "failed",
"output": "",
"errors": "Bandit error: [Errno 2] No such file or directory: 'bandit'"
},
"radon_cc": {
"status": "failed",
"output": "",
"errors": "Radon error: [Errno 2] No such file or directory: 'radon'"
},
"radon_mi": {
"status": "failed",
"output": "",
"errors": "Radon MI error: [Errno 2] No such file or directory: 'radon'"
}
},
"passed": false
} |
🔍 Pre-commit Checks✅ All pre-commit checks passed! Your code follows the project style guidelines. Pre-commit hooks help maintain code quality and consistency. |
🔒 Security Scan Results🛡️ Bandit Security Scan
📦 Dependency Vulnerabilities
Vulnerable Dependencies:
Security scans run automatically on every PR. View detailed reports in the Actions tab. |
`pre-commit run --all-files` scans the whole repo, so it can modify files a PR never touched. The auto-fix step staged only PR-diff files (correct, per #213) and then ran `git commit` unconditionally. When the hooks had touched ONLY files outside the diff, nothing was staged, `git commit` exited 1, and because steps run under `bash -e` the whole step died - reddening PRs that did not cause the violation. Reproduced before fixing: old logic exits 1 with 'no changes added to commit'. New logic exits 0 and skips the commit. Changes: - split hook-modified files into in-PR and outside-PR sets - guard the commit with `git diff --cached --quiet` (the actual defect: a file can be in the PR diff yet untouched by hooks, so the add stages nothing) - surface outside-PR files as a warning naming them, so a failure here is diagnosable as pre-existing repo debt rather than the author's change #213's protection is preserved and verified: with hooks touching both an in-PR and an out-of-PR file, only the in-PR file is committed. Follow-up promised in #262. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013JdYCSp4nmVgsNWuwNkUaZ
* fix(ci): stop pre-commit auto-fix failing when nothing is staged `pre-commit run --all-files` scans the whole repo, so it can modify files a PR never touched. The auto-fix step staged only PR-diff files (correct, per #213) and then ran `git commit` unconditionally. When the hooks had touched ONLY files outside the diff, nothing was staged, `git commit` exited 1, and because steps run under `bash -e` the whole step died - reddening PRs that did not cause the violation. Reproduced before fixing: old logic exits 1 with 'no changes added to commit'. New logic exits 0 and skips the commit. Changes: - split hook-modified files into in-PR and outside-PR sets - guard the commit with `git diff --cached --quiet` (the actual defect: a file can be in the PR diff yet untouched by hooks, so the add stages nothing) - surface outside-PR files as a warning naming them, so a failure here is diagnosable as pre-existing repo debt rather than the author's change #213's protection is preserved and verified: with hooks touching both an in-PR and an out-of-PR file, only the in-PR file is committed. Follow-up promised in #262. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013JdYCSp4nmVgsNWuwNkUaZ * test(ci): temporary probe to exercise the auto-fix path Deliberate trailing whitespace + missing EOF newline, so trailing-whitespace and end-of-file-fixer both fire and the rewritten auto-fix step actually RUNS in CI instead of being skipped. Removed again before merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style: auto-fix pre-commit issues [skip ci] * test(ci): remove the temporary auto-fix probe Its job is done: it forced the rewritten auto-fix step to actually execute in CI (run 32685272621, step 7 -> success, commit 70336b7), proving $RUNNER_TEMP, comm and xargs -d behave on the real runner rather than only in local tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…lth-check Co-authored-by: labgadget015-dotcom <232155002+labgadget015-dotcom@users.noreply.github.com>
… n8n-health-check (#269) * Initial plan * fix(ci): address PR #262 review comments on intake-canary and n8n-health-check Co-authored-by: labgadget015-dotcom <232155002+labgadget015-dotcom@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: labgadget015-dotcom <232155002+labgadget015-dotcom@users.noreply.github.com>
Why
GitHub→DRC intake has died silently twice:
Normalise Issue Payload(2026-07-23, undetected 7 days) and the HMAC secret (2026-08-22, issue #259 — still live right now). Both times, every signal we had reported green.The blind spot, verified
The n8n Webhook node answers
onReceivedbefore the workflow runs. GitHub records a success; the workflow errors asynchronously afterwards.Verified 2026-08-23 against live APIs:
06:12:29200 OK6970= errorn8n-health-check.ymlDRC probe200 OK200 OKThe
Route Health Pingshort-circuit added 2026-07-06 to stop false positives is what manufactured this false negative.Note DRC itself shows 20/20 success — it's healthy because it's only answering health pings and receiving no real work. That's the failure mode in one line.
What this adds
intake-canary.yml, every 30 min:startedAfter— verified.)Anti-silent-pass hardening
This is the category of check where silent passes hide, so:
N8N_API_KEYfails loudly. Without the guard: empty header → 401 →jq '.data | length'on the error body →0→ "zero errors, healthy". Verified against the live API — this trap is real.canary_broken(needs a developer) vspipeline_down(needs hands in the n8n UI). Job fails withexit 1regardless; Slack is fire-and-forget so job status is the backstop.Also
n8n-health-check.ymlamended to stop claiming pipeline health it cannot measure — relabelled as a reachability check, points at the canary as authoritative. Kept, not deleted: "is n8n Cloud serving HTTP" is still a real signal. A green check sitting next to a red one wins arguments it shouldn't.Verification
Ran the probe script locally against the live API:
N8N_API_KEYmissingcanary_broken, exit 1 ✅canary_broken, exit 1 — not a green pass ✅pipeline_down, streak 20, exit 1 ✅N8N_API_KEYas a repo secret (value: the working key at~/.config/n8n_watchdog/api_key). The canary fails loudly until then — by design.Expected first run after merge: RED, because the pipeline genuinely is down. That's the acceptance test.
Known gap (deliberate, v1)
Detects
error-status executions. A success-status logic failure — events routed down a wrong branch, DRC never triggered — still slips through. A true end-to-end synthetic-issue probe costs LLM spend per run and may be eaten by the Event Router's bot-sender filter. Documented in the file header; out of scope until error-class detection is proven in production.🤖 Generated with Claude Code
https://claude.ai/code/session_014NRTNCNBz1151WunaGhfuD
Added scope: two pre-existing CI blockers (found while verifying this PR's own checks)
Pre-commit Checkswas failing on every PR in this repo, for reasons unrelated to the PR under test. Both defects trace tochangelog_generator.py:end-of-file-fixerrewroteCHANGELOG.mdon every later PR. BecauseCHANGELOG.mdisn't in the PR's diff,pre-commit-ci.yml's auto-fix step (correctly, per fix(ci): scope pre-commit auto-fix to PR-diff files only #213) staged nothing,git commitexited 1, andset -efailed the step.markdownlint MD012on lines 7/10/383, pre-existing onmain.Neither is ever caught at the source, because
changelog.ymlcommits the file with a skip-ci marker so pre-commit never runs on it. The cost lands on unrelated PRs.Both fixed in the generator and normalised in the current file. Verified by regenerating: no MD012, exactly one trailing newline. 17 unit tests pass.
mainregeneratedCHANGELOG.mdmid-review (4945bd4), reintroducing both defects and conflicting this PR -- the bug demonstrating itself. Rebased ontomainand re-normalised.Not fixed here -- recommended follow-up:
pre-commit-ci.yml's auto-fix step still fails whenever any hook touches a file outside the PR diff. This PR removes today's trigger, not the underlying fragility. A|| echo "nothing staged"guard on thatgit commitwould close it, but that file has a footgun history (#213) and deserves its own PR.Live confirmation of the outage
Opening this PR at 20:02 fired three
pull_requestwebhooks. All three errored on the HMAC gate (executions7009,7010,7011). This PR's own events were dropped by the outage it exists to detect.