Problem
The gists list at https://gist.github.com/JacobPEvans-personal is cluttered with 19 orphan state.json gists dated 2026-04-18 through 2026-05-06 — each 04:xx UTC, one per day, content {"last_polished": "...", "last_date": "..."}. These are from an old Daily Polish version that created a new gist every run instead of updating one. The current Daily Polish writes to daily-polish-state (single gist, properly named), so the 19 are dead.
Additionally, every cloud routine maintains its own *-state gist. Today there are ~4 named ones; once feat/six-new-routines lands there will be ~10 (apothecary-state, archivist-state, conductor-state, custodian-state, daily-polish-state, inspector-state, observer-state, quartermaster-state, sentinel-state, solver-state — plus the cross-routine routine-pr-budget). The gists UI becomes a wall of *-state rows that mean nothing visually.
Two-step fix
Step 1: Delete the 19 orphans (immediate, 1 command)
Requires a token tier with gist scope (e.g. gh-private or higher). From a properly-tiered shell:
gh gist list --limit 100 \
| awk -F'\t' '$2 == "state.json" {print $1}' \
| xargs -I{} gh gist delete {} --yes
Verify orphan count first:
gh gist list --limit 100 | awk -F'\t' '$2 == "state.json"' | wc -l
# expect: 19
The 5 named *-state gists (apothecary, daily-polish, issue-solver, weekly-scorecard, plus any others) are NOT affected — they have proper titles, not the bare state.json.
Step 2: Migrate state from gists to dryvist/cc-routines-state repo
Create a new private repo dryvist/cc-routines-state that holds one JSON file per routine. Routines read+write via createCommitOnBranch (the same signed-commit path the Solver already uses for target repos). Gists list stays clean forever.
One-time setup (user-only, requires gh-claude-dryvist and secrets-sync write)
# 1. Create the repo
gh repo create dryvist/cc-routines-state \
--private \
--description "State files for dryvist/claude-code-routines cloud routines. Each routine owns one JSON file. Routines read+write via createCommitOnBranch with JacobPEvans-claude App installation token."
# 2. Seed initial README (manual or via Contents API)
# 3. Add the new repo to the *github_app_repos anchor in
# dryvist/secrets-sync/secrets-config.yml so the JacobPEvans-claude
# App gets installation access.
# 4. Re-run secrets-sync distribution workflow.
# 5. Copy each current *-state gist content into:
# dryvist/cc-routines-state:state/<routine>.json
Routine-prompt updates (one PR per routine, can be batched)
For each routine, replace the gist-read/write logic with Contents API equivalents against dryvist/cc-routines-state. Rough pattern:
# Read state
STATE=$(gh api repos/dryvist/cc-routines-state/contents/state/<routine>.json \
--jq '.content' | base64 -d)
# Mutate state in /tmp/state.json...
# Write state via createCommitOnBranch (signed by JacobPEvans-claude[bot])
B64=$(base64 < /tmp/state.json | tr -d '\n')
jq -n --arg b64 "$B64" --arg sha "$BASE_SHA" '{
query: "mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid } } }",
variables: {
input: {
branch: { repositoryNameWithOwner: "dryvist/cc-routines-state", branchName: "main" },
expectedHeadOid: $sha,
message: { headline: "chore(state): update <routine> state [routine:<name>]" },
fileChanges: { additions: [{path: "state/<routine>.json", contents: $b64}] }
}
}
}' | gh api graphql --input -
Routines in scope (10 total):
After migration: every routine deletes its legacy *-state gist on its first run after deployment, then writes to the new repo from there on.
Why a private repo, not a branch or another gist?
- No gist clutter. The whole point — gists list becomes just the user's actual personal gists.
- Browsable. GitHub repo UI is built for navigating files; gist UI is built for ephemera.
- Versioned. Every state mutation becomes a signed commit with a meaningful message. Auditable forever.
- Same mechanics routines already use.
createCommitOnBranch is already in the Solver and (after feat/six-new-routines lands) everywhere else. No new auth path.
- Centralized backup. One repo to clone if you ever want to inspect or archive state.
A dedicated state-only branch in claude-code-routines was the runner-up option but bleeds branches into the source-of-truth repo. Separate repo is cleaner.
Out of scope
- The 5 legitimate personal gists (proxmox scripts, AI prompts, learning resources, Splunk references, everything-as-code) — those are user content, untouched.
- The
routine-pr-budget cross-routine gist — same migration applies, gets its own file state/_pr-budget.json in the new repo.
Acceptance criteria
Suggested ordering
- Delete orphans (1 command, immediate).
- Create repo + README + secrets-sync wiring (one-time, ~15 min).
- Migrate routine prompts in batches: first the 6 on main (covers Solver, Observer, Daily Polish, Sentinel, Custodian), then the 5 on
feat/six-new-routines as part of that branch's merge.
- Verify each routine's first post-deploy run deletes its legacy gist and writes to the new repo successfully.
Problem
The gists list at https://gist.github.com/JacobPEvans-personal is cluttered with 19 orphan
state.jsongists dated 2026-04-18 through 2026-05-06 — each 04:xx UTC, one per day, content{"last_polished": "...", "last_date": "..."}. These are from an old Daily Polish version that created a new gist every run instead of updating one. The current Daily Polish writes todaily-polish-state(single gist, properly named), so the 19 are dead.Additionally, every cloud routine maintains its own
*-stategist. Today there are ~4 named ones; oncefeat/six-new-routineslands there will be ~10 (apothecary-state, archivist-state, conductor-state, custodian-state, daily-polish-state, inspector-state, observer-state, quartermaster-state, sentinel-state, solver-state — plus the cross-routineroutine-pr-budget). The gists UI becomes a wall of*-staterows that mean nothing visually.Two-step fix
Step 1: Delete the 19 orphans (immediate, 1 command)
Requires a token tier with gist scope (e.g.
gh-privateor higher). From a properly-tiered shell:Verify orphan count first:
The 5 named
*-stategists (apothecary, daily-polish, issue-solver, weekly-scorecard, plus any others) are NOT affected — they have proper titles, not the barestate.json.Step 2: Migrate state from gists to
dryvist/cc-routines-staterepoCreate a new private repo
dryvist/cc-routines-statethat holds one JSON file per routine. Routines read+write viacreateCommitOnBranch(the same signed-commit path the Solver already uses for target repos). Gists list stays clean forever.One-time setup (user-only, requires
gh-claude-dryvistandsecrets-syncwrite)Routine-prompt updates (one PR per routine, can be batched)
For each routine, replace the gist-read/write logic with Contents API equivalents against
dryvist/cc-routines-state. Rough pattern:Routines in scope (10 total):
maintoday:daily-polish,sentinel,custodian,issue-solver(becomingsolver),morning-briefing(becomingobserver),weekly-scorecard(retiring)feat/six-new-routines:apothecary,archivist,conductor,inspector,quartermaster(plusdistributorwhich retires per chore(routines): retire The Distributor — replaced by org Required Workflows #28)After migration: every routine deletes its legacy
*-stategist on its first run after deployment, then writes to the new repo from there on.Why a private repo, not a branch or another gist?
createCommitOnBranchis already in the Solver and (afterfeat/six-new-routineslands) everywhere else. No new auth path.A dedicated state-only branch in
claude-code-routineswas the runner-up option but bleeds branches into the source-of-truth repo. Separate repo is cleaner.Out of scope
routine-pr-budgetcross-routine gist — same migration applies, gets its own filestate/_pr-budget.jsonin the new repo.Acceptance criteria
state.jsongists deleted; total gist count drops from 28 to 9 (5 personal + 4 routine state).dryvist/cc-routines-stateprivate repo exists with README explaining purpose and JacobPEvans-claude App installed.*-stategists exist;gh gist listshows only the 5 personal gists.Suggested ordering
feat/six-new-routinesas part of that branch's merge.