fix(cron): persist the junk-entry repair when no valid job remains; log types only

An all-invalid list filtered to [] skipped save_jobs (the 'if jobs and repair' guard), so every tick repeated the warning. Save whenever a repair ran (the shrink-merge still keeps concurrent valid jobs), and log the dropped entries' types instead of their raw values.

(cherry picked from commit 3b5b1aa61332cf79554fc2a24ae925908c37fe5d)
This commit is contained in:
John Paul Soliva
2026-09-26 11:41:03 +09:00
committed by kshitij
parent 695766e3f9
commit e66e351cf5
2 changed files with 21 additions and 3 deletions

View File

@@ -1377,13 +1377,17 @@ def load_jobs() -> List[Dict[str, Any]]:
if isinstance(jobs, list) and not all(isinstance(j, dict) for j in jobs):
# Every reader and the due scan index records as dicts: one junk entry would crash the
# whole tick and freeze every healthy sibling job, so skip it like the id-keyed map does.
# Types only: the raw values are arbitrary file content and must not reach the logs.
junk = [j for j in jobs if not isinstance(j, dict)]
logger.warning(
"Skipping %d non-object entr%s in jobs.json: %s",
len(junk), "y" if len(junk) == 1 else "ies", ", ".join(map(repr, junk[:5])))
"Skipping %d non-object entr%s in jobs.json (types: %s)",
len(junk), "y" if len(junk) == 1 else "ies",
", ".join(sorted({type(j).__name__ for j in junk})))
jobs = [j for j in jobs if isinstance(j, dict)]
repair = repair or "non-object entries dropped"
if jobs and repair:
# Persist even an empty result, or an all-junk store repeats the repair on every tick; the
# save's shrink-merge still keeps any valid job a sibling wrote meanwhile.
if repair:
save_jobs(jobs)
logger.warning("Auto-repaired jobs.json (%s)", repair)
_record_load_stamp(pre_read_stamp)

View File

@@ -1735,6 +1735,20 @@ class TestJobsJsonIdKeyedMap:
on_disk = json.loads(JOBS_FILE.read_text(encoding="utf-8"))
assert [j["id"] for j in on_disk["jobs"]] == [job["id"]]
def test_all_junk_list_is_repaired_on_disk_without_logging_values(self, tmp_cron_dir, caplog):
"""With no valid job left the repair must still persist (else every tick repeats it), and
the warning must not copy raw file content into the logs."""
import json
from cron.jobs import JOBS_FILE, ensure_dirs
ensure_dirs()
JOBS_FILE.write_text(json.dumps({"jobs": [None, "sk-leaked-value", 42]}), encoding="utf-8")
with caplog.at_level("WARNING", logger="cron.jobs"):
assert list_jobs(include_disabled=True) == []
assert json.loads(JOBS_FILE.read_text(encoding="utf-8"))["jobs"] == []
assert "sk-leaked-value" not in caplog.text