Validate aeon.yml and .github/workflows/aeon.yml for structural invariants that have caused past outages — checkout step ordering, duplicate skill keys, missing skill files
Scanned 9/5/2026
Install to Claude Code
npx -y skills add anajuliabit/aeon --skill config-validator --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Config Validator?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anajuliabit-config-validator)More formats (shields.io, HTML) on the badges page.
---
name: Config Validator
category: meta
description: Validate aeon.yml and .github/workflows/aeon.yml for structural invariants that have caused past outages — checkout step ordering, duplicate skill keys, missing skill files
tags: [meta, dev]
---
Today is ${today}. Your task is to validate the structural correctness of `aeon.yml` and `.github/workflows/aeon.yml`.
This skill exists because two incident classes have caused major outages:
- **Checkout-ordering class**: `Early checkout` step missing or conditionally gated — can cause every skill to fail in a single run.
- **Duplicate-key class**: duplicate YAML keys in `aeon.yml` — silently disable any skill whose key is shadowed.
The same checks also run as a pre-merge CI workflow at `.github/workflows/ci-config-validate.yml` (via `scripts/validate-config.js`) — that workflow blocks PRs that break these invariants. This skill is the **weekly safety net** for state that drifts on `main` outside of PRs (manual edits, scheduled rewrites, post-process commits). Run all checks. Report findings. Alert if any fail.
### Fast path — invoke the shared validator
The fastest, most consistent way to run all three checks is the shared script the CI workflow uses:
```bash
node scripts/validate-config.js
```
Exit code 0 = CLEAN (no notification needed). Non-zero exit + `FAIL[*]:` lines on stdout = ISSUES (skip to step 4).
If the script is unavailable for any reason, fall back to the manual checks in steps 1–3.
## Steps
### 1. Check workflow step ordering (checkout-ordering class)
Read `.github/workflows/aeon.yml`.
Find the `jobs.run.steps` array. Verify:
a. A step named `Early checkout` (or `uses: actions/checkout`) exists.
b. That step has **no** `if:` condition — it must run unconditionally.
c. That step is positioned **before** any step that has an `if:` condition.
Use node inline to parse and check:
```bash
node -e "
const fs = require('fs');
const text = fs.readFileSync('.github/workflows/aeon.yml', 'utf8');
const lines = text.split('\n');
let inSteps = false, stepDepth = 0;
let steps = [], cur = null, lineNum = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
if (/^\s{4,6}steps:/.test(line)) { inSteps = true; continue; }
if (!inSteps) continue;
if (/^\s{4,6}[a-z]/.test(line) && !/^\s{6,}/.test(line) && i > 0) { inSteps = false; continue; }
if (/^\s{6}- /.test(line)) {
if (cur) steps.push(cur);
cur = { lineNum: i + 1, name: null, hasIf: false, isCheckout: false };
}
if (cur) {
if (/name:/.test(trimmed)) cur.name = trimmed.replace(/^name:\s*/, '').replace(/[\"']/g, '');
if (/uses:\s*actions\/checkout/.test(trimmed)) cur.isCheckout = true;
if (/Early checkout/.test(trimmed)) cur.isCheckout = true;
if (/^\s{6}if:/.test(line)) cur.hasIf = true;
}
}
if (cur) steps.push(cur);
let issues = [];
const checkoutIdx = steps.findIndex(s => s.isCheckout);
if (checkoutIdx === -1) {
issues.push('FAIL: No checkout step (actions/checkout or Early checkout) found in jobs.run.steps');
} else {
const cs = steps[checkoutIdx];
if (cs.hasIf) {
issues.push('FAIL: Checkout step at line ' + cs.lineNum + ' has an if: condition — must be unconditional');
}
const firstConditional = steps.findIndex(s => s.hasIf);
if (firstConditional !== -1 && firstConditional < checkoutIdx) {
issues.push('FAIL: Checkout step appears after a conditional step at line ' + steps[firstConditional].lineNum);
}
if (issues.length === 0) {
console.log('PASS checkout: Early checkout is unconditional and first (line ' + cs.lineNum + ')');
}
}
if (issues.length > 0) { issues.forEach(i => console.log(i)); process.exit(1); }
"
```
If the check fails, record the finding. Continue to next check regardless.
---
### 2. Check for duplicate skill keys (duplicate-key class)
Read `aeon.yml`. Scan the `skills:` block for duplicate top-level keys.
```bash
node -e "
const fs = require('fs');
const text = fs.readFileSync('aeon.yml', 'utf8');
const lines = text.split('\n');
let inSkills = false;
const seen = {};
const dupes = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/^skills:/.test(line)) { inSkills = true; continue; }
if (inSkills && /^[a-z]/.test(line)) { inSkills = false; continue; }
if (!inSkills) continue;
const match = line.match(/^ ([a-z][a-z0-9-]+):/);
if (match) {
const key = match[1];
if (seen[key]) {
dupes.push('FAIL: Duplicate skill key \"' + key + '\" at line ' + (i+1) + ' (first seen line ' + seen[key] + ')');
} else {
seen[key] = i + 1;
}
}
}
if (dupes.length > 0) { dupes.forEach(d => console.log(d)); process.exit(1); }
else { console.log('PASS duplicates: no duplicate skill keys found (' + Object.keys(seen).length + ' skills)'); }
"
```
If duplicates are found, record them. Continue to next check regardless.
---
### 3. Check all enabled skills have SKILL.md (missing-file class)
Read `aeon.yml`. For every skill with `enabled: true`, verify `skills/<name>/SKILL.md` exists.
```bash
node -e "
const fs = require('fs');
const text = fs.readFileSync('aeon.yml', 'utf8');
const lines = text.split('\n');
let inSkills = false;
const issues = [];
const ok = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/^skills:/.test(line)) { inSkills = true; continue; }
if (inSkills && /^[a-z]/.test(line)) { inSkills = false; continue; }
if (!inSkills) continue;
const match = line.match(/^ ([a-z][a-z0-9-]+):\s*\{(.+)\}/);
if (match) {
const name = match[1];
const props = match[2];
if (/enabled:\s*true/.test(props)) {
const skillFile = 'skills/' + name + '/SKILL.md';
if (!fs.existsSync(skillFile)) {
issues.push('WARN: enabled skill \"' + name + '\" has no SKILL.md at ' + skillFile);
} else {
ok.push(name);
}
}
}
}
if (issues.length > 0) { issues.forEach(i => console.log(i)); }
console.log('PASS skill-files: ' + ok.length + ' enabled skills have SKILL.md' + (issues.length > 0 ? ', ' + issues.length + ' missing' : ''));
if (issues.length > 0) process.exit(1);
"
```
---
### 4. Summarize findings
After running all three checks, collect results:
- Count PASSes and FAILs
- If all checks passed: **status = CLEAN**
- If any FAIL or WARN: **status = ISSUES**
---
### 5. Decide whether to notify
- **CLEAN**: Log only, no notification. Silent runs are expected — the value is the alert when something breaks.
- **ISSUES**: Send notification via `./notify`.
If ISSUES, write notification to `.pending-notify-temp/config-validator-${today}.md` then send with `./notify -f`:
```
*Config Validator — ${today}*
STATUS: ISSUES FOUND
[list each finding, one per line]
These invariants have caused full outages before.
Check aeon.yml and .github/workflows/aeon.yml immediately.
log: memory/logs/${today}.md
```
---
### 6. Log results
Append to `memory/logs/${today}.md`:
```
## Config Validator
- **Status:** CLEAN / ISSUES
- **Checkout step:** PASS / FAIL — [detail]
- **Duplicate keys:** PASS / FAIL — [detail]
- **Skill files:** PASS / N warnings — [detail]
- **Notification:** sent / skipped (clean)
```
## Sandbox Note
All checks use local file reads only — no external network calls needed. No prefetch/postprocess wrapper required.
## Environment Variables Required
None — reads only local files.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!