Skip to content
Back to the blog

· Updated · 8 min read

Running a software portfolio from one GitHub repo, with Claude Code and Codex

A case study in turning around seventy scattered repositories into one register, one idea inbox and two AI assistants that follow the same rules. What we built, what went wrong, and the starter files to copy.

case-study · github · claude-code · codex · workflow

The problem

1Zero9 had around seventy repositories across two GitHub owners. Some were live products, some were experiments, some were abandoned, and for many of them nobody could say which. Ideas lived in chat windows and in the heads of whoever had the last conversation. Handover notes drifted away from what the code actually did.

The question we wanted to answer on any day was simple: what's next? The hard part was answering it from evidence rather than from memory or from an out-of-date document.

What we built

Four small things, all on GitHub, with no new app, service or database:

  1. One central repository that holds the register and the idea inbox. It contains no product code.
  2. A register file, PROJECTS.md. One row per repository: what it is, its status, when it was last pushed, and what the evidence is. Anything not confirmed by the owner says Needs your input rather than a guess.
  3. Ideas as GitHub Issues in the central repository. Each one is labelled idea and with its project name, and every one is treated as unapproved. An idea for a project that does not exist yet gets the label New project instead, so a half-formed thought like "something that combines the night sky and flight tracking" has a home before it has a repository.
  4. The same written rules in both assistants' global instruction files. Claude Code reads ~/.claude/CLAUDE.md. Codex reads ~/.codex/AGENTS.md. Once the rules are in those files, saying Capture idea: ... from any project folder is meant to file a correctly labelled Issue in the central repository. (It did in our tests. See what went wrong below.)

On top of that sits one review rule. Asking "What's next?" inside a project reviews only that project. Asking it in the portfolio repository, or asking for a portfolio-wide review, reviews everything. Reviews are read-only unless you say otherwise.

What you need

  • A GitHub account and the GitHub CLI (gh), logged in with the repo scope. Check with gh auth status.
  • A repository for the portfolio. Private is fine.
  • Claude Code, Codex, or both. Anything that reads a global instruction file will work.
  • About an hour for the first pass. Most of it goes on the register.

Set it up

1. Create the central repository

gh repo create YOUR-OWNER/portfolio --private --add-readme

2. Seed the register from what GitHub already knows

gh repo list YOUR-OWNER --limit 200 \
  --json name,description,pushedAt,isArchived,url

Turn that into a table in PROJECTS.md. Record GitHub's archive flag as it is. For everything else, write Needs your input until the owner confirms. The one rule that keeps the register honest: last push is not proof of progress. Do not invent status, progress or completion. A sample file is below.

3. Create the labels

gh label create idea -R YOUR-OWNER/portfolio \
  --color FBCA04 --description "Captured idea, not approved development work"
gh label create "My App" -R YOUR-OWNER/portfolio --color 1D76DB
gh label create "New project" -R YOUR-OWNER/portfolio --color 0E8A16 \
  --description "Idea for a project that does not exist yet"
gh label create "Unassigned" -R YOUR-OWNER/portfolio --color BFD4F2

One label per project, named exactly as it appears in the issue title. New project is for ideas about things that do not exist yet. Unassigned is for ideas about existing work where the project could not be identified.

4. Add the instruction blocks

Paste the capture and review blocks into your global files. Replace the placeholders with your owner, repository and GitHub account:

  • ~/.claude/CLAUDE.md for Claude Code
  • ~/.codex/AGENTS.md for Codex

Keep the text identical in both so the assistants behave the same way.

Claude Code can also run a check automatically when a session starts, through a SessionStart hook. Merge the sample from the starter files into the hooks object in ~/.claude/settings.json. Merge it, do not replace the file, or you will lose your other settings.

5. Test it, then delete the test

From a project folder, say Capture idea: test. Check that the Issue appears in the central repository, with both labels, and that the notes arrived word for word. Then delete the test Issue.

The starter files

Copy these and replace the placeholders:

The heart of the capture rule is one command. The notes go through a quoted here-document so quotes, backticks and dollar signs arrive unchanged:

gh issue create -R YOUR-OWNER/portfolio \
  --title "My App — Short description" \
  --label idea --label "My App" \
  --body-file - <<'IDEA'
Your original notes, exactly as you wrote them.
IDEA

Keeping it current: the session check

