santifer/career-ops · critical · Error
Malformed states file at ${statesPath}: expected a top-level
Error message
Malformed states file at ${statesPath}: expected a top-level "states" list What it means
loadLifecycle() in tracker-sync-check.mjs reads templates/states.yml (the canonical source for tracker states and lifecycle order), parses it with the yaml library, and expects a top-level `states` array. If the document is null, not an object, or lacks an array `states` key, it throws an Error identifying the file. This is a config-integrity guard.
Source
Thrown at tracker-sync-check.mjs:113
// against every other status) instead of correctly ranking it as terminal.
//
// states.yml has no explicit ordering field, so LIFECYCLE_ORDER is taken from
// the file's own array order among states NOT marked `terminal: true`; the
// terminal set and the id -> display-label map are read directly off each
// state's `terminal` and `label` fields. See the comment above `states:` in
// templates/states.yml for the contract.
const STATES_FILE = join(CAREER_OPS, 'templates/states.yml');
/**
* Load the canonical lifecycle order, terminal-status set, and id -> label
* map from templates/states.yml.
* @param {string} statesPath - Path to templates/states.yml.
* @returns {{ order: string[], terminal: Set<string>, labels: Record<string,string> }}
*/
export function loadLifecycle(statesPath) {
const doc = yaml.load(readFileSync(statesPath, 'utf-8'));
if (!doc || !Array.isArray(doc.states)) {
throw new Error(`Malformed states file at ${statesPath}: expected a top-level "states" list`);
}
const order = [];
const terminal = new Set();
const labels = {};
for (const s of doc.states) {
const id = String(s?.id ?? '').trim();
if (!id) continue;
labels[id] = String(s.label ?? id);
if (s.terminal) terminal.add(id);
else order.push(id);
}
return { order, terminal, labels };
}
const { order: LIFECYCLE_ORDER, terminal: TERMINAL_STATUSES, labels: CANONICAL_LABELS } = loadLifecycle(STATES_FILE);
// Mirrors the ALIASES map in analyze-patterns.mjs / verify-pipeline.mjs —
// applications.md status cell normalization (bold markers, trailing dates,View on GitHub (pinned to 9b17a8ac97)
Solutions
- Restore states.yml from the repo: git checkout HEAD -- templates/states.yml.
- Re-run node update-system.mjs apply to restore system files.
- Validate the YAML: ensure it has `states:` as a top-level key whose value is a list of objects with id/label/terminal fields.
- Run node verify-pipeline.mjs after restoring to confirm integrity.
Example fix
# before (broken states.yml)
statuses:
- id: applied
# after (correct schema)
states:
- id: Applied
label: Applied
terminal: false Defensive patterns
Strategy: validation
Validate before calling
function validateStatesYml(statesPath) {
const doc = yaml.load(readFileSync(statesPath, 'utf-8'));
if (!doc || !Array.isArray(doc.states)) {
throw new Error(`states.yml at ${statesPath} is missing a top-level 'states' list`);
}
return doc;
}
validateStatesYml(STATES_FILE); Try / catch
try {
const lifecycle = loadLifecycle(STATES_FILE);
} catch (err) {
if (err.message.includes('Malformed states file')) {
console.error(`${err.message}\nRestore: git checkout HEAD -- templates/states.yml`);
process.exit(1);
}
throw err;
} Prevention
- Never hand-edit templates/states.yml — it is a system file.
- Add node verify-pipeline.mjs to CI/pre-commit to catch states.yml corruption early.
- Run node update-system.mjs apply after updates.
When it happens
Trigger: templates/states.yml is empty, contains valid YAML that is not an object (e.g. a bare scalar or list), or has a different top-level key (e.g. `statuses` instead of `states`). Hand-editing the file and breaking its structure triggers this.
Common situations: A user hand-edits states.yml and accidentally changes the top-level key or truncates the file; a merge conflict left unresolved or resolved incorrectly; a tool rewrote the YAML with a different schema.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed states file at ${statesPath}: expected a top-level
- ROW_NOT_FOUND
- Cannot read benchmarks at ${path}: ${err.message}
- Malformed benchmarks file at ${path}: expected a top-level "
- arbeitsagentur: entry "${entry.name || '(unnamed)'}" has no
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/e4eee8f2aaf7744f.
Report an issue: GitHub.