Skip to main content

Context

In the eigenoid org, repositories were created and deleted ad-hoc through the GitHub UI. This caused several problems:

  • Inconsistent configuration: new repos appeared without labels, rulesets, team permissions, or the org's standard configuration. safe-settings would eventually configure them, but there was a window where the repo lived ungoverned.
  • No audit trail: there was no record of who created or deleted a repo, or why. The history was buried in GitHub audit logs, which are hard to query.
  • No review process: any org member could create a repo without approval. Deletion required admin access, but had no peer review or documented justification.
  • Risk of accidental deletion: GitHub deletes repos permanently with no recovery window. An accidental (or malicious) click could destroy a repo and its entire history.
  • Repos outside governance: manually created repos had no entry in the safe-settings admin repo until someone added one manually, leaving a configuration gap.

ADR-0006 established safe-settings as the source of truth for GitHub configuration, but did not address the lifecycle (creation, archival, deletion) of repositories.

Decision

We implement full repository lifecycle management via issue templates and GitHub Actions workflows in eigenoid/platform-settings. Repo creation by org members is disabled at the org level.

Repository creation

  1. A member opens an issue in platform-settings using the "Request new repository" template.
  2. The new-repo.yml workflow triggers and generates a PR with the repo's YAML configuration file for safe-settings, using force_create: true.
  3. The PR is authored by eigenoid-settings-bot[bot] (both the commit and the PR), includes a table with the repo details, a review checklist, and Closes #N to auto-close the issue.
  4. A reviewer approves and merges the PR.
  5. safe-settings detects the merge and creates the repo with full configuration (labels, rulesets, teams, settings) in a single step.

Result: the repo is born fully governed, with a complete audit trail (issue → PR → merge → creation).

Archival (soft-delete)

  1. A member opens an issue using the "Request repo deletion" template. The notify-lifecycle-approvers.yml workflow automatically notifies @eigenoid/platform-lifecycle-approvers.
  2. A member of @eigenoid/platform-lifecycle-approvers applies the approved-deletion label. The archive-repo.yml workflow verifies the sender's team membership before proceeding.
  3. The workflow archives the repo on GitHub and records the entry in archived-repos.json with a timestamp, the requester, and a reference to the issue.

Result: the repo becomes read-only but recoverable. The entry in archived-repos.json starts the 30-day countdown.

Permanent deletion

  1. The delete-archived-repos.yml workflow runs daily via CRON.
  2. It reads archived-repos.json and permanently deletes repos archived for more than 30 days.
  3. It removes the registry entry and notifies the original issue author.

Result: 30-day recovery window. After that, automatic and clean deletion.

Lifecycle ruleset

An org-level ruleset restricts repository lifecycle operations:

  • Restrictions: repo creation, deletion, transfer, and visibility changes.
  • Bypass: only OrganizationAdmin and the eigenoid-settings-bot GitHub App can perform these operations directly.
  • Admin repo: platform-settings has its own repo-level rulesets (require PR + 1 approval) since it is excluded from org-level rulesets to prevent lockout.

Concurrency

The archival and deletion workflows (archive-repo.yml and delete-archived-repos.yml) use the repo-lifecycle concurrency group to prevent race conditions between archival and purge.

Consequences

  • Complete audit trail: every creation and deletion is an issue with discussion, approvals, and linked PRs. The history is browsable by any org member.
  • YAML as source of truth: repo configuration lives in platform-settings alongside the rest of the governance config. A repo exists if and only if it has its YAML file.
  • 30-day recovery window: archived repos can be recovered before permanent deletion. Eliminates the risk of irreversible accidental loss.
  • Friction for emergency operations: creating or deleting a repo requires an issue and a PR. Mitigated by the OrganizationAdmin and App bypass in the lifecycle ruleset, which allows direct operations in urgent cases.
  • Bot authorship: PRs and commits are attributed to eigenoid-settings-bot[bot], not the generic github-actions[bot]. Consistent with ADR-0004 and the App's identity.
  • Single governance point: the entire lifecycle flows through a single repo (platform-settings) and a set of workflows. Simplifies auditing and reduces the error surface.
  • Team-based authorization: the platform-lifecycle-approvers team (managed in settings.yml) controls who can approve deletions. The workflow verifies membership before archiving, and CODEOWNERS requires team review for changes to workflows and configuration.

Alternatives considered

  • Manual click-ops (status quo): no audit trail, inconsistent configuration, no recovery window. Does not scale and is not reproducible. Unacceptable for an org with multiple repos and contributors.
  • Terraform github_repository: good IaC, but does not offer an issue-based workflow, has no recovery window, and is heavier for managing lifecycle alone. Terraform is already used for GCP (ADR-0008); adding GitHub here would mix concerns.
  • GitHub Actions with gh api (imperative): scripts calling the API directly. No declarative configuration, no safe-settings integration, no persistent state. Fine for one-shot scripts, poor for governance.
  • Allow creation by org members + safe-settings auto-config: repos would briefly exist ungoverned before safe-settings configured them. The drift window (even if short) is unacceptable: a repo could receive pushes without rulesets, or be public by mistake.

References