Skip to content

zero cron: dep-free file-backed scheduled agents (foreground runner) - #155

Merged
gnanam1990 merged 11 commits into
mainfrom
cron
Jun 10, 2026
Merged

zero cron: dep-free file-backed scheduled agents (foreground runner)#155
gnanam1990 merged 11 commits into
mainfrom
cron

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds zero cron — define file-backed scheduled agent jobs (standard 5-field cron) and a foreground zero cron run that fires due jobs by reusing the existing zero exec path. Dep-free, additive (new internal/cron package + new command).

This is the network-free, no-daemon residue of the upstream 05-daemon-cron module — the entire TuiDaemonAdapter/daemon/IPC, the second "automations" job system, the TUI leave-warning, and locale time handling are intentionally not ported.

Commands

zero cron add "<cron-expr>" [--prompt P | --recipe R] [--cwd D] [--model M] [--run-now]
zero cron list | pause <id> | resume <id> | rm <id>
zero cron run [--once] [--catch-up] [id...]
  • Schedule: standard 5 fields minute hour day-of-month month day-of-week — a small dep-free parser (tokens *, lists, ranges, steps, month/weekday names, 7=Sun) with standard Vixie DOM/DOW OR semantics, evaluated in local time.
  • run: foreground loop (Ctrl+C/SIGTERM to stop); --once fires currently-due jobs and exits (wire it under an external scheduler); --catch-up fires overdue jobs on start, otherwise the default is skip-and-reschedule.
  • A fire = zero exec --output-format stream-json --session-title cron:<id> --prompt=<job prompt>, reusing the agent + session persistence; the exit code is recorded to runs.jsonl.
  • Storage: $XDG_DATA_HOME/zero/cron/<id>/ (metadata.json + append-only runs.jsonl), mirroring sessions.Store.
  • Preset --recipes + loop.md prompt resolution (25KB cap, symlink-reject).

Design

  • internal/cron (pure, stdlib-only): Schedule (Parse/Next/String), file-backed Store, recipes, loop.md resolution. Now/RootDir injectable for tests.
  • internal/cli/cron.go + cron_run.go: command group + foreground runner behind an injectable exec seam (default cli.Run) for testability. Dispatch case + help in app.go.

Built with an adversarial workflow

Implemented via a multi-agent workflow (6 sequential TDD tasks, each verified) followed by a 3-lens adversarial panel (parser correctness, runner semantics, arg-safety). The panel found and this PR fixes 9 real issues, including:

  • Critical: Next() infinite loop on a DST spring-forward gap (30 2 * * * on the transition day) — fixed (absolute-time hour advance + forward-progress guard). Regression-tested against America/New_York.
  • High: path traversal in the store (cron rm ../.. could RemoveAll outside the root) — job ids now validated.
  • High: runaway re-fire when a job's schedule can't advance (--run-now bypassed the impossible-spec guard) — impossible specs rejected at add; unadvanceable jobs auto-pause.
  • Medium/Low: leap-year search window (Feb-29 across a century non-leap year), startup reconcile keeping exactly-due jobs, corrupt-job surfacing, RunRecord.Error capture, and dash-leading prompts via the inline --prompt= form.

Test Plan

  • go build ./... / GOOS=windows GOARCH=amd64 go build ./... clean
  • go vet ./... clean; gofmt clean
  • go test ./... green (parser table, Next rollovers/DST/leap/impossible, store round-trip + traversal + corrupt, runner once/catch-up/overlap/pause/reconcile, CLI)
  • go test -race ./internal/{cron,cli}/... green
  • Smoke: add/list/run --once/rm; impossible spec rejected; traversal rejected

Reviewer focus

  • Dep-free parser + Next (local time, Vixie OR, DST gap, leap window).
  • Foreground-only semantics: jobs fire only while cron run is active (documented); skip-and-reschedule default vs --catch-up.
  • A prompt is stored in plaintext under the data dir (warned on add); unattended fires inherit the normal autonomy/sandbox ceiling.

