Skip to content

Instantly share code, notes, and snippets.

@mezhaka
Created June 24, 2026 17:09
Show Gist options
  • Select an option

  • Save mezhaka/3059cba4c88f26dc9a123ecd0392229d to your computer and use it in GitHub Desktop.

Select an option

Save mezhaka/3059cba4c88f26dc9a123ecd0392229d to your computer and use it in GitHub Desktop.
Organizing local checkouts + git worktrees for Claude Code across many repos

Organizing local checkouts + git worktrees for Claude Code across many repos

This is how I organize local checkouts and git worktrees for working with Claude Code. It's mostly relevant if you work across multiple repos at once (if you live in a single repo all day, you probably don't need any of this).

The layout

I keep every repo in my org cloned under one parent directory (call it ALL_REPOS), one directory per repo. The default branch is checked out at <repo>/<default-branch>/ — the branch name is used verbatim, so a / in the branch name becomes a real path separator — and I add one git worktree per ticket as a sibling directory named after the branch.

Here's what that looks like in practice (only checkout roots shown):

ALL_REPOS
├── api-gateway
│   ├── chore
│   │   ├── sc-1001-investigate-request-idempotency
│   │   └── sc-1002-investigate-caching-strategy
│   ├── feature
│   │   ├── sc-1003-document-vpn-setup
│   │   ├── sc-1004-event-feed-variant
│   │   └── sc-1005-prototype-data-sync-job
│   └── main
│       └── v13          ← default-branch checkout (default branch is main/v13)
├── data-pipeline
│   └── main
│       └── v9           ← default-branch checkout
└── infrastructure
    └── main             ← default-branch checkout

I start my Claude Code session inside ALL_REPOS and always work from there — it lets Claude see multiple repos at the same time. For example, it has the context of the infrastructure repo and can fuse it with my work in api-gateway or data-pipeline.

With this set up I can just say in the session: "Implement story XXXX, open a draft PR." If the story is well written, I go straight to the GitHub review afterwards. Claude creates a correctly-named branch (per our ticket naming convention) and a local worktree. This lets me work on several stories in parallel without checkouts fighting each other and without losing oversight of where each story is. Merged work cleans itself up.

The nice bit is that it makes working in parallel convenient.

Keeping it in sync

A daily job keeps every default-branch checkout in sync with origin (git reset --hard), then a second script removes worktrees and local branches once their PR is merged. The cleanup script skips anything with local changes or unpushed commits and logs why it kept it.

The CLAUDE.md setup: two files

Besides the default global ~/.claude/CLAUDE.md, I keep a second CLAUDE.md inside ALL_REPOS itself — Claude Code picks it up automatically whenever a session runs anywhere under that directory. So the layout rules live next to the repos they describe, while the global file keeps org-wide conventions (branch naming, ticket tooling).

1) ALL_REPOS/CLAUDE.md — the layout + worktree rules

# ALL_REPOS — local working copies of the org

Every non-archived repository in the org is cloned here. A daily job syncs
each default-branch checkout with `origin`, then removes worktrees and local
branches whose PRs have been merged.

## Directory layout

Each repo's default branch is checked out at `<repo>/<default-branch>/`.
The branch name is used verbatim, with `/` characters becoming real path
separators:

    api-gateway/main/v13/     # default branch is `main/v13`
    infrastructure/main/      # default branch is `main`
    data-pipeline/main/v9/    # default branch is `main/v9`

Treat the default-branch checkout as **read-only**: the daily refresh runs
`git reset --hard origin/<default>` and will wipe out any local edits.

## Starting work on a story → add a worktree

When picking up a new story, **do not** edit the default-branch checkout.
Instead, add a git worktree under the same `<repo>/` directory, named after
the ticket-derived branch.

The branch name follows the global rule: `{type}/sc-{story_id}-{slug}`.
Fetch the ticket tracker's own suggestion non-interactively via its MCP:

    mcp__shortcut__stories-get-branch-name(story_id=<id>)

The local **branch name** and the **worktree path** are the same string —
the path mirrors the `<repo>/<branch-path>/` convention used by the
default checkout.

Create the worktree from inside the default-branch checkout, branching off
the freshly-refreshed default branch. Example for `api-gateway`, story
sc-1010 with branch `feature/sc-1010-config-files-per-user`:

    cd ALL_REPOS/api-gateway/main/v13
    git fetch origin
    git worktree add -b feature/sc-1010-config-files-per-user \
        ALL_REPOS/api-gateway/feature/sc-1010-config-files-per-user \
        origin/main/v13

Then `cd` into the worktree to do your work. Push with
`git push -u origin HEAD` from inside the worktree once there's something
to share.

## Reviewing someone else's branch

Check out a colleague's branch as a parallel worktree (so the main
checkout stays put):

    cd ALL_REPOS/data-pipeline/main/v9
    git fetch origin
    git worktree add ALL_REPOS/data-pipeline/feature/sc-12345-foo feature/sc-12345-foo

2) ~/.claude/CLAUDE.md — the global bits on branch naming and ticket MCP usage

**Branch naming for PRs**: PR branches use `{type}/sc-{story_id}-{slug}` —
e.g. `chore/sc-1011-raise-disk-quota-eu-west`. `{type}` is the ticket type
(`chore`, `feature`, or `bug`); `{story_id}` is the numeric ticket ID;
`{slug}` is a short kebab-case description. Only the `sc-{id}` token matters
for the tracker's GitHub integration to auto-link the PR — the slug is
free-form. Pick the branch name before opening the PR: renaming the branch
of an open PR has been observed to close the PR.

**Ticket tooling — default to the tracker MCP, fall back to the CLI**:

- **Default**: use the tracker MCP (`mcp__shortcut__*`) for any
  single-object operation — fetching a story or epic by ID,
  creating/updating a story, posting a comment, setting state, reading
  workflow/team/iteration metadata, getting a branch-name suggestion.
  Returns structured JSON, no shell parsing. Common ones:
  `stories-get-by-id`, `stories-search`, `stories-create`,
  `stories-update`, `stories-create-comment`, `epics-get-by-id`,
  `epics-update`, `iterations-*`, `stories-get-branch-name`.
- **Use the CLI only when MCP can't do the job efficiently**:
  - Bulk / server-side-filtered search with operators `stories-search`
    doesn't expose: `short search "epic:5000 state:done" -f "%id" -q`.
  - Raw API passthrough for endpoints no MCP tool covers, e.g. epic
    rename: `short api /epics/{epic_id} -X PUT -f name="New Epic Name"`.
  - Scripted loops over many stories where one MCP call per item would
    be wasteful — write the CLI pipeline once, run it in Bash.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment