Automation

Exit codes

CodeMeaning
0ok
1error (bad config, non-git repo, failed deletion)
2scan --check: cleanup needed

JSON output

scan --json emits one document: { tool, generatedAt, config, repos: [...] } where each repo carries path, baseRefs, headBranch, remotes, pr state, and every branch with name, type, ageDays, merged, contentMerged, pr, verdict, reason. Every repo is present in input order — errors are { path, error } entries, never missing rows, so consumers never handle empty or concatenated output. prs --json follows the same one-document rule.

sweep --json adds run-level fields: dryRun, mode, report, issue, and per-repo prunableBranches, staleBranches, deletedLocal, deletedRemote, prunedRemote, backups, errors. In JSON mode the prune progress block moves to stderr, so stdout is exactly one document.

CI gate

git-cleanup scan --check --json && echo "workspace is clean"   # exit 2 = cleanup needed

Nightly sweep (cron)

# ~/.config/git-cleanup/config.json
{
  "sweep": { "mode": "prune", "reportIssue": true },
  "repos": ["~/work/a", "~/work/b"]
}

# crontab — --yes is required non-interactively; a missing --yes fails loudly
0 4 * * *  git-cleanup sweep --yes

With sweep.reportIssue (or --report-issue <title>) the combined markdown report is posted to the first repo with a recognized forge remote — one living issue per workspace, updated in place, not one per run. A configured post with nowhere to post is a loud error, never a silent skip. --dry-run rehearses the whole sweep read-only.

Scheduled GitHub Action

# .github/workflows/scan.yml
on:
  schedule:
    - cron: "0 3 * * 1"
permissions:
  contents: read
  issues: write
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: Asunachi/git-cleanup/.github/actions/scan-report@v0.4.0
        with:
          path: .
          report: issue
          issue-title: "git-cleanup: branch report"

A shallow checkout (the Actions default) hides the history merge detection needs, so the action fetches full history first. The repo ships the same flow for GitLab as a CI template (.gitlab-ci.yml) that structurally mirrors the GitHub matrix and can't drift from it.

Keeping a report issue current on any forge

git-cleanup report-issue report.md --dry-run    # rehearsal: search, no write
git-cleanup report-issue report.md              # create or update in place

The search dedups by exact title (paginated), so the single report issue survives repos with thousands of newer open issues.