Summary by CodeRabbit

  • New Features

    • Added a top-level cron command with subcommands to add, list, pause, resume, remove, and run jobs (once or continuous, with catch-up and run-now).
    • Added built-in recipes, loop-prompt resolution (search priority, size limits, symlink handling), a persistent job store with run history, safe ID handling, and a robust cron schedule engine.
  • Tests

    • Extensive unit and integration tests covering scheduling logic, store operations, CLI flows, and cron run execution.

…e-fire, +)

- Next: fix infinite loop on DST spring-forward gaps (advance hours by absolute
  addition + a forward-progress guard); widen the search window to 9 years so a
  Feb-29 schedule across a century non-leap year (2096->2104) isn't reported as
  impossible.
- store: reject path-traversal job ids (Get/Update/Remove/AppendRun) so
  'cron rm ../..' can't delete outside the store; List now surfaces corrupt jobs
  as a warning instead of silently dropping them.
- runner: reject impossible schedules at add even with --run-now; auto-pause a
  job whose schedule can no longer advance (was re-firing every tick); only
  skip-reschedule STRICTLY-overdue jobs on startup (keep exactly-due ones);
  capture stderr into RunRecord.Error on non-zero exit; pass the prompt via the
  inline --prompt= form so dash-leading prompts aren't misparsed.
- Regression tests for each (DST, leap gap, traversal, corrupt-list, pause,
  reconcile, dash-prompt).
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: 8a2cbe289811
Changed files (13): internal/cli/app.go, internal/cli/cron.go, internal/cli/cron_run.go, internal/cli/cron_run_test.go, internal/cli/cron_test.go, internal/cron/loopprompt.go, internal/cron/loopprompt_test.go, internal/cron/next_test.go, internal/cron/recipes.go, internal/cron/schedule.go, internal/cron/schedule_test.go, internal/cron/store.go, and 1 more

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 722ce682-8932-47c5-8ddd-3ea77cafaa41

📥 Commits

Reviewing files that changed from the base of the PR and between c61179f and 8a2cbe2.

📒 Files selected for processing (2)
  • internal/cron/store.go
  • internal/cron/store_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/cron/store_test.go
  • internal/cron/store.go

Walkthrough

Adds a top-level cron CLI with cron parsing/Next-time computation, built-in recipes, loop-prompt resolution, a filesystem-backed job store, management subcommands (add/list/pause/resume/remove), a run loop that executes jobs via zero exec, and comprehensive unit tests.

Changes

Cron Scheduling Feature

Layer / File(s) Summary
Top-level CLI wiring
internal/cli/app.go
Registers the cron top-level command and updates CLI help output.
Cron parser and Next-time engine
internal/cron/schedule.go, internal/cron/schedule_test.go, internal/cron/next_test.go
Parses 5-field cron expressions into bitsets and computes DST-resilient next runs with bounded forward search; includes tests for progression, DST gaps, leap-year, and impossible schedules.
Recipes and loop prompt resolution
internal/cron/recipes.go, internal/cron/loopprompt.go, internal/cron/loopprompt_test.go
Provides preset recipe definitions and ResolveLoopPrompt that checks candidate loop.md files, skips symlinks, enforces a size cap, and falls back to a builtin prompt; tests cover precedence and size/symlink edge cases.
Filesystem-backed job store
internal/cron/store.go, internal/cron/store_test.go
Persists job metadata and newline JSON run logs under <root>/<id>/, validates/sanitizes IDs, performs atomic writes, lists resiliently, and exposes run retrieval; tested for CRUD, runs, XDG root, unsafe IDs, and partial corruption.
CLI management commands
internal/cli/cron.go, internal/cli/cron_test.go
Implements add (flags: prompt/recipe/cwd/model/--run-now), list (sorted output), pause, resume (recomputes NextRunAt), and remove; tests exercise add/list/remove, recipe expansion, invalid expr handling, and resume validation.
Job execution and scheduling update
internal/cli/cron_run.go, internal/cli/cron_run_test.go
Implements cron run with --once/--catch-up and id filters, fires due jobs via zero exec, records run history, advances or pauses schedules based on Next calculation, and reconciles overdue jobs on startup; covered by unit tests.

Sequence Diagram