Capture alone leaves the backlog stale. Ideas get done and nobody closes the Issue, and follow-ups come up mid-session and are forgotten. So the same instruction files carry a second rule, the session check. Before it signs off after real work, or when you say you are wrapping up, the assistant:

  • Closes what is done. It looks at the project's open Issues and closes any that are verifiably finished, with a comment naming the evidence: a merged pull request, a commit, a passing check or a live URL. If it is unsure, it comments and leaves the Issue open. It never deletes.
  • Captures what came up. A new follow-up, blocker or forgotten deliverable is filed as an idea Issue. Because you did not write it, the body says so ("Auto-captured during a session") and gives the evidence.
  • Avoids noise. It reads the existing Issues first, skips anything already logged, and stops at five new Issues a session.
  • Reports in one line. What was closed, what was filed, or "nothing to change".

A crashed session cannot run its own end-check. So the same check runs at the start of the next session: the assistant looks at the repository for uncommitted, unpushed or unmerged work, mentions anything that looks interrupted without resuming it unasked, and then does the close and capture steps. In Claude Code a SessionStart hook injects that repository state, so the check does not depend on anyone remembering. In Codex we rely on the written instruction alone.

The authority is narrow on purpose. It covers creating, commenting on and closing Issues in the central repository. It never covers changing a project repository, deploying or deleting.

The rules that mattered

  • Ideas are not approved work. Every captured idea is labelled idea and the assistant must not start it.
  • Notes are kept verbatim. The assistant writes a short title. It does not add requirements or implementation details to your words.
  • Evidence beats documentation. Reviews check the live site, the commits, the CI runs and the open Issues, instead of trusting a status note.
  • Reviews are read-only. Changes need an explicit go-ahead, and anything outward-facing (a push, a merge, a deployment, a delete) is confirmed first.
  • Closing needs evidence. An Issue is closed only on proof, and the proof goes in the closing comment. A guess leaves it open.
  • Words the assistant wrote are labelled. Anything auto-captured says so, so you can tell your own ideas from the assistant's observations.
  • The destination is not negotiable. Captured ideas always go to the central repository, whichever project you happen to be standing in.

What went wrong

This is the useful part. None of it was dramatic, but all of it would have bitten a copy of this setup.

  • Ideas landed in the wrong repository. Seven ideas ended up in a project's own Issues instead of the central repository. We could not tell from GitHub which session created them, only that it happened while working inside that project. We tightened the rule to say that the current repository is used only to identify the project, and added a check: the Issue URL returned by gh must begin with the central repository's address, or the assistant must report a failed capture. Then we moved the seven Issues, checked each body was identical, and deleted the originals.
  • The register went stale within a day. On the day we wrote it, a project's repository appeared and its documented "next step" was completed. Reviews now cross-check the register against the repositories, and updating it is a normal small pull request.
  • Two assistants, two routes. A Codex test followed the written rules. Another test went through a connected GitHub integration with its own issue form, bypassed the rules, and produced an unlabelled Issue. If you use more than one entry route, test each one.
  • Some evidence is out of reach. The assistants could read GitHub but not every dashboard. A hosting build failed on feature branches, and the cause lived in a log only the owner could open. A good review says so instead of guessing.
  • Our own duplicate check searched the wrong place. The first version of the session check used gh issue list --search, and with an OR in the query it returned other people's repositories. The rule now lists the central repository's Issues and reads the titles instead.
  • A vague trigger is not an idea. "Add to backlog" with no notes produced a question, not an Issue. The rule says to keep the notes verbatim, so when there are none, the assistant should ask.

What it does not do

  • It does not label by type (bug, feature). That needs judgement about the idea, and the rule is not to invent. You triage those yourself.
  • It does not promote an approved New project idea into a project. That is still two manual steps: relabel the Issue, then add a row to the register.
  • The session check is instructions, plus a hook in Claude Code only. It will not fire if a session is killed and the next one is never started, and we have not seen the hook run on every kind of session.
  • It does not catch ideas typed into GitHub's web interface or mobile app. For that, a small GitHub Action that labels new Issues from their title would work. We have not built one.
  • It does not read a GitHub Project board unless your token has the read:project scope.
  • It is a manual register. It is only as current as the last time someone updated it.

Try it

Start with ten minutes: create the repository, seed the register with gh repo list, add the labels, and paste the instruction blocks. Then say Capture idea: from a project folder and see where the Issue lands. If it lands in the wrong place, the rule is the thing to fix, not the assistant.