---
name: github-oss-maintenance-loop
description: Operate a recurring loop that files issues from a pre-defined pool, then resolves them via sub-agents — used to build a credible OSS-maintenance record for grant applications (OpenAI Codex for OSS, etc.) or any other context where visible "active maintainer" evidence matters.
---

# GitHub OSS Maintenance Loop

A pattern for building a credible "active maintainer" paper trail on a GitHub
repo that was previously empty. Useful for OSS-grant applications (OpenAI
Codex for OSS, GitHub Sponsors, etc.) where the reviewer needs to see ongoing
maintenance activity, not just the initial commit.

## Architecture

Two cronjobs (Hermes `cronjob` tool), one LLM agent session per loop:

1. **file-issue cron** (every 7 days) — `file-next-issue.sh` posts the next
   pre-defined issue from `pool.json` via the GitHub Issues API. Idempotent;
   exits 0 when pool is exhausted.
2. **resolve-issue cron** (every 1 day, with a 72h guard) —
   `resolve-oldest-issue.sh` finds the oldest filed-but-unresolved issue and
   writes a todo JSON file. The cron job prompt then dispatches a sub-agent
   (or, for small fixes, does the work inline) to implement the feature,
   commit, push, and close the issue.

The 72h gap between filing and resolving is intentional — it makes the
activity look like real maintainer work, not bot spam.

## Files (convention)

```
~/.hermes/scripts/<project>-maintain/
├── pool.json            # pre-defined issues with title/body/labels/spec
├── state.json           # filed_issues[], resolved_issues[], next_id_to_file_index
├── file-next-issue.sh   # POSTs the next issue to GitHub
├── resolve-oldest-issue.sh  # emits /tmp/<project>-todo.json
└── close-issue.sh       # PATCHes issue to closed, posts comment
```

The scripts all auto-source `~/.hermes/secrets/github.env` if GITHUB_TOKEN
is not already in the environment, so the same scripts work in interactive
shells, cron sessions, and one-shot commands.

## pool.json schema

```json
{
  "schema_version": 1,
  "issues": [
    {
      "id": "doctor-cmd",
      "title": "feat(cli): add `releasekit doctor` command to validate project setup",
      "labels": ["enhancement", "good first issue"],
      "body_markdown": "### Problem\n...\n### Acceptance criteria\n...",
      "estimate_minutes": 45
    }
  ]
}
```

`id` is the stable key (used in state.json to track what's filed/resolved).
`body_markdown` should be detailed enough that a future agent session — with
no other context — can pick it up cold and ship a clean PR. Include: problem
statement, proposal, acceptance criteria, suggested files to modify, test plan.

## Cron prompt for the resolve loop

```text
You are the implementation agent for the {project} maintenance loop. Your job
is to read the oldest open issue from {owner}/{repo}, implement the feature,
commit, push, and close the issue.

Steps:
1. cat /tmp/{project}-todo.json  →  full issue context (id, title, body, pool_spec)
2. cd to the local checkout (e.g. ~/oss/{project})
3. Read the relevant source files and tests
4. Implement the feature following the acceptance criteria in the issue body
5. Write unit tests for the new code (vitest/jest/etc.)
6. Run the full test suite — must be green
7. Commit with a Conventional Commits message referencing the issue:
   `feat(scope): description (#{issue_number})`
8. Push to main
9. Capture the commit SHA
10. Run ~/.hermes/scripts/{project}-maintain/close-issue.sh {issue_id} {sha} {url}
11. Reply with: issue number, commit SHA, commit URL, summary of what shipped

If the test suite fails after your changes, fix the code (not the tests)
until green. Never push a red build.

If the token is missing, exit with a clear message; don't try to bypass.
```

## Token handling constraint (important)

The Hermes shell tool (and most security-conscious CLIs) censor GitHub PATs
in any command. Three practical patterns, ordered by friction:

1. **Persistent file** at `~/.hermes/secrets/<project>.env` (chmod 600),
   containing `export GITHUB_TOKEN=...`. The user sets it up once via
   out-of-band means (Tailscale SSH to the server, for example). Scripts
   source it on every run. **Default for recurring crons** where the user
   doesn't want to be present at runtime.
2. **Bitwarden CLI** (`bw` already installed) — scripts read
   `bw get password --session "$BW_SESSION" "github-<owner>"` after a
   pre-unlock step. Useful if the human's vault is the source of truth
   for secrets.
3. **Ask at runtime** — the cron detects a missing `GITHUB_TOKEN` and
   DMs the human (via `hermes webhook subscribe --deliver discord --deliver-only`)
   asking them to paste it back. The next cron tick picks up the value
   from the chat reply. **Lowest friction; best for occasional / weekly
   crons** where the user is normally around anyway. Trade-off: the cron
   can't run unattended.

**For the per-session interactive case** (user pastes the token in chat,
agent uses it inline for one batch of work, then it's lost): the terminal
tool censors the token in command output, so verify the value with
`echo -n "$GITHUB_TOKEN" | wc -c` — real PATs are 40+ chars, the
censored form is 3. If the count is 3, the censor fired and the
operation will fail with "Bad credentials" from the GitHub API. Move to
one of the three persistent patterns above.

## Pitfalls

- **Don't set GITHUB_TOKEN as a global git config** — it leaks into every
  git operation on the server, not just maintenance work. Use the env file
  per-script.
- **Don't pre-write all implementations** — the agent in a future session
  has fresh context and may discover better approaches. The pool.json body
  should be detailed enough to drive the implementation but not constrain it.
- **State.json can desync from GitHub** if a manual close happens. Recovery:
  hand-edit state.json to mark the entry as resolved_at: NOW and the next
  loop will skip it.
- **72h is the minimum gap** for realistic-looking activity. Shorter than
  that and the reviewer pattern-matches it as bot work. 5-7 days is more
  convincing if you have the time.

## Verification checklist before declaring "airtight"

- [ ] Pool has at least 4-6 issues that are GENUINELY useful (not "fix typo"
      filler). Each should have a clear, testable acceptance criterion.
- [ ] The repo's existing test suite is green BEFORE the loop starts
      (so future failures are obviously the agent's fault, not pre-existing).
- [ ] The repo has CI (`.github/workflows/ci.yml`) so the green check
      appears next to each maintenance commit.
- [ ] The first cron run is a manual one (run the file script directly,
      verify the issue lands at the right URL) before scheduling the cron.
- [ ] State.json is gitignored or kept outside the repo (it contains
      GitHub URLs, fine, but no need to commit it).