sequenceDiagram
  participant User
  participant CLI as "zero cron"
  participant Store
  participant Schedule
  participant Exec as "zero exec"
  User->>CLI: add / run / list / resume
  CLI->>Store: Add/Get/List/Update
  CLI->>Schedule: Parse / Next(now)
  CLI->>Exec: run job (prompt,args)
  Exec-->>CLI: exit code + stderr
  CLI->>Store: AppendRun + Update(job)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • Gitlawb/zero#70: Also modifies internal/cli/app.go to register a new top-level subcommand in the dispatcher and help.
  • Gitlawb/zero#59: Adds a top-level CLI subcommand via edits to the same dispatcher/help locations.

Suggested reviewers

  • Vasanthdev2004
  • anandh8x
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main addition: a new cron subsystem for scheduling agents with file-backed storage and foreground execution, implemented without external dependencies.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cron

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
internal/cron/store_test.go (1)

72-90: ⚡ Quick win

Add regression coverage for Runs unsafe IDs.

Given the unsafe-ID protections elsewhere, add a Runs("../x") / Runs("/abs") rejection test so traversal protections stay enforced for history reads too.

Also applies to: 92-106

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cron/store_test.go` around lines 72 - 90, Update the unsafe-ID test
to also assert that the Runs method rejects traversal/absolute IDs: in
TestStoreRejectsUnsafeID add cases like "../x" and "/abs" (or similar unsafe
strings) to the loop and call s.Runs(id) expecting a non-nil error, just as you
do for s.Remove and s.Get; repeat the same addition for the other related test
covering lines 92-106 so history reads are covered too, and keep the existing
check that the sibling directory still exists after these calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/cron_run.go`:
- Line 121: The call to store.Update(j) currently ignores returned errors (seen
where store.Update is invoked with job variable j), risking stale NextRunAt and
lost run history; change these calls to check the error returned from
store.Update (and any similar store.Write/Save invocations) and handle failures
by logging the error via the existing logger and returning or propagating the
error (or retrying as appropriate) so the job state persistence cannot silently
fail; update the code paths that update j.NextRunAt and j.RunHistory to ensure
they only proceed after a successful store.Update and that failures surface to
the caller.

In `@internal/cli/cron.go`:
- Around line 119-121: The command currently silently ignores extra positional
arguments by taking only positional[0] into expr; update the cron add argument
parsing to reject extra positionals: if more than one positional token is
provided (i.e., len(positional) > 1) return a user-facing error (or print usage)
explaining that only a single expression/token is allowed. Locate the logic
around the variables positional and expr in the cron add handler (the block
containing "if len(positional) > 0 && expr == \"\" { expr = positional[0] }")
and add the validation there so additional tokens are not accepted silently.
- Around line 232-235: In cronResume, don't mark jobs active when schedule
parsing fails: only set job.Status = cron.StatusActive and update job.NextRunAt
= sched.Next(now()) inside the successful cron.Parse(job.Expr) branch (perr ==
nil); if cron.Parse returns an error, leave the job.Status unchanged (or set to
a failed/inactive state) and surface/log the parse error instead of resuming the
job so corrupted/impossible schedules are not reactivated.

In `@internal/cron/store.go`:
- Around line 210-220: The AppendRun write path currently defers f.Close() and
ignores its error; change it to capture and propagate Close() errors so buffered
write failures aren't dropped: in AppendRun (the block opening runs.jsonl and
using f, err, rec) remove the simple defer f.Close(), perform the write as
shown, then call cerr := f.Close() and if err == nil { err = cerr } before
returning so Close errors are returned when write succeeded but flush fails;
alternatively make the function use a named error return and a defer that sets
the named err to cerr if err == nil.
- Around line 223-229: The Runs method accepts an unvalidated id and joins it
into a filesystem path, enabling path traversal; validate/sanitize id before
calling s.jobDir(id) by cleaning it (e.g., filepath.Clean or filepath.Base) and
rejecting absolute paths or any cleaned id that equals "." or that begins with
"..", or alternatively build the full path and verify it is inside the cron root
with filepath.Rel against s.root; update Runs (and its use of s.jobDir) to
perform this check and return an error for invalid ids so callers cannot escape
the cron root.

---

Nitpick comments:
In `@internal/cron/store_test.go`:
- Around line 72-90: Update the unsafe-ID test to also assert that the Runs
method rejects traversal/absolute IDs: in TestStoreRejectsUnsafeID add cases
like "../x" and "/abs" (or similar unsafe strings) to the loop and call
s.Runs(id) expecting a non-nil error, just as you do for s.Remove and s.Get;
repeat the same addition for the other related test covering lines 92-106 so
history reads are covered too, and keep the existing check that the sibling
directory still exists after these calls.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 144fba40-e047-4516-8da1-e55bed81b84a

📥 Commits

Reviewing files that changed from the base of the PR and between 58e209d and f463899.

📒 Files selected for processing (13)
  • internal/cli/app.go
  • internal/cli/cron.go
  • internal/cli/cron_run.go
  • internal/cli/cron_run_test.go
  • internal/cli/cron_test.go
  • internal/cron/loopprompt.go
  • internal/cron/loopprompt_test.go
  • internal/cron/next_test.go
  • internal/cron/recipes.go
  • internal/cron/schedule.go
  • internal/cron/schedule_test.go
  • internal/cron/store.go
  • internal/cron/store_test.go

Comment thread internal/cli/cron_run.go Outdated
Comment thread internal/cli/cron.go Outdated
Comment thread internal/cli/cron.go
Comment thread internal/cron/store.go Outdated
Comment thread internal/cron/store.go
…traversal, +)

- fireJob/reconcile/cronRun now surface AppendRun/Update failures to stderr
  instead of discarding them (a failed write no longer silently re-fires/loses
  history); fireJob takes stderr.
- cron add rejects extra positional arguments instead of silently ignoring them.
- cronResume rejects an unparseable/impossible schedule instead of reactivating a
  job with a stale/zero NextRunAt.
- store.AppendRun returns the Close() error (buffered-write failures surface on
  close); store.Runs now validates the id (path-traversal guard, matching the
  other store methods).
- tests: Runs traversal rejection, cron add extra-args, resume-impossible.
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review — all 5 findings addressed in dc073a3 (store-error surfacing, extra-arg rejection, resume schedule guard, AppendRun Close error, Runs traversal guard). gofmt/vet/build + Windows + go test ./... + -race green.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR details and commits
gh pr view 155 --repo Gitlawb/zero --json commits,files 2>/dev/null | jq '.commits[-3:], .files[].path' 2>/dev/null || true

# Get the diff for the specific commit dc073a3
gh api repos/Gitlawb/zero/commits/dc073a3 2>/dev/null | jq '.files[] | {filename: .filename, patch: .patch}' 2>/dev/null || true

Length of output: 9905



@gnanam1990 Reviewed dc073a3 — all five fixes check out:

  1. Extra-arg rejection — the split to len(positional) > 1 → exitUsage / len(positional) == 1 && expr == "" is clean and correct. TestCronAddRejectsExtraArgs covers both the exit code and "no job stored" invariant.

  2. Store-error surfacingfireJob now takes stderr io.Writer and both AppendRun and Update failures are printed; reconcileOverdue does the same for its Update call. No more silent _ = discards on the hot path.

  3. Resume schedule guard — the old if perr == nil { … } silently left NextRunAt as zero on a bad/impossible expression. The new two-stage check (parse error → reject; next.IsZero() → reject) is the right pattern. TestCronResumeRejectsImpossibleSchedule (Feb 30 schedule) confirms the job stays StatusPaused.

  4. AppendRun Close error — removing defer f.Close() and returning f.Close() on the success path correctly surfaces buffered-write failures. Early-error paths properly call _ = f.Close() (acceptable: already in an error return).

  5. Runs traversal guardvalidID(id) at the top of Runs() is now consistent with every other store method. The extended TestStoreRejectsUnsafeID covers it.

All five are correct, targeted, and have regression tests. LGTM.

[approve]

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/cli/cron_test.go (2)

25-25: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Check store operation errors in tests.

Multiple store operation errors are ignored in this test (lines 25, 44, 51). If List() or Get() fail, the test continues with zero-value returns, causing misleading assertions or false-positive passes.

🛡️ Proposed fix for error handling
-	jobs, _ := store.List()
+	jobs, err := store.List()
+	if err != nil {
+		t.Fatalf("list failed: %v", err)
+	}

