Skip to content

feat(cron): Add comprehensive cron jobs support#316

Open
Quentin-Gillet wants to merge 12 commits intoaegra:mainfrom
olixid:feat/cron-jobs
Open

feat(cron): Add comprehensive cron jobs support#316
Quentin-Gillet wants to merge 12 commits intoaegra:mainfrom
olixid:feat/cron-jobs

Conversation

@Quentin-Gillet
Copy link
Copy Markdown

@Quentin-Gillet Quentin-Gillet commented Apr 16, 2026

Description

Add comprehensive cron jobs support to Aegra with full LangGraph SDK compatibility.

Implements CRUD REST API for scheduling agent runs, timezone-aware background scheduler with crash recovery, automatic run lifecycle management, and state persistence for reliable execution.

Type of Change

  • feat: New feature
  • docs: Documentation changes
  • test: Tests added/updated

Changes Made

  • API Endpoints (libs/aegra-api/src/aegra_api/api/crons.py): Full REST CRUD operations for cron jobs
  • Scheduler Service (services/cron_scheduler.py): Background worker with timezone support, graceful error handling, and crash recovery
  • Cron Service (services/cron_service.py): Complete scheduling lifecycle (create, reschedule, automatic cleanup, validation)
  • Run Cleanup (services/run_cleanup.py): TTL-based cleanup of completed runs with configurable retention
  • Database Schema (core/orm.py + migration): New crons table with LangGraph state integration
  • Models (models/crons.py): Pydantic schemas with validation
  • Documentation (docs/guides/cron.mdx): Comprehensive guide with examples and best practices
  • Example (examples/cron_example/): Working agent with cron setup and configuration

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • E2E tests added/updated
  • Manual testing performed

Checklist

  • Code follows project style (Ruff formatting)
  • All linting checks pass (make lint)
  • Type checking passes (make type-check)
  • All tests pass (make test)
  • Documentation updated (if needed)
  • Commit messages follow Conventional Commits
  • PR title follows Conventional Commits format

Summary by CodeRabbit

  • New Features

    • Full cron/scheduled jobs support with a background scheduler, stateless and thread-bound crons, initial-run triggering, and configurable post-run behavior.
  • API

    • CRUD + listing/count endpoints for managing crons.
  • Configuration

    • New env vars to enable the scheduler and set poll interval: CRON_ENABLED, CRON_POLL_INTERVAL_SECONDS.
  • Examples

    • New cron example and setup script demonstrating scheduling.
  • Documentation

    • New cron guide, updated quickstart, feature matrix, migration notes, and env var reference.
  • Tests

    • Extensive unit, integration, and e2e tests covering cron behaviors.

Quentin-Gillet and others added 8 commits April 14, 2026 15:56
Add full cron job support as a drop-in replacement for LangSmith Deployments:
- 6 Agent Protocol endpoints: create, create-for-thread, update, delete, search, count
- CronService with schedule validation (croniter), payload persistence, metadata
- CronScheduler background task for automatic run triggering
- CronORM model with Alembic migration
- CRON_ENABLED and CRON_POLL_INTERVAL_SECONDS settings
- Health flag for cron subsystem
- 59 unit tests for CronService: create, update, delete, search, count,
  get_due_crons, advance_next_run, and all helper functions
- 15 unit tests for CronScheduler: lifecycle, tick, fire_cron, loop
- 30 integration tests for all 6 cron API endpoints including
  validation errors, filters, pagination, and response structure
_trigger_first_run fires a run immediately when a cron is created.
Previously, next_run_date was set to the first scheduled occurrence,
causing the scheduler to fire a second run seconds after creation.

Fix by advancing next_run_date to the second occurrence so the
scheduler starts from the correct interval boundary.
Add IANA timezone support for cron creation and updates; preserve timezone when recomputing next run. Include environment variable docs and a new cron guide, plus unit, integration, and e2e coverage.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

Adds first-class cron/scheduled-job support: DB migration and ORM, API endpoints and Pydantic models, CronService and CronScheduler, background run cleanup extraction, settings and env examples, docs/examples, tests, and app wiring (lifespan + health flag).

