Skip to content

Add ox_health --format json for machine-readable output - #41

Merged
PhiLily merged 4 commits into
oxpull:mainfrom
burakeyler:issue-36-health-json
Sep 15, 2026
Merged

PhiLily merged 4 commits into
oxpull:mainfrom
burakeyler:issue-36-health-json

Conversation

@burakeyler

Copy link
Copy Markdown
Contributor

Closes #36

Output

python manage.py ox_health --format json --max-backlog 1

{"ok": false, "queue": null, "backlog": 2, "oldest_age_seconds": 41.3, "last_claim_age_seconds": null, "problems": ["backlog is 2, over --max-backlog 1"]}
  • Same figures as the text line; ages are timedelta.total_seconds() floats, null where the text form says none.
  • problems holds the same strings the CommandError joins with "; ".

The question the issue asked to settle

On failure, JSON prints the object and still exits non-zero. handle() writes the object to stdout, then raises the same CommandError as before. So the exit status (and the stderr reason) are identical to text mode, and stdout always has a parseable object.

That includes database unreachable: the object is printed with backlog/ages null and problems: ["Database unreachable: ..."].

Only the reporting at the end of handle() changed. The default output and the checks themselves are untouched.

Tests (tests/test_health.py, 4 new)

  • OK run reports backlog/ages matching the fixtures and problems: [].
  • Empty database with --queue gives null ages and echoes the queue.
  • Two failing checks: the object has ok: false and both problems, and the raised CommandError message equals the joined problems.
  • Database unreachable still prints the object.

Gates (local, Windows, SQLite)

  • pytest -q tests/test_health.py: 45 passed.
  • ruff check ., ruff format --check ., mypy --strict src/, tools/check_release.py: pass.
  • Not run: mkdocs build --strict.
  • Docs: --format row in the ox_health table in docs/configuration.md.
  • Changelog: Unreleased → Added.

This is independent of #40 (ox_prune --queue); both touch CHANGELOG.md under Unreleased, so whichever lands second may need a trivial rebase.

🤖 Generated with Claude Code

@burakeyler

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up commit: docs/llms-full.txt was stale after the docs update in this PR, which failed test_llms_full_is_current across every matrix job (that's the whole CI matrix showing red — one shared cause, not a real per-environment failure). Ran python tools/build_llms_full.py and committed the regenerated file.

Verified locally:

  • pytest tests/test_llms_full.py — 3 passed
  • ruff check . — clean
  • mypy --strict src/ — clean

@PhiLily

PhiLily commented Sep 14, 2026

Copy link
Copy Markdown
Member

Thanks @burakeyler, a few things before I merge:

  1. Rebase on main. Add ox_prune --queue, to match ox_health --queue #40 is in, so CHANGELOG.md now conflicts. Both entries go under the same ### Added heading.
  2. Print the object for invalid values too. ox_health --format json --max-age 0 exits 1 with nothing on stdout. The same goes for --max-backlog -1 and --worker-timeout 0. A probe that parses stdout on a non-zero exit gets nothing to parse. These three should print the object (ok: false, figures null, the message in problems) and then raise the same CommandError, like the database-unreachable path does. Please add a test for one of them. Argparse errors like --format JSON can stay plain usage errors (exit 2).
  3. Add the --format row to the other two ox_health tables: the one in README.md and the one in docs/monitoring.md. They list the same flags as docs/configuration.md.
  4. The null wording. The docs say null means "nothing to measure", but backlog and the ages are also null when the check couldn't run (database unreachable, and after point 2, an invalid value). Please say that.
  5. Regenerate docs/llms-full.txt after the docs edits.

burakeyler and others added 3 commits September 15, 2026 03:05
A healthcheck or monitoring agent wanting the figures had to parse the OK
line or re-query the database. `--format json` prints one object with the
same figures (backlog, oldest age, last claim age) plus `ok` and the list of
problems. On a failing check the object is still printed before the same
CommandError, so the exit status keeps its meaning and the numbers are
there when they matter most. Text output is unchanged.

Closes oxpull#36

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rebases onto main (CHANGELOG entries combined under one Added heading).
ox_health --format json now prints the object (ok: false, figures null)
before raising CommandError on an invalid --max-backlog/--max-age/
--worker-timeout, matching the existing database-unreachable path.
Adds the --format row to README.md and docs/monitoring.md, clarifies
that null also covers a check that could not run, and regenerates
docs/llms-full.txt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@burakeyler

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — all five addressed in the latest push:

  1. Rebased on main. CHANGELOG.md now has both ox_prune --queue (Add ox_prune --queue, to match ox_health --queue #40) and ox_health --format json entries under the same ### Added heading.
  2. Invalid thresholds now print the object. --max-backlog, --max-age and --worker-timeout validation now goes through the same path as the database-unreachable case: with --format json, the object is printed first (ok: false, backlog/oldest_age_seconds/last_claim_age_seconds all null, the message in problems), then the same CommandError is raised. Argparse-level errors (e.g. --format JSON) are untouched and still exit 2 before handle() runs. Added test_json_rejects_a_bad_threshold_but_still_prints_the_object — confirmed it fails on the previous code (empty stdout, nothing to parse) and passes now.
  3. Added the --format row to the ox_health tables in README.md and docs/monitoring.md, matching docs/configuration.md.
  4. Reworded the null explanation in docs/configuration.md (and mirrored in the README/monitoring rows) to cover a check that couldn't run, not just "nothing to measure."
  5. Regenerated docs/llms-full.txt via tools/build_llms_full.py after the docs edits.

Verified locally:

  • pytest (SQLite, default settings) — 1016 passed, 35 skipped, 3 failed. The 3 failures (test_platform_signals.py, two in test_worker.py/test_timeouts.py) are pre-existing and unrelated to this change — signal.SIGHUP doesn't exist on Windows, which is the environment I ran this in. tests/test_health.py itself: 46/46 passed.
  • ruff check . and ruff format --diff . — clean.
  • mypy --strict src/ — clean.
  • mkdocs build --strict — not run; mkdocs isn't installed in this checkout's venv (not in pyproject.toml's dependency groups) and I didn't want to add a dependency just to verify docs. Happy to fix anything the CI docs build flags.

PostgreSQL test run also not done here (no local Postgres in this environment) — relying on CI for that leg.

@burakeyler

Copy link
Copy Markdown
Contributor Author

The one red job on the latest push is postgres py3.12 dj6.1, and it failed in tests/test_supervisor.py::TestOrphans::test_children_drain_when_the_supervisor_is_killed (wait_for(... not alive ..., timeout=10) timed out), with 1046 other tests passing. This PR doesn't touch the supervisor, and the same test passed in the other 26 matrix legs of the same run, so it looks like a timing flake on that runner. I can't re-run jobs on this repo; could you re-run the failed job when you get a chance? Happy to push an empty commit instead if you prefer.

The README, configuration and monitoring tables each described
--format in slightly different words, and only one named the cases
where the figures are null. All three now carry the same row.

The validation helper in handle() always raises, so it is typed
NoReturn rather than None.
@PhiLily
PhiLily merged commit 7d662e4 into oxpull:main Sep 15, 2026
30 checks passed
@PhiLily

PhiLily commented Sep 15, 2026

Copy link
Copy Markdown
Member

Thanks @burakeyler, merged. All five points landed cleanly, and the test for an invalid threshold was exactly what was missing. The red Postgres job was a flaky supervisor test, not your change.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ox_health --format json for machine-readable output

2 participants