Skip to content

fix(cron): accept bare cron-expression string in Schedule deserializer#2868

Closed
graycyrus wants to merge 2 commits into
tinyhumansai:mainfrom
graycyrus:fix/cron-update-bare-schedule-string
Closed

fix(cron): accept bare cron-expression string in Schedule deserializer#2868
graycyrus wants to merge 2 commits into
tinyhumansai:mainfrom
graycyrus:fix/cron-update-bare-schedule-string

Conversation

@graycyrus
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus commented May 28, 2026

Summary

  • Root cause (Sentry CORE-RUST-FY / Cron update fails: Schedule field rejects bare cron strings #2866): openhuman.cron_update deserializes the schedule field as an internally-tagged Schedule enum. Agents and some frontend callers send a bare string like "0 9 * * 1" instead of the structured form {"kind":"cron","expr":"0 9 * * 1"}. Serde rejected this with: invalid type: string, expected internally tagged enum Schedule.
  • Fix: Custom Deserialize impl for Schedule in src/openhuman/cron/types.rs. A bare string is coerced to Schedule::Cron { expr, tz: None, active_hours: None }; the full object form still works as before. Serialization is unchanged.
  • Tests: 4 new unit tests cover: bare string deserialization, 5-field cron string, CronJobPatch with bare string (exact failing shape), and regression guard for the structured object form. All 20 cron::types tests pass.

Test plan

  • cargo test --lib openhuman::cron::types — all 20 pass
  • cargo check — clean (no new errors)
  • Manual: send {"method":"openhuman.cron_update","params":{"job_id":"<id>","patch":{"schedule":"0 9 * * 1"}}} — should succeed instead of returning deserialization error

Summary by CodeRabbit

  • New Features

    • Cron schedules now accept a simplified string format (e.g., "0 9 * * 1") in addition to the structured object form; both formats are supported for schedule fields.
    • Backward compatibility preserved for existing configurations.
  • Tests

    • Added tests covering string-based cron parsing, including 5-field and 6-field cron expressions and acceptance in job patch payloads.

Review Change Stack

@graycyrus graycyrus requested a review from a team May 28, 2026 19:50
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Warning

Review limit reached

@M3gA-Mind, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 5 minutes and 18 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c1460988-3cb2-4d45-9c4c-67971ba56162

📥 Commits

Reviewing files that changed from the base of the PR and between 1cb015e and 9f27d7d.

📒 Files selected for processing (1)
  • src/openhuman/cron/types.rs
📝 Walkthrough

Walkthrough

Schedule deserialization now uses a custom Serde Visitor: JSON strings are coerced into Schedule::Cron (tz and active_hours set to None), and map/object inputs are deserialized via the existing internally-tagged representation. Tests cover both input forms.

Changes

Cron Schedule deserialization

Layer / File(s) Summary
Imports and struct setup
src/openhuman/cron/types.rs
Serde imports expanded to include Deserializer; Schedule derive list removes Deserialize and docs added describing dual-format parsing (bare cron string or internally-tagged object).
Custom Deserialize visitor implementation
src/openhuman/cron/types.rs
Manual Deserialize impl using a Visitor that coerces bare &str/String into Schedule::Cron { expr, tz: None, active_hours: None } and delegates map inputs to a locally-defined internally-tagged enum mapped back to Schedule.
Deserialization tests
src/openhuman/cron/types.rs
Unit tests cover bare cron string deserialization into Schedule::Cron (5-field and 6-field) and verify CronJobPatch.schedule accepts both bare string and structured tagged object payloads.

Sequence Diagram

sequenceDiagram
  participant Input as JSON Input
  participant Visitor as ScheduleVisitor
  participant Cron as Schedule::Cron
  participant Tagged as TaggedEnum
  Input->>Visitor: "0 9 * * 1" (bare string)
  Visitor->>Cron: coerce to Cron { expr, tz=None, active_hours=None }
  Input->>Visitor: {"kind":"cron","expr":"0 9 * * 1"} (tagged object)
  Visitor->>Tagged: deserialize tagged enum
  Tagged->>Visitor: map to Schedule::Cron variant
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 Hopping through formats with a twitchy whisker,
Bare strings or objects, the parser grows brisker.
A Visitor nibbles each incoming token,
Turning crons to variants—no errors to beckon.
Celebrate the hop, schedule’s now slicker!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: accepting bare cron-expression strings in the Schedule deserializer, which directly addresses the fix for the deserialization error described in the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 28, 2026
M3gA-Mind
M3gA-Mind previously approved these changes May 28, 2026
Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

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

Clean fix. The Deserialize visitor approach is idiomatic — deserialize_any dispatches to visit_str/visit_string for bare strings and visit_map (delegating via MapAccessDeserializer to an inner tagged-enum derivation) for the full object form. Serialization is unchanged so no wire-format regression.

Tests hit all four cases: bare 5-field string, 6-field ( */5), CronJobPatch with bare string (exact CORE-RUST-FY payload), and regression guard for the structured object form.

One minor observation: visit_string duplicates the body of visit_str rather than forwarding (self.visit_str(value.as_str())), but both are functionally correct and the duplication is minimal.

Approving — waiting on CI green to merge.

@M3gA-Mind
Copy link
Copy Markdown
Contributor

The branch has fix(observability): classify Embedding API 401 Invalid-token as SessionExpired on top as an extra commit, which was already merged to main independently as #2869. This puts the PR in mergeable_state: dirty and blocks CI.

I've opened #2874 with the two cron-fix commits cleanly rebased on current main (the observability commit dropped since it's already in main). Functionally identical to this PR. Recommending we close this in favour of #2874.

@M3gA-Mind M3gA-Mind force-pushed the fix/cron-update-bare-schedule-string branch from 8c5a573 to 1cb015e Compare May 28, 2026 22:07
graycyrus and others added 2 commits May 29, 2026 03:38
Fixes Sentry issue CORE-RUST-FY (closes tinyhumansai#2866).

The `openhuman.cron_update` RPC deserializes the `schedule` field as an
internally-tagged `Schedule` enum.  Agents and some frontend callers were
sending a bare string like `"0 9 * * 1"` instead of the structured object
`{"kind":"cron","expr":"0 9 * * 1"}`, causing serde to reject with:
  "invalid type: string, expected internally tagged enum Schedule"

Fix: add a custom `Deserialize` impl for `Schedule` that routes a bare
string to `Schedule::Cron { expr, tz: None, active_hours: None }`, while
still delegating the full object form to the existing tagged-enum logic.
Serialization is unchanged (always emits the object form).

New tests:
- `schedule_deserializes_bare_cron_string`
- `schedule_deserializes_bare_5_field_cron_string`
- `cron_job_patch_accepts_bare_schedule_string` (the exact failing shape)
- `cron_job_patch_still_accepts_structured_schedule_object` (regression)
@M3gA-Mind M3gA-Mind force-pushed the fix/cron-update-bare-schedule-string branch from 1cb015e to 9f27d7d Compare May 28, 2026 22:08
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.

2 participants