Skip to content

fix(cron): guard load_jobs against malformed jobs.json shapes#23002

Open
KhanCold wants to merge 1 commit into
NousResearch:mainfrom
KhanCold:fix/cron-load-jobs-malformed-guard
Open

fix(cron): guard load_jobs against malformed jobs.json shapes#23002
KhanCold wants to merge 1 commit into
NousResearch:mainfrom
KhanCold:fix/cron-load-jobs-malformed-guard

Conversation

@KhanCold

Copy link
Copy Markdown

Summary

Fixes #22569.

cron.jobs.load_jobs called data.get("jobs", []) immediately after json.load(), assuming the parsed root is always a dict. If jobs.json is hand-edited or written by a legacy release in any non-dict shape — a bare list ([{"id": "x"}]), null, a scalar, or a string — the call crashes with AttributeError.

Changes

  • Guard both json.load() and json.loads(strict=False) fallback paths with isinstance(data, dict) checks.
  • If data is a bare list (legacy format), auto-migrate by calling save_jobs() and return the list.
  • If data is any other non-dict shape, log an error and return [] instead of crashing.

Test Plan

Added regression tests in tests/cron/test_cron_load_jobs_malformed.py covering:

  1. Bare list auto-migration
  2. null root → empty list
  3. String root → empty list
  4. Number root → empty list
  5. Normal dict shape → works as before
  6. Empty dict → empty list
python3 -m unittest tests.cron.test_cron_load_jobs_malformed -v
# 6 passed

@alt-glitch alt-glitch added type/bug Something isn't working comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists labels May 10, 2026
@liuhao1024

Copy link
Copy Markdown
Contributor

Review: Tests read source from a hardcoded /tmp/hermes-agent-fork/ path

Several new test files open source files from a fixed path that will not exist in CI:

# tests/hermes_cli/test_split_guard.py
with open("/tmp/hermes-agent-fork/cli.py") as f:
    source = f.read()

# tests/hermes_cli/test_banner_cprint_fallback.py
with open("/tmp/hermes-agent-fork/hermes_cli/banner.py") as f:
    source = f.read()

# tests/hermes_cli/test_gemini_doctor_probe.py
with open("/tmp/hermes-agent-fork/hermes_cli/doctor.py") as f:
    source = f.read()

Why this breaks: CI runners do not have /tmp/hermes-agent-fork/. Even locally, the directory may contain a stale checkout that does not reflect the changes in this PR. These tests will always pass on the author's machine and always fail everywhere else.

Fix options (pick one):

  1. Import the module and use inspect.getsource — the most Pythonic approach:

    import inspect
    from hermes_cli import banner
    source = inspect.getsource(banner.cprint)
  2. Use pathlib.Path(__file__) to locate the repo root relative to the test:

    repo_root = Path(__file__).resolve().parents[2]  # tests/hermes_cli/ → repo root
    source = (repo_root / "hermes_cli" / "banner.py").read_text()
  3. Test the actual behavior instead of source text — e.g., mock _pt_print to raise and verify cprint falls back to print():

    def test_cprint_falls_back_on_error(capsys):
        with patch("hermes_cli.banner._pt_print", side_effect=Exception("no console")):
            banner.cprint("hello")
        assert "hello" in capsys.readouterr().out

Option 3 is preferred where feasible — it tests the contract, not the implementation.

The same pattern appears in PRs #23018, #23017, #23011, #23008, and #23000.

…search#22569)

cron.jobs.load_jobs called data.get("jobs", []) immediately after
json.load(), assuming the root is always a dict. If jobs.json is
hand-edited or written by a legacy release in a non-dict shape
(bare list, null, scalar, string), the call crashes with AttributeError.

Changes:
- Guard both json.load() and json.loads(strict=False) paths with
  isinstance(data, dict) checks.
- If data is a bare list (legacy format), auto-migrate by calling
  save_jobs() and return the list.
- If data is any other non-dict shape, log an error and return []
  instead of crashing.

Fixes NousResearch#22569
@KhanCold KhanCold force-pushed the fix/cron-load-jobs-malformed-guard branch from cc94667 to 6491bf9 Compare May 10, 2026 12:00
@KhanCold

Copy link
Copy Markdown
Author

Thanks for the review. The test file in this PR (tests/cron/test_cron_load_jobs_malformed.py) does not use any hardcoded /tmp/hermes-agent-fork/ paths — it uses tempfile.TemporaryDirectory() for isolation.

The examples in your review appear to reference other PRs. If there are specific changes needed in this PR, please let me know and I'll address them promptly.

@KhanCold

Copy link
Copy Markdown
Author

Hi @liuhao1024 — I replied to your review above confirming that the hardcoded path concern does not apply to this PR (it uses tempfile for isolation). If everything looks good, would appreciate a review or approval. Thanks! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants