Skip to content

fix(api): wrap readiness-probe filesystem ops in asyncio.to_thread (Phase 3 cleanup)#96

Merged
pcalnon merged 1 commit intomainfrom
fix/async-route-audit-phase-3-cleanup
May 6, 2026
Merged

fix(api): wrap readiness-probe filesystem ops in asyncio.to_thread (Phase 3 cleanup)#96
pcalnon merged 1 commit intomainfrom
fix/async-route-audit-phase-3-cleanup

Conversation

@pcalnon
Copy link
Copy Markdown
Owner

@pcalnon pcalnon commented May 6, 2026

Summary

Phase 3 cleanup of the 3 `ASYNC240` violations Phase 0
enumerated in juniper-data
(juniper-ml `ASYNC_ROUTE_VIOLATIONS_2026-05-06.md` §2.2).

First of two Phase 3 cleanup PRs (data, then canopy); cascor and
worker have nothing to fix at Phase 3.

Fix

`api/routes/health.py` — extracted a `_probe_storage(storage_path)`
helper that runs both `is_dir()` and `glob('*.npz')` in a single
`asyncio.to_thread` call from the readiness route. One thread-hop
instead of two; the previous synchronous-in-async pattern blocked
the event loop while the disk was probed.

`api/app.py` — per-line `# noqa: ASYNC240` for the
`storage_path.absolute()` lifespan log line. Unlike `is_dir()` /
`glob()`, `Path.absolute()` is pure path manipulation with no
filesystem I/O — the `ASYNC240` rule is over-conservative. Comment
explains the distinction so a future reviewer doesn't wrap it in
`to_thread` thinking it's I/O. Lifespan startup is also a one-shot
event, not a request handler.

Effect

After this change `ruff check --select ASYNC juniper_data/` reports
zero violations — the async-route audit lane stays green without
needing per-file-ignores.

Test plan

  • 950/950 tests in `juniper_data/tests/` pass.
  • 12/12 tests in `test_health_enhanced.py` pass — the
    readiness route behaviour is unchanged; the helper extraction
    is purely internal restructuring.
  • Pre-commit hooks pass (including the new `ruff-async-audit`
    hook from Phase 2).
  • `ruff check --select ASYNC juniper_data/` exits clean.

🤖 Generated with Claude Code

…hase 3 cleanup)

Phase 3 cleanup of the 3 ASYNC240 violations Phase 0 enumerated
in juniper-data
(juniper-ml notes/ASYNC_ROUTE_VIOLATIONS_2026-05-06.md §2.2):

- juniper_data/api/app.py:46 (lifespan probe ``storage_path.absolute()``)
- juniper_data/api/routes/health.py:131 (``storage_path.is_dir()``)
- juniper_data/api/routes/health.py:132 (``storage_path.glob(\"*.npz\")``)

Fix
---
``health.py``: extracted a ``_probe_storage(storage_path)`` helper
that runs both the ``is_dir()`` stat and the ``*.npz`` glob in a
single ``asyncio.to_thread`` call from the readiness route. One
thread-hop instead of two; the previous synchronous-in-async
pattern blocked the event loop while the disk was probed.

``app.py``: per-line ``# noqa: ASYNC240`` for the
``storage_path.absolute()`` call. Unlike ``is_dir()``/``glob()``,
``Path.absolute()`` is pure path manipulation with no filesystem
I/O — the ASYNC240 rule is over-conservative. Comment explains
the distinction so a future reviewer doesn't accidentally wrap
it in ``to_thread`` thinking it's I/O. Lifespan startup is also a
one-shot event, not a request handler.

After this change ``ruff check --select ASYNC juniper_data/``
reports zero violations — the async-route audit lane stays green
without needing per-file-ignores.

Tests
-----
- 950/950 in juniper_data/tests/ pass.
- 12/12 in test_health_enhanced.py pass — the readiness route
  behaviour is unchanged; the helper extraction is purely
  internal restructuring.

Plan: juniper-ml notes/ASYNC_ROUTE_AUDIT_HOOK_MIGRATION_PLAN.md §4
Phase 3 cleanup. First of two cleanup PRs (data, then canopy);
cascor and worker have nothing to fix at Phase 3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pcalnon pcalnon self-assigned this May 6, 2026
Copy link
Copy Markdown
Owner Author

@pcalnon pcalnon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved

@pcalnon pcalnon merged commit e8fc5a4 into main May 6, 2026
28 of 38 checks passed
pcalnon added a commit that referenced this pull request May 7, 2026
Drop the soft-fail flags so any new ASYNC* violation hard-fails:
- ``.github/workflows/ci.yml``: remove ``continue-on-error: true`` from
  the ``async-route-audit`` job and ``--exit-zero`` from the ruff
  command. Rename job to "Async-route audit (BUG-JD-10 class)" (no
  more "soft-fail" suffix).
- ``.pre-commit-config.yaml``: remove ``--exit-zero`` from the
  ``ruff-async-audit`` hook so violations block commits.

Repo reached zero violations at end of Phase 3 (PR #96). Verified
``ruff check --select ASYNC juniper_data/`` exits clean and
``pre-commit run ruff-async-audit --all-files`` passes.

First of four Phase 4 PRs (data → cascor → canopy → worker).

Refs: notes/ASYNC_ROUTE_AUDIT_HOOK_MIGRATION_PLAN.md §4 (in juniper-ml)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant