setup-cron · diff
git:20260531.fcd5f6a to git:20260714.471fb70
61 added, 110 removed. Audit A to A.
---
name: setup-cron
- description: "Use when the user asks to schedule daily-cycle, set up cron, or run daily-cycle every day. Installs an OS-level schedule (macOS LaunchAgent / Windows Task / Linux cron) for `/daily-cycle PROJECT`. Also covers /loop and /schedule alternatives."
+ description: "Use when the user asks to schedule daily-cycle, set up cron, or run daily-cycle every day. Installs an OS-level schedule (macOS LaunchAgent / Windows Task / Linux cron) for `/daily-cycle PROJECT`. Also covers the in-Claude-Code /loop alternative."
argument-hint: "[project-name]"
allowed-tools:
- Bash
- Read
- Write
- AskUserQuestion
- mcp__plugin_leadace_api__list_projects
---
# Setup-cron - Schedule Daily Automation
- A skill that wires `/daily-cycle` into the user's OS scheduler so it runs every day without manual invocation. The skill is **idempotent**: re-running it replaces any existing LeadAce schedule entry.
-
- The skill writes one of:
- - macOS LaunchAgent at `~/Library/LaunchAgents/ai.leadace.daily-cycle.plist`
- - Windows scheduled task named `LeadAceDailyCycle`
- - Linux user crontab line tagged `# leadace-daily-cycle`
-
- Or, if the user prefers in-Claude-Code scheduling, it explains how to use the plugin's existing `/loop` skill or Claude Code's built-in `/schedule` (Remote Trigger).
-
- **Before starting:** `Read` `${CLAUDE_PLUGIN_ROOT}/references/workspace-conventions.md` and follow the cross-cutting conventions there (data storage, MCP error handling, document writes, output discipline).
-
- ## Prerequisites
-
- - The user has already run `/leadace` and has at least one project. If not, abort and ask them to run `/leadace` first.
- - The `claude` CLI is on `PATH` (required for OS-level scheduling). The skill verifies this with `which claude`.
- - The user has an active Claude Code session with a valid LeadAce MCP OAuth token. **Headless `claude` invocations cannot complete OAuth interactively** — if the token expires (currently every 30 days, sliding), the next scheduled run fails until the user signs in again.
-
- ## Steps
-
- ### 1. Pick the project
-
- If `$0` is given, use it. Otherwise call `mcp__plugin_leadace_api__list_projects` and:
- - If exactly one project exists, use it.
- - If multiple exist, ask via `AskUserQuestion`.
- - If none, abort: "No projects yet. Run `/leadace <your-homepage-URL>` first."
-
- Hold the chosen name in `PROJECT_NAME`.
-
- ### 2. Pick the scheduling method
-
- Use `AskUserQuestion` with these options (omit options that don't apply to the user's OS — detect with `uname -s`: `Darwin` -> mac, `Linux` -> linux, otherwise -> windows):
-
- - `macOS LaunchAgent` — runs even when no Terminal is open, as long as the user is logged in (mac only)
- - `Windows Task Scheduler` — runs on a fixed schedule (windows only)
- - `Linux cron` — user crontab entry (linux only)
- - `/loop (in-Claude-Code)` — runs only while a Claude Code session has `/loop` active
- - `/schedule (in-Claude-Code, Remote Trigger)` — runs in the cloud regardless of the local machine, OS-independent
-
- If the user picks `/loop` or `/schedule`, skip steps 3-5 and jump to "In-Claude-Code alternative" below.
-
- ### 3. Pick run time
-
- Ask in plain text (do not use AskUserQuestion for free-form): "What local time should `/daily-cycle` run? (24h `HH:MM`, default `09:00`)". Validate the format; default to `09:00` if blank.
-
- ### 4. Verify `claude` CLI
-
- ```bash
- which claude || echo "MISSING"
- ```
+ Installs an OS-scheduler entry that runs `/daily-cycle <project>` headless on a daily schedule. One entry per project: re-running for the same project replaces its entry; entries for different projects coexist.
- If `MISSING`, abort: "The `claude` CLI is not on PATH. OS-level scheduling needs it. Install Claude Code (https://claude.com/claude-code) or pick `/loop` / `/schedule` instead."
+ **Before starting:** `Read` `${CLAUDE_PLUGIN_ROOT}/references/workspace-conventions.md` and follow the cross-cutting conventions there.
- Capture the absolute path; LaunchAgent / cron / schtasks all need an absolute path because they don't inherit a shell PATH.
+ ## Prerequisites — verify, and abort with the fix if missing
- ### 5. Install the scheduler entry
+ - At least one project exists. None -> "Run `/leadace <your-homepage-URL>` first."
+ - `claude` CLI on PATH (`which claude`; capture the absolute path). Missing -> offer the `/loop` alternative.
+ - Headless runs cannot answer permission prompts: any tool without a stored allow rule is denied, and repeated denials abort the run. The user should have run `/daily-cycle` interactively at least once from this directory, answering "always allow" for its tools; if they haven't, warn that scheduled runs may abort until they do. Never compensate with permission-bypass flags — denying un-allowed tools is the intended fail-safe.
+ - The MCP OAuth token is reused from interactive sessions and expires after ~30 days of inactivity; scheduled runs then fail until the user signs in interactively again.
- Generate the config for the chosen OS (templates below). Show the user the exact file/command that will be installed and ask `AskUserQuestion`: "Install now? (yes / no, just print)". On `no`, print the install commands and stop. On `yes`, run the install commands shown for that OS.
+ ## Confirm with the user
- After installation, verify:
- - macOS: `launchctl list | grep ai.leadace.daily-cycle` returns a row
- - Linux: `crontab -l | grep leadace-daily-cycle` returns the line
- - Windows: `schtasks /Query /TN LeadAceDailyCycle` returns the task
+ Use `AskUserQuestion` for enumerable choices, plain text for free-form input.
- If verification fails, surface the raw output and stop without claiming success.
+ 1. **Project**: `$0` if given; else `list_projects` — exactly one -> use it, several -> ask.
+ 2. **Method**: the OS scheduler matching `uname -s`, or in-Claude-Code `/loop`. If `/loop`: print `/loop 24h /daily-cycle <PROJECT_NAME>` (runs only while that session stays open) and stop. `/schedule` cloud routines cannot run LeadAce yet — plugin skills and the MCP server are not available in cloud runs.
+ 3. **Schedule**: time (24h `HH:MM`, local timezone, default `09:00`) and frequency (every day / weekdays only).
+ 4. **Model**: default = omit `--model` so the run uses the user's configured model; add `--model <name>` only if the user names one.
+ 5. Show the exact file/command about to be installed and ask "Install now / just print". On print: output the commands and stop.
- ### 6. Report
+ ## Invariants of the scheduled command
- Print:
- - The schedule (`HH:MM` daily, OS, project name)
- - The file/task the skill installed and how to inspect/remove it
- - A reminder: "OAuth token currently expires after ~30 days of inactivity. If the scheduled run starts failing, run `/leadace` again from an interactive Claude Code session to re-sign in."
+ Every template below must:
- ---
+ - run `<CLAUDE_PATH> -p "/daily-cycle <PROJECT_NAME>" --permission-mode dontAsk` (plus `--model` if chosen), with `<CLAUDE_PATH>` absolute — schedulers don't inherit shell PATH
+ - set `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=10800000` (3 h) — daily-cycle runs its phases as sub-agents, and headless mode otherwise cuts sub-agent waits off at 10 minutes
+ - start in the current session's working directory (`<WORKDIR>` = `pwd`) — plugins, MCP servers, and permission rules are discovered from the cwd, so a different start directory can silently lose the LeadAce plugin
+ - carry `LEADACE_MCP_URL` into the entry's environment if it is set in the current session (self-host) — schedulers don't read shell rc files. Place it alongside `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` (plist `EnvironmentVariables` / cron line prefix / `.cmd` `set`); omit entirely when unset
+ - embed `<PROJECT_SLUG>` in every named artifact — scheduler label/task name, plist/wrapper file, crontab tag, log files — so schedules for several projects coexist and replace/remove operations touch only their own project
+ - append stdout/stderr to a log file in the user's home
## Templates
- Use the user's local timezone (whatever `date +%Z` reports). Substitute `<HH>`, `<MM>`, `<PROJECT_NAME>`, `<CLAUDE_PATH>` (absolute path from `which claude`), and `<HOME>` (`echo $HOME`).
-
- ### macOS LaunchAgent
+ Substitute `<HH>`, `<MM>`, `<PROJECT_NAME>`, `<PROJECT_SLUG>`, `<CLAUDE_PATH>`, `<WORKDIR>`, `<HOME>`.
+ `<PROJECT_SLUG>` = the project name lowercased, with every character outside `[a-z0-9]` replaced by `-` (collapse runs, trim leading/trailing `-`).
- Path: `<HOME>/Library/LaunchAgents/ai.leadace.daily-cycle.plist`
+ ### macOS — `<HOME>/Library/LaunchAgents/ai.leadace.daily-cycle.<PROJECT_SLUG>.plist`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
- <string>ai.leadace.daily-cycle</string>
+ <string>ai.leadace.daily-cycle.<PROJECT_SLUG></string>
<key>ProgramArguments</key>
<array>
<string><CLAUDE_PATH></string>
- <string>--print</string>
+ <string>-p</string>
<string>/daily-cycle <PROJECT_NAME></string>
+ <string>--permission-mode</string>
+ <string>dontAsk</string>
</array>
+ <key>WorkingDirectory</key>
+ <string><WORKDIR></string>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer><HH></integer>
<key>Minute</key><integer><MM></integer>
</dict>
<key>StandardOutPath</key>
- <string><HOME>/Library/Logs/leadace-daily-cycle.log</string>
+ <string><HOME>/Library/Logs/leadace-daily-cycle.<PROJECT_SLUG>.log</string>
<key>StandardErrorPath</key>
- <string><HOME>/Library/Logs/leadace-daily-cycle.err.log</string>
+ <string><HOME>/Library/Logs/leadace-daily-cycle.<PROJECT_SLUG>.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
+ <key>CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS</key>
+ <string>10800000</string>
</dict>
</dict>
</plist>
```
- Install:
+ Weekdays only: make `StartCalendarInterval` an array of five dicts, each also containing `<key>Weekday</key><integer>N</integer>` for N in 1-5.
+ Write the plist with the Write tool (heredoc escaping is error-prone), then install:
+
```bash
mkdir -p ~/Library/LaunchAgents
- # Write the plist with Write tool, not heredoc, so we don't tangle with shell escaping
- launchctl unload ~/Library/LaunchAgents/ai.leadace.daily-cycle.plist 2>/dev/null
- launchctl load ~/Library/LaunchAgents/ai.leadace.daily-cycle.plist
+ launchctl unload ~/Library/LaunchAgents/ai.leadace.daily-cycle.<PROJECT_SLUG>.plist 2>/dev/null
+ launchctl load ~/Library/LaunchAgents/ai.leadace.daily-cycle.<PROJECT_SLUG>.plist
```
- Inspect/remove later:
- - `launchctl list | grep ai.leadace.daily-cycle`
- - `launchctl unload ~/Library/LaunchAgents/ai.leadace.daily-cycle.plist && rm ~/Library/LaunchAgents/ai.leadace.daily-cycle.plist`
-
- ### Linux cron
-
- Add this line to the user's crontab (preserve any existing lines):
+ ### Linux — user crontab
```
- <MM> <HH> * * * <CLAUDE_PATH> --print "/daily-cycle <PROJECT_NAME>" >> <HOME>/.leadace-daily-cycle.log 2>&1 # leadace-daily-cycle
+ <MM> <HH> * * * cd <WORKDIR> && CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=10800000 <CLAUDE_PATH> -p "/daily-cycle <PROJECT_NAME>" --permission-mode dontAsk >> <HOME>/.leadace-daily-cycle.<PROJECT_SLUG>.log 2>&1 # leadace-daily-cycle-<PROJECT_SLUG>
```
- Install (replace any existing leadace-daily-cycle line):
+ Weekdays only: `<MM> <HH> * * 1-5`. Install by replacing any existing line with the same tag:
```bash
- ( crontab -l 2>/dev/null | grep -v "# leadace-daily-cycle" ; echo '<MM> <HH> * * * <CLAUDE_PATH> --print "/daily-cycle <PROJECT_NAME>" >> <HOME>/.leadace-daily-cycle.log 2>&1 # leadace-daily-cycle' ) | crontab -
+ ( crontab -l 2>/dev/null | grep -v "# leadace-daily-cycle-<PROJECT_SLUG>" ; echo '<the line above>' ) | crontab -
```
- Inspect/remove later:
- - `crontab -l | grep leadace-daily-cycle`
- - `crontab -l | grep -v "# leadace-daily-cycle" | crontab -`
+ ### Windows — Task Scheduler
- ### Windows Task Scheduler
+ Write `<HOME>\leadace-daily-cycle-<PROJECT_SLUG>.cmd` (a wrapper file sidesteps `/TR` quoting limits):
- Run from PowerShell or cmd. The user must already have `claude` on PATH for the task — verify by running `claude --version` in the same shell.
+ ```bat
+ @echo off
+ cd /d <WORKDIR>
+ set CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=10800000
+ "<CLAUDE_PATH>" -p "/daily-cycle <PROJECT_NAME>" --permission-mode dontAsk >> "%USERPROFILE%\leadace-daily-cycle-<PROJECT_SLUG>.log" 2>&1
+ ```
```powershell
- schtasks /Create /TN LeadAceDailyCycle /SC DAILY /ST <HH>:<MM> /TR "\"<CLAUDE_PATH>\" --print \"/daily-cycle <PROJECT_NAME>\"" /F
+ schtasks /Create /TN LeadAceDailyCycle-<PROJECT_SLUG> /SC DAILY /ST <HH>:<MM> /TR "\"<HOME>\leadace-daily-cycle-<PROJECT_SLUG>.cmd\"" /F
```
- Inspect/remove later:
- - `schtasks /Query /TN LeadAceDailyCycle`
- - `schtasks /Delete /TN LeadAceDailyCycle /F`
-
- ---
-
- ## In-Claude-Code alternative
-
- If the user picked `/loop` or `/schedule`:
-
- - **`/loop`**: tell the user to start a Claude Code session and run `/loop 24h /daily-cycle <PROJECT_NAME>`. The loop continues only while that session stays open. Best when the user always has Claude Code running anyway.
- - **`/schedule`**: tell the user to run `/schedule` (Remote Trigger) and follow the prompt to create a daily routine that runs `/daily-cycle <PROJECT_NAME>` at the desired time. Runs in the cloud, OS-independent, survives machine sleep.
+ Weekdays only: `/SC WEEKLY /D MON,TUE,WED,THU,FRI`.
- Print the exact command they should run, then stop. The skill does not auto-install in-Claude-Code alternatives — those are user-driven.
+ ## Verify, then report
- ---
+ Verification (on failure: show the raw output and stop without claiming success):
- ## Notes
+ - macOS: `launchctl list | grep ai.leadace.daily-cycle.<PROJECT_SLUG>`
+ - Linux: `crontab -l | grep leadace-daily-cycle-<PROJECT_SLUG>`
+ - Windows: `schtasks /Query /TN LeadAceDailyCycle-<PROJECT_SLUG>`
- - The skill never tries to bypass safety prompts in scheduled runs. `claude --print` runs the skill non-interactively and the LLM will refuse irreversible actions if it would normally prompt. The user's plan-tier rate limits still apply at run time.
- - If the user is on a self-host backend, the schedule does not need any change — `claude` reads `LEADACE_MCP_URL` from the user's existing Claude Code config the same way as the interactive session.
+ Report: the schedule (time, frequency, project), the installed file/task, the log path, how to remove it (macOS: `launchctl unload <plist> && rm <plist>` / Linux: `crontab -l | grep -v "# leadace-daily-cycle-<PROJECT_SLUG>" | crontab -` / Windows: `schtasks /Delete /TN LeadAceDailyCycle-<PROJECT_SLUG> /F`), and the ~30-day OAuth reminder from Prerequisites.