Changes

Cohort / File(s) Summary
Configuration & Settings
\.env\.example, libs/aegra-cli/src/aegra_cli/templates/env.example.template, libs/aegra-api/src/aegra_api/settings.py
Added CRON_ENABLED and CRON_POLL_INTERVAL_SECONDS, CronSettings and integration into global Settings.
Database & ORM
libs/aegra-api/alembic/versions/20260413201423_add_crons_table.py, libs/aegra-api/src/aegra_api/core/orm.py
New migration to create crons table and added Cron SQLAlchemy model with indexes and relationships.
Service Layer
libs/aegra-api/src/aegra_api/services/cron_service.py, libs/aegra-api/src/aegra_api/services/cron_scheduler.py, libs/aegra-api/src/aegra_api/services/run_cleanup.py
Implemented CronService (validation, CRUD, next-run logic), CronScheduler background poller, and extracted run/thread cleanup helpers with scheduling.
API & Models
libs/aegra-api/src/aegra_api/models/crons.py, libs/aegra-api/src/aegra_api/models/__init__.py, libs/aegra-api/src/aegra_api/api/crons.py
Added Pydantic cron request/response models and FastAPI router for create (stateless/thread), update, delete, search, and count.
Stateless Runs Refactor
libs/aegra-api/src/aegra_api/api/stateless_runs.py
Replaced local background cleanup/task bookkeeping with calls into run_cleanup.schedule_background_cleanup and delegated deletion/cleanup functions.
App Integration & Health
libs/aegra-api/src/aegra_api/main.py, libs/aegra-api/src/aegra_api/core/health.py
Registered cron router and OpenAPI tag; wired cron scheduler start/stop into app lifespan; /info now reflects settings.cron.CRON_ENABLED.
Dependency
libs/aegra-api/pyproject.toml
Added runtime dependency croniter>=6.0.0.
Examples & Project Config
aegra.json, examples/cron_example/__init__.py, examples/cron_example/graph.py, examples/cron_example/setup.py
Added cron_example graph entry and example package + setup script demonstrating cron creation.
Documentation
docs/docs.json, docs/guides/cron.mdx, docs/*
Added cron guide and navigation; updated feature-support, quickstart, assistants guidance, and environment-variable reference.
Tests
libs/aegra-api/tests/unit/..., libs/aegra-api/tests/integration/..., libs/aegra-api/tests/e2e/...
Added extensive unit, integration, and E2E tests for scheduler, service, API endpoints, settings validation, health flag; updated stateless-run tests to new cleanup helpers.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as Cron API
    participant Service as CronService
    participant Scheduler as CronScheduler
    participant LangGraph as LangGraph Service
    participant DB as Database

    rect rgba(100, 150, 200, 0.5)
    note over Client,API: Cron creation (immediate run)
    Client->>API: POST /runs/crons (assistant_id, schedule, payload)
    API->>Service: create_cron(request, user)
    Service->>LangGraph: resolve_assistant_id(...)
    LangGraph-->>Service: assistant
    Service->>DB: INSERT cron (compute next_run_date)
    DB-->>Service: cron record
    API->>Service: _trigger_first_run(cron)
    Service->>LangGraph: _prepare_run(RunCreate)
    LangGraph-->>Service: Run (pending)
    API-->>Client: 200 OK (run_id, cron_id)
    end

    rect rgba(200, 150, 100, 0.5)
    note over Scheduler,DB: Scheduler polling loop
    loop every CRON_POLL_INTERVAL_SECONDS
        Scheduler->>DB: SELECT enabled crons WHERE next_run_date <= now
        DB-->>Scheduler: due crons
        loop for each cron
            Scheduler->>Service: build RunCreate from cron.payload
            Service->>LangGraph: _prepare_run(RunCreate)
            LangGraph-->>Service: Run
            Scheduler->>DB: UPDATE cron.next_run_date / disable if ended
            DB-->>Scheduler: updated
            Scheduler->>Service: schedule_background_cleanup (if stateless)
        end
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ibbybuilds

Poem

🐇 I tick, I tock, I spin my run,

Threads awaken, one by one.
Timezones hum and cronlines chime,
Messages hop in perfect time.
—A happy rabbit, scheduling a rhyme

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(cron): Add comprehensive cron jobs support' clearly and concisely summarizes the main objective of the changeset, which is adding full cron job scheduling functionality to Aegra.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (6)
libs/aegra-api/src/aegra_api/core/health.py (1)

47-47: Make flags.crons reflect runtime configuration

crons is hardcoded to True; when CRON_ENABLED=false, /info can report an unavailable capability. Consider wiring it to settings.cron.CRON_ENABLED for accurate introspection.

Suggested patch
 from aegra_api import __version__
 from aegra_api.core.database import db_manager
 from aegra_api.models.errors import UNAVAILABLE
+from aegra_api.settings import settings
@@
-        flags={"assistants": True, "crons": True},
+        flags={"assistants": True, "crons": settings.cron.CRON_ENABLED},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/aegra-api/src/aegra_api/core/health.py` at line 47, The health endpoint
currently hardcodes flags={"assistants": True, "crons": True}, which can falsely
report crons as available; change the 'crons' flag to reflect runtime config by
using the project settings value (e.g., replace the literal True with
settings.cron.CRON_ENABLED or the appropriate settings attribute) so the flags
dict becomes flags={"assistants": True, "crons": settings.cron.CRON_ENABLED};
ensure you import the settings/crons config symbol referenced
(settings.cron.CRON_ENABLED) and keep the key name 'crons' unchanged.
libs/aegra-api/src/aegra_api/main.py (1)

285-295: Update _include_core_routers docstring order list to include Crons.

The implementation now includes crons_router, but the documented router order still skips it, which can mislead future maintenance.

Suggested docstring tweak
-    Routers are included in consistent order:
-    1. Health (no auth)
-    2. Assistants (with auth)
-    3. Threads (with auth)
-    4. Runs (with auth)
-    5. Stateless Runs (with auth)
-    6. Store (with auth)
+    Routers are included in consistent order:
+    1. Health (no auth)
+    2. Assistants (with auth)
+    3. Threads (with auth)
+    4. Runs (with auth)
+    5. Stateless Runs (with auth)
+    6. Crons (with auth)
+    7. Store (with auth)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/aegra-api/src/aegra_api/main.py` around lines 285 - 295, The docstring
for _include_core_routers is out of sync with the implementation because it
omits the crons router; update the ordered list in the _include_core_routers
docstring to include "Crons (with auth)" in the correct position matching the
actual inclusion of crons_router (alongside health, assistants, threads, runs,
stateless runs, store) so the documentation reflects the code path that
references crons_router.
libs/aegra-api/src/aegra_api/services/cron_scheduler.py (1)

92-104: Consider reusing CronService.get_due_crons to avoid query duplication.

This _find_due_crons method duplicates the query logic from CronService.get_due_crons (in cron_service.py:307-325). Consider instantiating CronService with the session and delegating to it to maintain a single source of truth for the due-crons query.

♻️ Suggested approach
+from aegra_api.services.cron_service import CronService
+
 class CronScheduler:
     ...
     `@staticmethod`
     async def _find_due_crons(session: AsyncSession, now: datetime) -> list[CronORM]:
         """Return enabled crons whose next_run_date has passed."""
-        stmt = (
-            select(CronORM)
-            .where(
-                CronORM.enabled.is_(True),
-                CronORM.next_run_date <= now,
-            )
-            .order_by(CronORM.next_run_date.asc())
-        )
-        result = await session.scalars(stmt)
-        return list(result.all())
+        service = CronService(session)
+        return await service.get_due_crons(now)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/aegra-api/src/aegra_api/services/cron_scheduler.py` around lines 92 -
104, _private method _find_due_crons duplicates the due-crons query from
CronService.get_due_crons; replace the duplicate query by creating a CronService
instance with the given AsyncSession and call its get_due_crons method instead
of reimplementing the select logic. Locate _find_due_crons and change it to
delegate to CronService(session).get_due_crons(now) (or the appropriate async
call pattern) so the query logic is centralized in CronService.get_due_crons.
examples/cron_example/setup.py (1)

35-35: Move httpx import to the top of the file.

The inline import of httpx inside the function violates the coding guideline requiring all imports at the top of the file. There's no circular dependency concern with this external package.

♻️ Proposed fix

Move the import to the top with other imports:

 import asyncio
 
 from langgraph_sdk import get_client
+import httpx
 
 
 async def main() -> None:
     client = get_client(url="http://localhost:2026")
     ...
-    import httpx
-
     async with httpx.AsyncClient() as http:

As per coding guidelines: "ALWAYS place imports at the top of the file. Never use inline/lazy imports inside functions unless there is a proven circular dependency."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cron_example/setup.py` at line 35, The inline import "import httpx"
must be moved to the module-level imports at the top of
examples/cron_example/setup.py; remove the local/in-function import and add
"import httpx" with the other top-of-file imports so no inline/lazy import
remains (there is no circular dependency here).
libs/aegra-api/tests/integration/test_api/test_crons_crud.py (2)

167-169: Remove redundant import.

HTTPException is already imported at line 10. This local import is unnecessary.

♻️ Suggested fix
     def test_returns_404_when_not_found(self, client, mock_cron_service: AsyncMock) -> None:
-        from fastapi import HTTPException
-
         mock_cron_service.update_cron.side_effect = HTTPException(404, "Cron 'missing' not found")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/aegra-api/tests/integration/test_api/test_crons_crud.py` around lines
167 - 169, In test_returns_404_when_not_found remove the redundant local import
of HTTPException (the line "from fastapi import HTTPException") since
HTTPException is already imported at the top of the file; update the test
function to rely on the module-level import and delete that duplicate import
statement.

193-195: Remove redundant import.

Same issue - HTTPException is already imported at the module level.

♻️ Suggested fix
     def test_returns_404_when_not_found(self, client, mock_cron_service: AsyncMock) -> None:
-        from fastapi import HTTPException
-
         mock_cron_service.delete_cron.side_effect = HTTPException(404, "Cron 'missing' not found")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/aegra-api/tests/integration/test_api/test_crons_crud.py` around lines
193 - 195, The test function test_returns_404_when_not_found contains a
redundant local import of HTTPException; remove the inner "from fastapi import
HTTPException" so the test uses the module-level HTTPException import instead,
leaving the rest of the test and the mock_cron_service AsyncMock usage
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/cron_example/__init__.py`:
- Line 1: Replace the absolute package import in __init__.py with a relative
intra-package import: update the current statement that imports graph to use a
relative import (from .graph import graph) so the module re-exports the graph
symbol consistently with other example modules; keep the exported symbol name
(graph) unchanged so downstream imports continue to work.

In `@libs/aegra-api/src/aegra_api/models/crons.py`:
- Around line 53-72: CronUpdate is missing the checkpoint_during and
multitask_strategy fields that CronCreate defines, but _build_payload in
cron_service.py expects to handle them for both CronCreate | CronUpdate; add
matching optional fields checkpoint_during and multitask_strategy to the
CronUpdate Pydantic model (keeping the same types as in CronCreate) so updates
can modify those values, or if omission was intentional, add a brief comment on
CronUpdate explaining why those fields are exclusionary so _build_payload
handling is clear.

In `@libs/aegra-api/src/aegra_api/settings.py`:
- Around line 217-218: CRON_POLL_INTERVAL_SECONDS must be validated to be > 0;
add a check in the settings class that owns CRON_POLL_INTERVAL_SECONDS (e.g.,
add a pydantic `@validator` for "CRON_POLL_INTERVAL_SECONDS" if using BaseSettings
or perform an explicit runtime check in the class __post_init__/__init__) and
raise a clear ValueError (or ValidationError) when the value is <= 0, ensuring
misconfiguration fails fast with a helpful message referencing
CRON_POLL_INTERVAL_SECONDS.

---

Nitpick comments:
In `@examples/cron_example/setup.py`:
- Line 35: The inline import "import httpx" must be moved to the module-level
imports at the top of examples/cron_example/setup.py; remove the
local/in-function import and add "import httpx" with the other top-of-file
imports so no inline/lazy import remains (there is no circular dependency here).

In `@libs/aegra-api/src/aegra_api/core/health.py`:
- Line 47: The health endpoint currently hardcodes flags={"assistants": True,
"crons": True}, which can falsely report crons as available; change the 'crons'
flag to reflect runtime config by using the project settings value (e.g.,
replace the literal True with settings.cron.CRON_ENABLED or the appropriate
settings attribute) so the flags dict becomes flags={"assistants": True,
"crons": settings.cron.CRON_ENABLED}; ensure you import the settings/crons
config symbol referenced (settings.cron.CRON_ENABLED) and keep the key name
'crons' unchanged.

In `@libs/aegra-api/src/aegra_api/main.py`:
- Around line 285-295: The docstring for _include_core_routers is out of sync
with the implementation because it omits the crons router; update the ordered
list in the _include_core_routers docstring to include "Crons (with auth)" in
the correct position matching the actual inclusion of crons_router (alongside
health, assistants, threads, runs, stateless runs, store) so the documentation
reflects the code path that references crons_router.

In `@libs/aegra-api/src/aegra_api/services/cron_scheduler.py`:
- Around line 92-104: _private method _find_due_crons duplicates the due-crons
query from CronService.get_due_crons; replace the duplicate query by creating a
CronService instance with the given AsyncSession and call its get_due_crons
method instead of reimplementing the select logic. Locate _find_due_crons and
change it to delegate to CronService(session).get_due_crons(now) (or the
appropriate async call pattern) so the query logic is centralized in
CronService.get_due_crons.

In `@libs/aegra-api/tests/integration/test_api/test_crons_crud.py`:
- Around line 167-169: In test_returns_404_when_not_found remove the redundant
local import of HTTPException (the line "from fastapi import HTTPException")
since HTTPException is already imported at the top of the file; update the test
function to rely on the module-level import and delete that duplicate import
statement.
- Around line 193-195: The test function test_returns_404_when_not_found
contains a redundant local import of HTTPException; remove the inner "from
fastapi import HTTPException" so the test uses the module-level HTTPException
import instead, leaving the rest of the test and the mock_cron_service AsyncMock
usage unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 163a6071-2f96-4586-a042-bd11f384cdd3

📥 Commits

Reviewing files that changed from the base of the PR and between 936a814 and 5168568.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (34)
  • .env.example
  • aegra.json
  • docs/docs.json
  • docs/feature-support.mdx
  • docs/getting-started.mdx
  • docs/guides/assistants.mdx
  • docs/guides/cron.mdx
  • docs/migration.mdx
  • docs/quickstart.mdx
  • docs/reference/environment-variables.mdx
  • examples/cron_example/__init__.py
  • examples/cron_example/graph.py
  • examples/cron_example/setup.py
  • libs/aegra-api/alembic/versions/20260413201423_add_crons_table.py
  • libs/aegra-api/pyproject.toml
  • libs/aegra-api/src/aegra_api/api/crons.py
  • libs/aegra-api/src/aegra_api/api/stateless_runs.py
  • libs/aegra-api/src/aegra_api/core/health.py
  • libs/aegra-api/src/aegra_api/core/orm.py
  • libs/aegra-api/src/aegra_api/main.py
  • libs/aegra-api/src/aegra_api/models/__init__.py
  • libs/aegra-api/src/aegra_api/models/crons.py
  • libs/aegra-api/src/aegra_api/services/cron_scheduler.py
  • libs/aegra-api/src/aegra_api/services/cron_service.py
  • libs/aegra-api/src/aegra_api/services/run_cleanup.py
  • libs/aegra-api/src/aegra_api/settings.py
  • libs/aegra-api/tests/e2e/test_crons/__init__.py
  • libs/aegra-api/tests/e2e/test_crons/test_crons.py
  • libs/aegra-api/tests/integration/test_api/test_crons_crud.py
  • libs/aegra-api/tests/unit/test_api/test_crons.py
  • libs/aegra-api/tests/unit/test_api/test_stateless_runs.py
  • libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py
  • libs/aegra-api/tests/unit/test_services/test_cron_service.py
  • libs/aegra-cli/src/aegra_cli/templates/env.example.template
💤 Files with no reviewable changes (1)
  • docs/migration.mdx

Comment thread examples/cron_example/__init__.py Outdated
Comment thread libs/aegra-api/src/aegra_api/models/crons.py
Comment thread libs/aegra-api/src/aegra_api/settings.py
Align cron update payload handling, scheduler delegation, health flags, and settings validation. Add regression coverage for the reviewed cron paths and tighten the example package imports.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/aegra-api/src/aegra_api/services/cron_scheduler.py`:
- Around line 132-177: The code currently advances the cron schedule even when
_prepare_run fails; change _fire_cron so it only advances next_run_date or
disables the cron when the run was successfully created. Concretely, introduce a
boolean (e.g., run_created = False) before calling _prepare_run, set run_created
= True after _prepare_run returns successfully in the try block, and in the
except blocks perform logging and call
CronScheduler._cleanup_failed_stateless_thread as now but return or skip the
schedule-advancement logic; only compute/assign next_run_date or set
enabled=False when run_created is True (leave existing commit behavior intact).
This prevents transient _prepare_run failures from advancing the cron schedule
and skipping the due occurrence.

In `@libs/aegra-api/src/aegra_api/services/cron_service.py`:
- Around line 319-328: The current plain SELECT of due crons can return the same
rows to multiple instances; replace it with an atomic claim using an UPDATE ...
RETURNING so rows are reserved before being processed: use SQLAlchemy's
update(CronORM).where(CronORM.enabled.is_(True), CronORM.next_run_date <=
now).values(next_run_date=<new_next_run_value> OR set claimed_at/claimed_by)
.returning(CronORM), execute it with await self.session.execute(...), fetch
returned rows and commit; reference CronORM, self.session.execute (instead of
self.session.scalars), update(), and returning() to locate and implement the
change. Ensure the new_next_run_value or claimed_* fields mark the cron as
claimed so other pollers won't select it.
- Around line 219-244: The patch handler currently only validates timezone when
request.schedule is provided; change it so that when request.timezone is not
None you first validate the timezone and then, if cron.schedule (existing) is
present, recompute and set values["next_run_date"] using
_compute_next_run(schedule, timezone=request.timezone) (raising
HTTPException(422) on invalid timezone), and still merge/update the payload via
_build_payload and existing_payload updates; ensure validation occurs separately
from _is_valid_schedule and that values["payload"] and values["timezone"] (in
payload) are updated consistently when request.timezone is provided even if
request.schedule is absent.

In `@libs/aegra-api/tests/integration/test_health_info.py`:
- Around line 3-21: This test currently constructs FastAPI() and TestClient
directly; replace that with the repo's integration helpers by importing and
using create_test_app() and make_client() from tests/fixtures/clients.py, remove
direct app.include_router(router) and TestClient usage, and create the test
client via make_client(create_test_app()) (or create_test_app() followed by
make_client) so the full integration middleware/dependency stack is exercised;
keep the monkeypatch of settings.cron.CRON_ENABLED and ensure it runs before
creating the client so the flag is reflected in the app under test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2761c5b9-99a5-4703-bbcb-7f2754f080d2

📥 Commits

Reviewing files that changed from the base of the PR and between 5168568 and fc626ae.

📒 Files selected for processing (13)
  • examples/cron_example/__init__.py
  • examples/cron_example/setup.py
  • libs/aegra-api/src/aegra_api/core/health.py
  • libs/aegra-api/src/aegra_api/main.py
  • libs/aegra-api/src/aegra_api/models/crons.py
  • libs/aegra-api/src/aegra_api/services/cron_scheduler.py
  • libs/aegra-api/src/aegra_api/services/cron_service.py
  • libs/aegra-api/src/aegra_api/settings.py
  • libs/aegra-api/tests/integration/test_api/test_crons_crud.py
  • libs/aegra-api/tests/integration/test_health_info.py
  • libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py
  • libs/aegra-api/tests/unit/test_services/test_cron_service.py
  • libs/aegra-api/tests/unit/test_settings.py
✅ Files skipped from review due to trivial changes (3)
  • libs/aegra-api/src/aegra_api/models/crons.py
  • examples/cron_example/init.py
  • libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/cron_example/setup.py
  • libs/aegra-api/src/aegra_api/settings.py

Comment thread libs/aegra-api/src/aegra_api/services/cron_scheduler.py
Comment thread libs/aegra-api/src/aegra_api/services/cron_service.py
Comment thread libs/aegra-api/src/aegra_api/services/cron_service.py Outdated
Comment thread libs/aegra-api/tests/integration/test_health_info.py Outdated
Prevent cron schedule advancement when run preparation fails, validate timezone-only cron updates, and atomically claim due crons before firing them.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py`:
- Around line 447-474: The test test_builds_run_create_from_payload is missing
an assertion that verifies the checkpoint field is propagated correctly: cron
payload uses checkpoint_during (in cron_service.py) while
CronScheduler._fire_cron (in cron_scheduler.py) reads payload.get("checkpoint"),
so extend the test to include the checkpoint mapping by adding an assertion on
mock_prepare's run_request (from call_args[0][2]) that run_request.checkpoint
(or the appropriate attribute name used when creating the run) equals the
expected value supplied in the cron payload (e.g., set payload with
"checkpoint_during": True and assert run_request.checkpoint is True) to catch
the regression.

In `@libs/aegra-api/tests/unit/test_services/test_cron_service.py`:
- Line 1146: The test file defines ZoneInfoNotFoundError via an inline import at
line where ZoneInfoNotFoundError is used; move the "from zoneinfo import
ZoneInfoNotFoundError" statement from inside the function to the module-level
import block at the top of test_cron_service.py so the symbol is imported once
for the module (avoid inline imports unless circular or optional), then remove
the local import and reference ZoneInfoNotFoundError directly in the test
function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 14d3f615-0d1a-46e6-b07f-7c9419424410

📥 Commits

Reviewing files that changed from the base of the PR and between fc626ae and 3553caf.

📒 Files selected for processing (7)
  • libs/aegra-api/src/aegra_api/services/cron_scheduler.py
  • libs/aegra-api/src/aegra_api/services/cron_service.py
  • libs/aegra-api/tests/e2e/test_crons/test_crons.py
  • libs/aegra-api/tests/fixtures/clients.py
  • libs/aegra-api/tests/integration/test_health_info.py
  • libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py
  • libs/aegra-api/tests/unit/test_services/test_cron_service.py
✅ Files skipped from review due to trivial changes (2)
  • libs/aegra-api/tests/fixtures/clients.py
  • libs/aegra-api/tests/integration/test_health_info.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • libs/aegra-api/tests/e2e/test_crons/test_crons.py
  • libs/aegra-api/src/aegra_api/services/cron_scheduler.py
  • libs/aegra-api/src/aegra_api/services/cron_service.py

Comment thread libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py
Comment thread libs/aegra-api/tests/unit/test_services/test_cron_service.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py (1)

447-476: ⚠️ Potential issue | 🟠 Major

Add regression coverage for checkpoint_during → run request mapping

This test currently verifies only payload["checkpoint"]. If cron payload creation uses checkpoint_during, this suite won’t catch a silent drop in checkpoint behavior. Please extend this case to assert whichever payload key is canonical across cron creation and scheduler run construction.

#!/bin/bash
set -euo pipefail

echo "== Check checkpoint key usage in scheduler and cron service =="
rg -n --type=py -C3 'checkpoint_during|payload\.get\("checkpoint"\)|checkpoint=' \
  libs/aegra-api/src/aegra_api/services/cron_scheduler.py \
  libs/aegra-api/src/aegra_api/services/cron_service.py \
  libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py

echo
echo "== Locate RunCreate payload construction sites =="
rg -n --type=py -C4 'RunCreate\(' libs/aegra-api/src/aegra_api/services
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py` around lines
447 - 476, The test only asserts payload["checkpoint"] mapping into the
RunCreate but misses the alternate key checkpoint_during; update the
test_builds_run_create_from_payload in test_cron_scheduler to include and assert
the canonical mapping for checkpoint_during as well (or parametrize to cover
both keys) by passing payload with "checkpoint_during":
{"checkpoint_id":"abc","checkpoint_ns":""} and/or "checkpoint" and verifying the
run_request (obtained from the mocked _prepare_run call in
CronScheduler._fire_cron) has run_request.checkpoint equal to the expected dict;
ensure you reference the existing mock_prepare/_prepare_run and run_request
variables so the test exercises the same code path that constructs RunCreate.
🧹 Nitpick comments (1)
libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py (1)

9-10: Extract async session maker setup to a reusable fixture

The session context-manager mocking pattern (lines 115–117 and repeated 16 other times) can be consolidated into a shared fixture in tests/conftest.py:

`@pytest.fixture`
def mock_async_session_maker() -> Mock:
    """Mock async session maker for services that call _get_session_maker()."""
    mock_session = AsyncMock()
    mock_session.__aenter__ = AsyncMock(return_value=mock_session)
    mock_session.__aexit__ = AsyncMock(return_value=False)
    return Mock(return_value=mock_session)

Then use it directly in tests:

with patch("aegra_api.services.cron_scheduler._get_session_maker", return_value=mock_async_session_maker):

This reduces boilerplate across lines 115–117, 138–140, 170–172, and similar patterns throughout the file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py` around lines
9 - 10, Add a reusable pytest fixture named mock_async_session_maker that
returns a Mock wrapping an AsyncMock context manager (set __aenter__ to return
the async mock and __aexit__ to AsyncMock(return_value=False)), place it in
tests conftest so it’s importable, then replace repeated manual context-manager
mocks in test_cron_scheduler (and other tests) by importing that fixture and
using patch("aegra_api.services.cron_scheduler._get_session_maker",
return_value=mock_async_session_maker) in those tests (targets:
_get_session_maker and tests that currently set AsyncMock + __aenter__/__aexit__
patterns around lines where session maker is mocked).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py`:
- Around line 447-476: The test only asserts payload["checkpoint"] mapping into
the RunCreate but misses the alternate key checkpoint_during; update the
test_builds_run_create_from_payload in test_cron_scheduler to include and assert
the canonical mapping for checkpoint_during as well (or parametrize to cover
both keys) by passing payload with "checkpoint_during":
{"checkpoint_id":"abc","checkpoint_ns":""} and/or "checkpoint" and verifying the
run_request (obtained from the mocked _prepare_run call in
CronScheduler._fire_cron) has run_request.checkpoint equal to the expected dict;
ensure you reference the existing mock_prepare/_prepare_run and run_request
variables so the test exercises the same code path that constructs RunCreate.

---

Nitpick comments:
In `@libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py`:
- Around line 9-10: Add a reusable pytest fixture named mock_async_session_maker
that returns a Mock wrapping an AsyncMock context manager (set __aenter__ to
return the async mock and __aexit__ to AsyncMock(return_value=False)), place it
in tests conftest so it’s importable, then replace repeated manual
context-manager mocks in test_cron_scheduler (and other tests) by importing that
fixture and using patch("aegra_api.services.cron_scheduler._get_session_maker",
return_value=mock_async_session_maker) in those tests (targets:
_get_session_maker and tests that currently set AsyncMock + __aenter__/__aexit__
patterns around lines where session maker is mocked).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1eabbdb4-56c9-442a-a87c-99a1acaab509

📥 Commits

Reviewing files that changed from the base of the PR and between 3553caf and 778730a.

📒 Files selected for processing (2)
  • libs/aegra-api/tests/unit/test_services/test_cron_scheduler.py
  • libs/aegra-api/tests/unit/test_services/test_cron_service.py
✅ Files skipped from review due to trivial changes (1)
  • libs/aegra-api/tests/unit/test_services/test_cron_service.py

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