Schedule Manager
Generate GitHub Actions workflow files that run Soleur skills on a schedule. Two modes:
- Recurring (
--cron): Standard cron-triggered workflow that fires on every matching tick. - One-time (
--once --at <YYYY-MM-DD>): Single-fire workflow that self-disables after running. Useful for "remind me to do X in 2 weeks" style tasks.
Each schedule becomes a standalone .github/workflows/scheduled-<name>.yml.
When to use this skill vs harness schedule
Two skills exist with the name schedule. They serve different jobs:
| Use this (soleur:schedule) when | Use harness schedule when | |---|---| | Push commits, open PRs, modify the user's repo | Analyze, summarize, report — no repo writes | | Use repo secrets (Doppler, Vercel, Cloudflare) | No secrets needed | | Invoke a Soleur skill (/soleur:<skill>) | Generic Claude API task | | Run Terraform / migrations / deploys | Read-only research, posting somewhere |
Examples for soleur:schedule:
- "Open a cleanup PR removing feature flag X in 2 weeks" →
--once - "Run a weekly Terraform drift check" → recurring
Examples for harness schedule:
- "Summarize recent issues every Monday and post to Slack"
- "Check if a vendor's API changed and email the diff"
If the agent doesn't need access to your repo, prefer harness schedule.
Both schedule skills generate GitHub-Actions cron only. For a one-time, autonomous, server-side action that needs fire-time prd secrets or an installation-token repo write (no operator, no GH-Actions runner), use the Inngest path instead: a self-armed oneshot (ADR-046, bespoke logic) or the generic reminder primitive (POST /api/internal/schedule-reminder, no deploy for a comment / registered check — incl. the parametric sentry-issue-rate check). See [inngest-oneshot-and-reminder-patterns.md](../../../../knowledge-base/engineering/operations/runbooks/inngest-oneshot-and-reminder-patterns.md). create's Step 0 routing gate (below) enforces this automatically — Inngest is the structural default for secret/server-side work; GHA is the pure-GH-ops exception.
Commands
create
Generate a new scheduled workflow.
Step 0: Execution-substrate routing gate (run FIRST — before generating any GHA YAML).
This skill generates GitHub-Actions cron. But a GHA runner has no Doppler/prd access, so a scheduled task that needs fire-time prd secrets (Sentry/Supabase/ Doppler tokens) will fire but fail to authenticate — and the new-scheduled-cron-prefer-inngest PreToolUse hook blocks the YAML write anyway. Route by what the task actually needs BEFORE generating anything; Inngest is the structural default for secret/server-side work, GHA the exception.
Classify $ARGUMENTS (the task description):
- Needs fire-time prd secrets OR autonomous server-side logic. Grep the task
for doppler, SUPABASE_SERVICE_ROLE_KEY, SENTRY_* tokens, an installation- token repo write, or "server-side"/"runtime" execution. Route to Inngest — STOP, do not generate GHA:
- Verification-shaped ("did Sentry issue X drop below N/day?", "is metric Y
under threshold?", "comment/close an issue after a check") → the reminder primitive's named-check (zero-deploy to arm; POST /api/internal/schedule-reminder). If a suitable check exists in CHECK_REGISTRY (e.g. sentry-issue-rate, the worked example — events/day of a tagged Sentry issue over a window, with optional close_on_pass), arming is a single zero-deploy POST. If not, add a small reviewed check to the registry (one deploy), then arm. Note: a check that sets close_on_pass performs a scoped issue mutation — it closes the action's own report_to_issue (never an arbitrary issue) — so treat arming a closing check as a privileged action, not a read-only probe.
- Bespoke server-side logic (multi-step, custom data shape) → a self-armed
oneshot (ADR-046; one .ts + boot-arm + deploy).
- Full decision matrix + the exact arm HTTP shape: [
inngest-oneshot-and-reminder-patterns.md](../../../../knowledge-base/engineering/operations/runbooks/inngest-oneshot-and-reminder-patterns.md). - **Periodic verification needing a secret already wired to the follow-through
sweeper** (scheduled-followthrough-sweeper.yml passes an allowlist; e.g. the job exposes the GitHub secret SENTRY_IAC_AUTH_TOKEN as env SENTRY_AUTH_TOKEN) → a vetted follow-through script (under the repo's followthroughs dir) + a follow-through directive. No new workflow, no Inngest deploy.
- Pure-GH ops (no prd secrets — a skill reading public state, a Dependabot-
like job). Only this class falls through to Step 0a and GHA-cron generation.
Announce the routing decision. If routing to Inngest/sweeper, do NOT proceed to Step 0a — point the operator at the substrate and stop. The new-scheduled-cron-prefer-inngest hook is now the BACKSTOP (it should rarely fire because this gate routes upstream); its gate-override is reserved for a genuinely pure-GH op and the override comment must assert "no prd secrets".
Step 0a: Mode detection (recurring vs one-time)
Inspect $ARGUMENTS:
- If both
--cronand--onceare present → error:Cannot specify both --once and --cron. Stop. - If neither
--cronnor--onceis present → error:Specify either --once <YYYY-MM-DD> or --cron <expression>. Stop. (No silent default — the operator must declare intent.) - If
--onceis present → one-time mode. Validate per Step 0c below, then skip to Step 2 → Step 3b. - If
--cronis present → recurring mode. Continue with Step 0b below.
Step 0b: Check arguments (recurring mode)
If $ARGUMENTS contains --name, --skill, --cron, and --model flags, extract values directly and skip to Step 2. Validate each value using the same rules below. If any required flag is missing, proceed to Step 1 for the missing parameters only. Optional flags: --timeout (minutes, default 30) and --max-turns (default 30).
Step 0c: Check arguments (one-time mode)
For --once, all five flags are MANDATORY. There is no AskUserQuestion fallback (Step 1 is recurring-only). Operator-supplied values are required so the workflow file is self-documenting at delete/cleanup time. Each value MUST match its regex EXACTLY before substitution into the YAML — these regexes are the load-bearing defense against shell/YAML injection at fire time.
If ANY regex below fails, emit ERROR: --<flag> value '<value>' does not match required pattern <regex> to stderr and stop.
--once(mode flag, no value)--at <YYYY-MM-DD>— strict ISO date pattern^\d{4}-\d{2}-\d{2}$. Default time = 09:00 UTC. Natural-language dates ("2 weeks from now",tomorrow) and ISO datetimes (2026-05-17T03:00) are intentionally rejected. Validate with:
``bash python3 -c " import re, sys from datetime import datetime, timezone, timedelta if not re.fullmatch(r'\d{4}-\d{2}-\d{2}', sys.argv[1]): sys.exit(f\"ERROR: --at value '{sys.argv[1]}' does not match required pattern ^\\\d{{4}}-\\\d{{2}}-\\\d{{2}}\$\") d = datetime.fromisoformat(sys.argv[1]).replace(tzinfo=timezone.utc) now = datetime.now(timezone.utc) if d.date() < now.date(): sys.exit('ERROR: --at date is in the past') if (d - now) > timedelta(days=50): sys.exit('ERROR: --at date is more than 50 days out (GHA auto-disables workflows after 60d inactivity; 10d margin)') " '<AT_VALUE>' ``
--issue <N>— GitHub issue number, pattern^[1-9][0-9]{0,8}$(positive integer, no leading zeros, ≤9 digits).--comment <id>— GitHub comment ID, pattern^[1-9][0-9]{0,18}$(positive int64, no leading zeros). Look it up viagh api repos/<OWNER>/<REPO>/issues/<N>/comments --jq '.[] | "\(.id) \(.user.login) \(.created_at): \(.body | .[0:80])"'if the operator needs to find it.--name <kebab-case>— schedule name, pattern `^[a-z][a-z0-9-]{