Skip to content

Auto-create GitHub repo on spec approval (multi-client onboarding) #12

@tkowalczyk

Description

@tkowalczyk

Parent PRD

#1

Context

Issue #8 / #11 shipped GitHub sync using a single global `GITHUB_TOKEN` and per-project `githubRepo` selected manually from a dropdown. This works when the admin already has access to the target repo, but creates friction (and a security surface) as the project scales to multiple clients:

  • The admin must manually create a repo for each new project before pushing tasks
  • A single PAT must be authorized on every client repo, becoming a master-key
  • Clients with their own GitHub org need to add the admin's personal account as collaborator

Per discussion in PR/conversation following #11: in the majority of cases the admin will own the repo. Automating that default eliminates manual setup for ~90% of projects without precluding the edge case (client's own org).

What to build

When a spec transitions to `review` (admin approves spec), the data-service automatically creates a private GitHub repo under the configured org and persists its `full_name` to the project. The dropdown shipped in #11 continues to work as a manual override for edge cases (e.g. client owns the repo).

Backend

Org configuration: New worker env var `GITHUB_ORG` (default `auditmos-projects`). Set as a non-secret `var` in `wrangler.jsonc` per environment.

New service: `createRepo(token, org, name, options)` in `apps/data-service/src/hono/services/github-client.ts`:

  • Calls `POST https://api.github.com/orgs/{org}/repos\`
  • Body: `{ name, private: true, description, auto_init: false }`
  • Returns `{ fullName, defaultBranch, private }` (matches existing `GithubRepo` shape)
  • Handles `422 name already exists` by retrying with `{slug}-{nanoid(4)}` once

Hook into spec approval: In `spec-task-service.ts → approveSpec(slug)`, after the `interviewing/review → review` status transition succeeds:

  • If `project.githubRepo` is already set → skip (admin manually configured)
  • Else → call `createRepo(env.GITHUB_TOKEN, env.GITHUB_ORG, project.slug)` and `updateProjectGithubRepo(project.id, fullName)`
  • Failure to create repo MUST NOT roll back the approval — log the error, return success; admin can manually pick a repo later

PAT scopes (fine-grained, scoped to `auditmos-projects` org):

  • Repository permissions: Administration: Read and write (create repo), Issues: Read and write (existing), Contents: Read and write
  • One PAT covers all current and future repos under the org

Frontend

No changes required. The dropdown from #11 already:

  • Shows current `githubRepo` selection (now auto-populated by the backend)
  • Allows the admin to override (e.g. switch to client's own repo)
  • The repo list (`GET /github/repos`) continues to fetch from the same token's accessible repos

Acceptance criteria

  • New env var `GITHUB_ORG` defined in `worker-configuration.d.ts` and set per-env in `wrangler.jsonc` (`auditmos-projects` for staging/production)
  • `createRepo()` in github-client.ts: creates private repo via GitHub API, returns `GithubRepo` shape, handles 401 (token invalid) and 422 (name taken) with one retry using suffix
  • On `POST /projects/:slug/approve`, when project has no `githubRepo`, a private repo `auditmos-projects/{slug}` is created and persisted
  • If `createRepo` fails (network/422-after-retry/forbidden), spec approval still succeeds; error is logged; admin can use the dropdown override
  • If project already has `githubRepo` set (manual override), `approveSpec` does NOT create a new repo
  • Manual override flow from GitHub Sync — Frontend (admin dropdown, push button, client polling) #11 (`PUT /projects/:slug/github-repo`) continues to work end-to-end
  • TDD: unit tests for `createRepo` (happy path, 401, 422 retry → success, 422 retry → final fail), integration test for `approveSpec` covering both auto-create and skip-when-set branches

TDD slice plan (suggested)

  1. `createRepo` happy path — POSTs to `/orgs/{org}/repos` with private: true, returns `GithubRepo`
  2. `createRepo` 401 — returns `Result.error` with `GITHUB_UNAUTHORIZED`
  3. `createRepo` 422 + retry success — second call uses suffixed name, returns repo
  4. `createRepo` 422 + retry fail — returns `Result.error` with `GITHUB_REPO_NAME_TAKEN`
  5. `approveSpec` auto-creates — when `githubRepo: null`, calls createRepo and persists
  6. `approveSpec` skips — when `githubRepo` set, no createRepo call
  7. `approveSpec` resilient — when createRepo fails, approval still completes (200), log emitted

Out of scope (defer to future issues)

  • GitHub App (true multi-tenancy with per-installation tokens) — only needed when many clients want their repo under their own org, and admin doesn't want to be a collaborator. Track separately.
  • Per-project token storage — only relevant if we move away from single-org model
  • Repo seeding (push spec.md as README, push tasks as initial issues) — the current flow creates issues via GitHub Sync — Frontend (admin dropdown, push button, client polling) #11's "Push to GitHub" button; pre-seeding is a UX improvement, not a correctness fix
  • Repo deletion on project delete — leave as manual cleanup (preserves history; matches "treat repos as artifacts not state")
  • Webhook subscription for issue closed events — current sync is pull-based on client page mount (GitHub Sync — Frontend (admin dropdown, push button, client polling) #11); webhook upgrade is a separate optimization

User stories addressed

Reduces friction for the multi-client workflow described in user stories 13-15 (PRD #1) by removing the manual "create repo + select repo" step from project setup.

Notes

  • `GITHUB_TOKEN` must be re-issued as a fine-grained PAT scoped to `auditmos-projects` org with Administration: write before deploy. Existing PAT (Issues: write only) will fail `createRepo` with 403.
  • Slug format from `packages/data-ops/src/project/slug.ts` already includes a random suffix, so collisions on first create should be rare; the 422 retry handles the edge case.
  • Logged `createRepo` failures should include `{ slug, status, githubError }` for debugging without leaking the token.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions