Skip to main content

kind: RecurringIssue

What it is

A RecurringIssue is a workspace-scoped, crew-owned schedule that stamps out a fresh issue every time its cron expression fires. It is the declarative equivalent of opening the same recurring task in your project tracker every week — “weekly status review”, “monthly billing reconciliation”, “daily on-call handoff” — except authored in YAML, version-controlled, and applied through crewship apply --file recurring.yaml. Every recurring issue must belong to a specific crew (the crew_slug field is required). The cron and timezone fields drive the scheduler; the nested template: block describes the issue that gets created on each fire.

YAML schema

Field reference

Cron syntax

spec.cron uses the standard 5-field cron expression parsed by github.com/robfig/cron/v3, which mirrors the dialect of crontab(5):
Useful examples: Special syntax:
  • Lists: 1,15,30 — minutes 1, 15, 30
  • Ranges: 1-5 — Monday through Friday (in DOW)
  • Steps: */5 — every 5th unit
  • Names: JAN, FEB, …, MON, TUE, … (case-insensitive)
The parser does not support the @yearly, @monthly, @hourly descriptor shortcuts — write the equivalent cron string instead (0 0 1 1 * for yearly, etc.). It also does not support seconds (no 6-field form) because the existing server handler uses the same 5-field parser; staying in lockstep prevents a manifest from validating client-side and then failing server-side. The timezone is independent of the system’s TZ — 0 9 * * MON with timezone: Europe/Prague fires at 09:00 Prague time regardless of where the Crewship server runs.

Examples

Minimal — daily standup reminder

Realistic — weekly review with labels and assignee

Cross-kind references in one apply

A single crewship apply --file can declare every dependency the recurring issue needs:
Apply runs phases in topological order (crew → labels/projects → recurring issues), so every cross-kind slug resolves cleanly even on a brand-new workspace.

CLI reference

The crewship recurring command (cmd/crewship/cmd_admin_extras.go) offers list, create, update, and delete (there is no get, enable, or disable subcommand — enable/disable is the --enabled flag on recurring update). The CLI create / update take flags (--crew, --title, --cron, --project, --assignee, …) that resolve to entity IDs; the manifest instead resolves slugs and can express the full template in one file, so reach for crewship apply for repeatable, slug-based setups.

REST endpoint mapping

The endpoint is:
The kind sends a single template_json string field carrying the resolved template; the server unmarshals it into the per-column fields (title, description, labels_json, etc.) defined by the existing recurring-issues table. Keeping the manifest payload shaped as a single blob means future template fields don’t require DB migrations or handler changes.

Validation rules

  • metadata.slug is required.
  • spec.cron is required and must parse via cron.NewParser(cron.Minute|cron.Hour|cron.Dom|cron.Month|cron.Dow).
  • spec.timezone is required and must parse via time.LoadLocation (i.e. a valid IANA zone).
  • spec.template.title is required (issues need a title).
  • spec.template.crew_slug is required and must reference a crew that exists in the workspace (declared in this manifest or already on the server).
  • spec.template.project_slug, if set, must reference an existing project.
  • spec.template.assignee_agent_slug, if set, must reference an existing agent.
  • Every entry in spec.template.labels[] must reference an existing label.
  • spec.template.priority, if set, must be one of none, low, medium, high, urgent.
Validation runs client-side before any REST call. A failing manifest is reported with every offending rule in one ValidationError so the author can fix all of them in one pass.

Apply behavior

Default mode (ApplyUpsert):
  1. Look up the existing row by metadata.slug via GET /api/v1/recurring-issues.
  2. If absent → Action=CreatePOST /api/v1/recurring-issues.
  3. If present and any field drifts → Action=UpdatePATCH /api/v1/recurring-issues/{id}.
  4. If present and identical (including the resolved template_json) → Action=Unchanged, no network call.
ApplyStrict: Same as Upsert but a pre-existing slug aborts the apply with a clear error — useful in CI when the manifest must create fresh resources. ApplyReplace: Emits a Delete plan item followed by a Create. Destructive; the apply path prompts for confirmation unless --yes was passed. Drift detection looks inside template_json — adding or removing a single label surfaces as Action=Update even if every top-level column (cron, timezone, enabled) matches. The diff compares resolved IDs, not slugs, so reordering a labels list in the YAML without semantic change produces an Unchanged plan (label IDs are sorted before comparison).

Round-trip via export

crewship export workspace calls ExportRecurringIssues(ctx, client) once per workspace, which:
  1. GETs /api/v1/recurring-issues for the row list.
  2. GETs /api/v1/crews, /api/v1/projects, /api/v1/agents, /api/v1/labels once to build id → slug lookup tables.
  3. For each row, unmarshals template_json, reverse-resolves each ID back to its slug, and emits a RecurringIssueDocument.
The exported YAML re-applies cleanly: every slug in the file resolves to the same row on the next crewship apply, producing zero diff. Labels are sorted alphabetically in the exported file so successive exports of the same state produce byte-identical output. crewship export crew <slug> filters: only recurring issues whose template.crew_slug == <slug> are included, so a per-crew bundle ships exactly the schedules that crew owns.

See also

  • kind: Crew — provides the crew_slug reference. Crew must exist before the recurring issue applies.
  • kind: Label — provides the labels[] references.
  • kind: Project — provides the project_slug reference.
  • kind: Routine — for cron-triggered automation pipelines, where the trigger runs code instead of opening an issue. Recurring issues are the lightweight cousin: they only create a tracked work item; routines run a full agent pipeline.
  • kind: TriageRule — to auto-label or auto-route the issues a recurring schedule creates.