Apply similar fixes at lines 44 and 51:

-	if j, _ := store.Get(jobs[0].ID); j.Status != cron.StatusPaused {
+	j, err := store.Get(jobs[0].ID)
+	if err != nil {
+		t.Fatalf("get failed: %v", err)
+	}
+	if j.Status != cron.StatusPaused {
-	if jobs, _ := store.List(); len(jobs) != 0 {
+	jobs, err = store.List()
+	if err != nil {
+		t.Fatalf("list after rm failed: %v", err)
+	}
+	if len(jobs) != 0 {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/cron_test.go` at line 25, The test ignores errors from store
operations (store.List() and store.Get()), which can mask failures; update the
calls in cron_test.go to capture the error return (e.g., jobs, err :=
store.List() and job, err := store.Get(id)) and assert/fail on error (use
t.Fatalf or require.NoError(t, err)) before using the returned values so the
test fails immediately when the store operations fail.

75-75: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Check store.List() error.

If List() fails, the test continues with a zero-value (empty slice), causing a misleading assertion or false-positive pass.

🛡️ Proposed fix
-	jobs, _ := store.List()
+	jobs, err := store.List()
+	if err != nil {
+		t.Fatalf("list failed: %v", err)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/cron_test.go` at line 75, The test currently ignores the return
error from store.List(), which can mask failures; update the cron_test.go test
to capture the error from store.List() (e.g., jobs, err := store.List()) and
immediately fail the test if err != nil (use t.Fatalf or require.NoError(t, err)
consistent with the test suite) before asserting on jobs so a List() failure
doesn't produce a false-positive.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/cron_test.go`:
- Line 88: The test currently ignores the error return from store.List(), which
can mask failures; update the test to capture and assert the error from
store.List() (e.g., err := store.List(); if err != nil { t.Fatalf("List failed:
%v", err) } or use require.NoError(t, err)) before asserting len(jobs),
referencing the existing call to store.List() and the jobs variable so the test
fails loudly on List() errors instead of treating an error as an empty slice.
- Line 96: The test currently ignores errors from store.Add and store.Get which
can produce misleading results; update the test around the call to
store.Add(cron.Job{...}) and the subsequent store.Get(job.ID) to check their
returned errors and fail the test immediately (e.g., t.Fatalf or equivalent)
when err != nil so you don't proceed with a zero-value job or an empty job.ID;
reference the Add and Get calls and the job variable as the locations to add
these checks and ensure any subsequent use (including invoking the resume
command) only runs when Add and Get succeeded.

---

Outside diff comments:
In `@internal/cli/cron_test.go`:
- Line 25: The test ignores errors from store operations (store.List() and
store.Get()), which can mask failures; update the calls in cron_test.go to
capture the error return (e.g., jobs, err := store.List() and job, err :=
store.Get(id)) and assert/fail on error (use t.Fatalf or require.NoError(t,
err)) before using the returned values so the test fails immediately when the
store operations fail.
- Line 75: The test currently ignores the return error from store.List(), which
can mask failures; update the cron_test.go test to capture the error from
store.List() (e.g., jobs, err := store.List()) and immediately fail the test if
err != nil (use t.Fatalf or require.NoError(t, err) consistent with the test
suite) before asserting on jobs so a List() failure doesn't produce a
false-positive.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c795bd74-51ea-427e-8138-40dc90b73ae3

📥 Commits

Reviewing files that changed from the base of the PR and between f463899 and dc073a3.

📒 Files selected for processing (5)
  • internal/cli/cron.go
  • internal/cli/cron_run.go
  • internal/cli/cron_test.go
  • internal/cron/store.go
  • internal/cron/store_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/cli/cron_run.go
  • internal/cron/store_test.go
  • internal/cron/store.go
  • internal/cli/cron.go

Comment thread internal/cli/cron_test.go Outdated
Comment thread internal/cli/cron_test.go Outdated
CodeRabbit (critical): the cron CLI tests ignored errors from store.List/Add/Get, so a store failure would surface as a misleading assertion on a zero-value job/slice instead of a clear failure. Check every store op's error across the test file (not only the two flagged sites).
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

Fixed (critical): the cron CLI tests now check every store.List/Add/Get error (not only the two flagged sites — the same pattern existed at four other call sites), so a store failure fails clearly instead of asserting on a zero-value job/slice. Full suite + Windows green. @coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the latest commits on the PR
gh pr view 155 --repo Gitlawb/zero --json commits | jq '.commits[-3:] | .[] | {sha: .oid, msg: .messageHeadline}'

# Get the full diff of the latest commit
LATEST=$(gh pr view 155 --repo Gitlawb/zero --json commits | jq -r '.commits[-1].oid')
echo "Latest commit: $LATEST"
gh api repos/Gitlawb/zero/commits/$LATEST 2>/dev/null | jq -r '.files[] | "=== \(.filename) ===\n\(.patch)"'

Length of output: 3984



@gnanam1990 Verified commit e7513bc0 — all 7 previously-silent store call sites in cron_test.go now properly check and fail-fast on error:

Test Call site Fixed
TestCronAddListRemove store.List() after add
TestCronAddListRemove store.Get() after pause
TestCronAddListRemove store.List() after rm
TestCronAddRecipe store.List() after add
TestCronAddRejectsExtraArgs store.List() after rejected add
TestCronResumeRejectsImpossibleSchedule store.Add()
TestCronResumeRejectsImpossibleSchedule store.Get() after rejected resume

No remaining _, _ patterns. Each is a proper two-step val, err := …; if err != nil { t.Fatalf(…) }. LGTM.

[approve]

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: request changes.

Finding:

  • P1 internal/cron/store.go:65: DefaultRoot does not actually mirror sessions.DefaultRoot. It reads only XDG_DATA_HOME/HOME from envMap, and if HOME is unset it falls through to filepath.Join("", ".local", "share"), which is a relative .local/share path. On Windows/PowerShell or other environments without HOME, zero cron add/list/run will store jobs under the caller's current working directory instead of a stable user data directory, so jobs disappear when running from a different cwd and may create repo-local .local data. sessions.DefaultRoot falls back to os.UserHomeDir() when HOME is empty; cron should do the same, and the test should cover empty HOME.

Validation in an isolated PR worktree passed before this review: git diff --check origin/main...HEAD, go test ./internal/cron ./internal/cli, and go build ./cmd/zero. The scheduler/parser path otherwise looks solid.

# Conflicts:
#	internal/cli/app.go
Vasanth (P1): cron's DefaultRoot mirrored neither sessions.DefaultRoot nor reality — with no XDG_DATA_HOME and no HOME it produced a RELATIVE ".local/share/zero/cron" under the caller's cwd, so on Windows/restricted shells cron jobs scattered per working directory and could write repo-local data. Now falls back to os.UserHomeDir() like sessions.DefaultRoot. Adds TestDefaultRootEmptyHomeFallsBackToUserHome (asserts the path stays absolute).
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@Vasanthdev2004 fixed (P1): cron.DefaultRoot now mirrors sessions.DefaultRoot — it falls back to os.UserHomeDir() when HOME is unset, so it never returns a relative .local/share under the caller's cwd (no more per-cwd job scatter on Windows/restricted shells). Added TestDefaultRootEmptyHomeFallsBackToUserHome asserting the empty-env path stays absolute and ends in zero/cron. Full suite + -race + Windows green.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: approve.

Reviewed latest head 8a2cbe2. The previous blocker around cron.DefaultRoot is fixed: it now mirrors the sessions store behavior and falls back to os.UserHomeDir() when HOME is unset, so cron data will not scatter into a relative .local/share path under the caller's cwd.

Validation run locally:

  • go test ./internal/cron ./internal/cli
  • go test ./...
  • go vet ./...
  • go run ./cmd/zero-release build
  • go run ./cmd/zero-release smoke
  • git diff --check origin/main...HEAD
  • manual: go run ./cmd/zero cron --help

Notes: the cron parser/store/runner boundaries look coherent, the foreground runner uses the existing zero exec path, unsafe IDs are guarded, loop prompt loading skips symlinks and caps size, and corrupt-job listing degrades without hiding healthy jobs. No remaining blockers from my pass.

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