Files
evanpelle 885e31460c issue-lifecycle: greet new unmilestoned issues with a comment
When an issue is opened without a milestone, post a friendly comment
alongside the not-approved label so the reporter knows it's queued for
triage. Scoped to the opened event only to avoid spamming old issues on
cron reconciliation.
2026-05-30 09:19:47 -07:00
..

Issue Lifecycle

Deterministic GitHub Actions that enforce OpenFront's issue lifecycle. No LLM calls — only the default GITHUB_TOKEN.

Rules

  1. Stale closer — daily cron. Unmilestoned issues get a warning at 7 days of inactivity, are closed at 14. Exempt: milestoned or keep-open-labelled issues. Assignees are not an exemption — if an unmilestoned issue somehow has assignees (event drift), Rule 2 unassigns them and Rule 1 still applies.
  2. Assignment invariant — event + cron. You cannot assign a person to an unmilestoned issue. Violators are unassigned automatically.
  3. Approval label sync — event + cron. not-approved and approved track milestone state (none = not-approved, any milestone = approved).

See the parent spec for the full contribution model rationale.

Triggers

Local testing

cd scripts/issue-lifecycle
npm install
export GITHUB_TOKEN=ghp_... # PAT with repo scope

# Full cron sweep against the real repo, dry-run (default for CLI):
npx tsx index.ts --mode cron

# Single-issue event-mode dry-run (simulates the assigned event):
EVENT_NAME=assigned npx tsx index.ts --mode event --issue 1234

# Force live mode locally (BE CAREFUL — this will mutate the repo):
npx tsx index.ts --mode cron --no-dry-run

CLI invocations are dry-run by default. Pass --no-dry-run to apply.

Toggling dry-run in production

  1. Go to repo Settings → Secrets and variables → Actions → Variables.
  2. Edit ISSUE_LIFECYCLE_DRY_RUN.
  3. Set to false to make the Actions act for real; any other value (or unset) keeps them in dry-run mode.

The default is true — both workflows log decisions but do not act until the maintainer flips the variable.

File layout

Notes

  • Labels are auto-created on every run (idempotent). The maintainer does not need to manually pre-create any of them.
  • Rules act only on issues — PRs are filtered out (pull_request field check).
  • API rate limit during the cron sweep causes the run to exit non-zero; the next day's cron retries.
  • No Claude / LLM calls. Layer B will layer triage on top of this foundation.