Repository files navigation

codeherd

License: Apache 2.0

A CLI for managing parallel agentic coding sessions. It organizes projects and git worktrees, configures per-agent environments with deterministic port allocation, and orchestrates tmux sessions where AI coding agents run independently.

It is like a shepherd, but for coding agents.

Why

Running multiple AI coding agents in parallel requires isolated workspaces, separate environments, and session management. codeherd handles the infrastructure -- git worktrees for isolation, tmux sessions for persistence, deterministic ports to avoid conflicts -- so each agent gets a clean, independent workspace without manual setup.

How It Works

codeherd manages a lifecycle that takes a project from configuration to a running agent session:

Clone -> Worktree -> File Copy -> Template Processing -> Session Start

Each step has pre/post hooks for custom automation (install dependencies, start services, notify external systems). See docs/hooks.md for the full hook lifecycle reference.

Configuration

All configuration lives in ~/.config/codeherd/config.toml. Here is a full example:

[defaults]
projects_dir = "~/projects"# base directory for clones and worktreesagent = "claude"# default agent for new sessions# ── Agents ──────────────────────────────────────────────────────────────────
[agents.claude]
cmd = "claude"args = ["--dangerously-skip-permissions"]
[agents.claude.env]
CLAUDE_CONFIG_DIR = "/home/user/.config/claude"
[agents.aider]
cmd = "aider"args = ["--model", "opus"]
[agents.codex]
cmd = "codex"args = ["--approval-mode", "full-auto"]
# ── Projects ────────────────────────────────────────────────────────────────
[projects.myapp]
repo = "git@github.com:user/myapp.git"default_branch = "main"# Files copied into every new worktree (see File Copy section)files = [
"CLAUDE.md", # same path in worktree".cursorrules", # same path in worktree"~/.config/codeherd/prompts/safety.md:RULES.md", # absolute source, custom destination
]
# Hooks run at each lifecycle step (see Hooks section)
[projects.myapp.hooks]
post-clone = "make deps"post-worktree = "npm install"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"
[projects.api]
repo = "git@github.com:user/api.git"default_branch = "develop"files = [".envrc"]
[projects.api.hooks]
post-worktree = "bundle install"

Projects

Each project points to a git repository and a default branch. Clone paths mirror the repo URL under projects_dir:

~/projects/
github.com/user/
myapp/ # main clone
myapp__worktrees/
feature/ # worktree for "feature" branch
fix-123/ # worktree for "fix-123" branch
api/ # another project

Agents

Agents are CLI tools configured once and selected at session start. Any command-line tool works -- Claude Code, Aider, Codex, or a custom script. Each agent defines a command, optional arguments, and optional environment variables.

Run an agent in the current shell with ch run <agent>. Arguments after -- are forwarded to the agent's command verbatim, appended after its configured args -- for example ch run claude -- --model opus.

Profiles

Profiles let one machine carry several independent codeherd configs — e.g. a personal and a work set of projects and agents. Enable them in the main config and keep each profile as its own TOML file under profiles_dir:

[defaults]
profiles_enabled = trueprofiles_dir = "~/.config/codeherd/profiles"# default: <config dir>/profilesmain_profile = "personal"# used when nothing else selects one

Each <profiles_dir>/<name>.toml is a full config (its own projects, agents, projects_dir). When profiles_enabled = true, projects/agents/projects_dir in the main config are ignored (with a warning).

The active profile is resolved by precedence, lowest to highest:

  1. defaults.main_profile in the main config
  2. the CODEHERD_PROFILE environment variable
  3. the --profile / -p flag

codeherd stamps CODEHERD_PROFILE into every profile-mode session, so a nested ch call inside a session (for example ch run <agent>) defaults to that session's profile without needing --profile.

Sessions

Sessions are tmux sessions anchored to a project and branch. They run independently and persist across disconnects:

tmux sessions:
codeherd # TUI dashboard
myapp-feature # Claude Code working on feature
myapp-fix-123 # Aider fixing a bug
api-experiment # another agent exploring an idea

Session environment

codeherd stamps a fixed set of CODEHERD_* environment variables on every session it starts (both agent and shell). Use them in the agent's cmd/args or in scripts the agent invokes:

VariableValueNotes
CODEHERD_SESSIONCanonical session name, e.g. myapp-featureAlways set
CODEHERD_PROJECTProject name from configAlways set
CODEHERD_BRANCHBranch nameAlways set
CODEHERD_CLONE_DIRAbsolute path to the main git cloneSet whenever the project has a valid repo URL. Needed by anything that runs git inside a worktree — git worktrees keep .git as a file that points back to the main clone
CODEHERD_WORKTREE_PATHAbsolute path to the worktree rootAlways set
CODEHERD_PROFILEActive profile nameOnly set when a profile is active. Nested ch calls inherit it as the default profile (see Profiles)

These values win over any conflicting keys in [agents.<name>].env — the agent-level env is applied first, then codeherd's values are stamped on top.

Example: sandbox an agent with ai-jail while keeping git operations working:

[agents.claude-sandboxed]
cmd = "ai-jail"args = ["--rw-map", "$CODEHERD_WORKTREE_PATH", "--rw-map", "$CODEHERD_CLONE_DIR", "--", "claude"]

Lifecycle

When you create a worktree and start a session, codeherd runs through a five-step lifecycle. Each step has optional pre/post hooks.

 Clone ──> Worktree ──> File Copy ──> Template Processing ──> Session Start
│ │ │ │ │ │ │ │ │ │
pre post pre post pre post pre post pre post

File Copy

The files list in project config copies files into new worktrees. This is useful for shared configuration (editor rules, prompt files, env configs) that should exist in every worktree but isn't tracked in git.

Entry formatSourceDestination
"CLAUDE.md"Clone dir / CLAUDE.mdWorktree / CLAUDE.md
"src/config.json"Clone dir / src/config.jsonWorktree / src/config.json
"~/.config/prompts/safety.md:RULES.md"~/.config/prompts/safety.mdWorktree / RULES.md
"/absolute/path/file.txt:subdir/file.txt"/absolute/path/file.txtWorktree / subdir/file.txt

Relative paths resolve from the clone directory. Absolute paths and ~/ paths copy from the filesystem. Intermediate directories are created automatically.

Template Processing

After files are copied, codeherd scans the worktree for .herd files and renders them using Go's text/template engine. The rendered output is written as a sibling file without the .herd suffix:

  • .env.herd renders to .env
  • docker-compose.yml.herd renders to docker-compose.yml
  • nginx.conf.herd renders to nginx.conf

This is how you generate per-worktree configuration with unique ports and branch-specific values.

Template Variables

Templates receive a context object with these fields:

VariableTypeDescriptionExample value
.ProjectstringProject name from configmyapp
.BranchstringBranch namefeature
.WorktreePathstringAbsolute path to the worktree/home/user/projects/github.com/user/myapp__worktrees/feature
.SessionNamestringDerived session namemyapp-feature

Template Functions

FunctionDescriptionExample
port "name"Deterministic port (10000-59999) derived from project + branch + name. Same inputs always produce the same port, different branches get different ports.{{ port "http" }}
env "VAR" "default"Read an environment variable, with an optional fallback value.{{ env "API_KEY" "dev-key" }}

Example: .env.herd

# .env.herd — place this in your repo or copy it via the files list
APP_PORT={{ port "http" }}
GRPC_PORT={{ port "grpc" }}
DEBUG_PORT={{ port "debug" }}
DATABASE_URL=postgres://localhost:5432/{{ .Project }}_{{ .Branch }}
API_KEY={{ env "API_KEY" "dev-key-for-local" }}
SESSION={{ .SessionName }}

Running ch create worktree myapp feature renders this to .env:

# .env (generated)
APP_PORT=34521
GRPC_PORT=18973
DEBUG_PORT=42810
DATABASE_URL=postgres://localhost:5432/myapp_feature
API_KEY=dev-key-for-local
SESSION=myapp-feature

Every worktree gets unique ports. The feature branch and fix-123 branch will never collide.

Example: docker-compose.yml.herd

# docker-compose.yml.herdservices:
postgres:
image: postgres:16ports:
- "{{ port "postgres" }}:5432"environment:
POSTGRES_DB: {{ .Project }}_{{ .Branch }}POSTGRES_PASSWORD: {{ env "PG_PASSWORD" "devpass" }}redis:
image: redis:7ports:
- "{{ port "redis" }}:6379"

Hooks

Hooks are shell commands that run at each lifecycle step. Configure them per-project:

[projects.myapp.hooks]
pre-clone = "echo preparing to clone"post-clone = "make deps"pre-worktree = "echo creating worktree"post-worktree = "npm install"pre-copy = "echo copying files"post-copy = "chmod 600 .env"pre-template = "vault read secret/myapp > .secrets"post-template = "echo templates rendered"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"

Hooks receive context as environment variables (CODEHERD_PROJECT, CODEHERD_BRANCH, CODEHERD_WORKTREE_PATH, etc.). A non-zero exit code stops the workflow. Omit a hook to skip it.

See docs/hooks.md for the full reference including environment variables per step, working directory rules, and error handling.

Install

Via mise

mise use github:xico42/codeherd@latest

Once codeherd lands in the mise official registry, this becomes mise use codeherd@latest.

Manual

Download the appropriate archive from the latest release, extract, and place ch on your PATH. Each release ships archives for linux and darwin on amd64 and arm64, with sigstore signatures and a checksums.txt.

From source

Requires Go 1.22+, git, and tmux.

make install # builds and installs to ~/.local/bin/ch

Shell Completion

ch ships dynamic completion for agents, projects, profiles, and worktree branches. Cobra generates per-shell scripts via ch completion <shell>.

# zsh — ensure the dir is on your $fpath, then reload
ch completion zsh >"${fpath[1]}/_ch"# bash
ch completion bash | sudo tee /etc/bash_completion.d/ch > /dev/null
# fish
ch completion fish >~/.config/fish/completions/ch.fish

Completion respects the active profile: inside a profile-mode session ($CODEHERD_PROFILE set) or with -p <profile>, branch suggestions come from that profile's worktrees.

Quick Start

# Configure a project (edit ~/.config/codeherd/config.toml)# See the Configuration section above for the full config format# Clone the project
ch clone project myapp
# Create a worktree and start a session
ch create session myapp feature --agent claude --attach

Or use the interactive TUI:

ch

Commands

CommandDescription
chLaunch the TUI dashboard
ch list projectList configured projects
ch show project <name>Show project details
ch clone project <name>Clone a project
ch list worktreeList all worktrees
ch create worktree <project> <branch>Create a worktree
ch delete worktree <project> <branch>Delete a worktree
ch list sessionList active sessions
ch create session <project> <branch>Start an agent session (use --shell for a plain shell)
ch attach session <project> <branch>Attach to a session
ch show session <project> <branch>Show session details
ch delete session <project> <branch>Stop a session
ch run <agent> [-- <args>]Run a registered agent in the current shell; args after -- are forwarded to it
ch versionPrint the installed version

Development

make build # build ./ch binary
make test# run unit tests
make lint # run linter
make check # coverage (80%+) + integration tests + lint + build

Documentation

About

Like a shepherd, but for coding agents :)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

codeherd

License: Apache 2.0

A CLI for managing parallel agentic coding sessions. It organizes projects and git worktrees, configures per-agent environments with deterministic port allocation, and orchestrates tmux sessions where AI coding agents run independently.

It is like a shepherd, but for coding agents.

Why

Running multiple AI coding agents in parallel requires isolated workspaces, separate environments, and session management. codeherd handles the infrastructure -- git worktrees for isolation, tmux sessions for persistence, deterministic ports to avoid conflicts -- so each agent gets a clean, independent workspace without manual setup.

How It Works

codeherd manages a lifecycle that takes a project from configuration to a running agent session:

Clone -> Worktree -> File Copy -> Template Processing -> Session Start

Each step has pre/post hooks for custom automation (install dependencies, start services, notify external systems). See docs/hooks.md for the full hook lifecycle reference.

Configuration

All configuration lives in ~/.config/codeherd/config.toml. Here is a full example:

[defaults]
projects_dir = "~/projects"# base directory for clones and worktreesagent = "claude"# default agent for new sessions# ── Agents ──────────────────────────────────────────────────────────────────
[agents.claude]
cmd = "claude"args = ["--dangerously-skip-permissions"]
[agents.claude.env]
CLAUDE_CONFIG_DIR = "/home/user/.config/claude"
[agents.aider]
cmd = "aider"args = ["--model", "opus"]
[agents.codex]
cmd = "codex"args = ["--approval-mode", "full-auto"]
# ── Projects ────────────────────────────────────────────────────────────────
[projects.myapp]
repo = "git@github.com:user/myapp.git"default_branch = "main"# Files copied into every new worktree (see File Copy section)files = [
"CLAUDE.md", # same path in worktree".cursorrules", # same path in worktree"~/.config/codeherd/prompts/safety.md:RULES.md", # absolute source, custom destination
]
# Hooks run at each lifecycle step (see Hooks section)
[projects.myapp.hooks]
post-clone = "make deps"post-worktree = "npm install"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"
[projects.api]
repo = "git@github.com:user/api.git"default_branch = "develop"files = [".envrc"]
[projects.api.hooks]
post-worktree = "bundle install"

Projects

Each project points to a git repository and a default branch. Clone paths mirror the repo URL under projects_dir:

~/projects/
github.com/user/
myapp/ # main clone
myapp__worktrees/
feature/ # worktree for "feature" branch
fix-123/ # worktree for "fix-123" branch
api/ # another project

Agents

Agents are CLI tools configured once and selected at session start. Any command-line tool works -- Claude Code, Aider, Codex, or a custom script. Each agent defines a command, optional arguments, and optional environment variables.

Run an agent in the current shell with ch run <agent>. Arguments after -- are forwarded to the agent's command verbatim, appended after its configured args -- for example ch run claude -- --model opus.

Profiles

Profiles let one machine carry several independent codeherd configs — e.g. a personal and a work set of projects and agents. Enable them in the main config and keep each profile as its own TOML file under profiles_dir:

[defaults]
profiles_enabled = trueprofiles_dir = "~/.config/codeherd/profiles"# default: <config dir>/profilesmain_profile = "personal"# used when nothing else selects one

Each <profiles_dir>/<name>.toml is a full config (its own projects, agents, projects_dir). When profiles_enabled = true, projects/agents/projects_dir in the main config are ignored (with a warning).

The active profile is resolved by precedence, lowest to highest:

  1. defaults.main_profile in the main config
  2. the CODEHERD_PROFILE environment variable
  3. the --profile / -p flag

codeherd stamps CODEHERD_PROFILE into every profile-mode session, so a nested ch call inside a session (for example ch run <agent>) defaults to that session's profile without needing --profile.

Sessions

Sessions are tmux sessions anchored to a project and branch. They run independently and persist across disconnects:

tmux sessions:
codeherd # TUI dashboard
myapp-feature # Claude Code working on feature
myapp-fix-123 # Aider fixing a bug
api-experiment # another agent exploring an idea

Session environment

codeherd stamps a fixed set of CODEHERD_* environment variables on every session it starts (both agent and shell). Use them in the agent's cmd/args or in scripts the agent invokes:

VariableValueNotes
CODEHERD_SESSIONCanonical session name, e.g. myapp-featureAlways set
CODEHERD_PROJECTProject name from configAlways set
CODEHERD_BRANCHBranch nameAlways set
CODEHERD_CLONE_DIRAbsolute path to the main git cloneSet whenever the project has a valid repo URL. Needed by anything that runs git inside a worktree — git worktrees keep .git as a file that points back to the main clone
CODEHERD_WORKTREE_PATHAbsolute path to the worktree rootAlways set
CODEHERD_PROFILEActive profile nameOnly set when a profile is active. Nested ch calls inherit it as the default profile (see Profiles)

These values win over any conflicting keys in [agents.<name>].env — the agent-level env is applied first, then codeherd's values are stamped on top.

Example: sandbox an agent with ai-jail while keeping git operations working:

[agents.claude-sandboxed]
cmd = "ai-jail"args = ["--rw-map", "$CODEHERD_WORKTREE_PATH", "--rw-map", "$CODEHERD_CLONE_DIR", "--", "claude"]

Lifecycle

When you create a worktree and start a session, codeherd runs through a five-step lifecycle. Each step has optional pre/post hooks.

 Clone ──> Worktree ──> File Copy ──> Template Processing ──> Session Start
│ │ │ │ │ │ │ │ │ │
pre post pre post pre post pre post pre post

File Copy

The files list in project config copies files into new worktrees. This is useful for shared configuration (editor rules, prompt files, env configs) that should exist in every worktree but isn't tracked in git.

Entry formatSourceDestination
"CLAUDE.md"Clone dir / CLAUDE.mdWorktree / CLAUDE.md
"src/config.json"Clone dir / src/config.jsonWorktree / src/config.json
"~/.config/prompts/safety.md:RULES.md"~/.config/prompts/safety.mdWorktree / RULES.md
"/absolute/path/file.txt:subdir/file.txt"/absolute/path/file.txtWorktree / subdir/file.txt

Relative paths resolve from the clone directory. Absolute paths and ~/ paths copy from the filesystem. Intermediate directories are created automatically.

Template Processing

After files are copied, codeherd scans the worktree for .herd files and renders them using Go's text/template engine. The rendered output is written as a sibling file without the .herd suffix:

  • .env.herd renders to .env
  • docker-compose.yml.herd renders to docker-compose.yml
  • nginx.conf.herd renders to nginx.conf

This is how you generate per-worktree configuration with unique ports and branch-specific values.

Template Variables

Templates receive a context object with these fields:

VariableTypeDescriptionExample value
.ProjectstringProject name from configmyapp
.BranchstringBranch namefeature
.WorktreePathstringAbsolute path to the worktree/home/user/projects/github.com/user/myapp__worktrees/feature
.SessionNamestringDerived session namemyapp-feature

Template Functions

FunctionDescriptionExample
port "name"Deterministic port (10000-59999) derived from project + branch + name. Same inputs always produce the same port, different branches get different ports.{{ port "http" }}
env "VAR" "default"Read an environment variable, with an optional fallback value.{{ env "API_KEY" "dev-key" }}

Example: .env.herd

# .env.herd — place this in your repo or copy it via the files list
APP_PORT={{ port "http" }}
GRPC_PORT={{ port "grpc" }}
DEBUG_PORT={{ port "debug" }}
DATABASE_URL=postgres://localhost:5432/{{ .Project }}_{{ .Branch }}
API_KEY={{ env "API_KEY" "dev-key-for-local" }}
SESSION={{ .SessionName }}

Running ch create worktree myapp feature renders this to .env:

# .env (generated)
APP_PORT=34521
GRPC_PORT=18973
DEBUG_PORT=42810
DATABASE_URL=postgres://localhost:5432/myapp_feature
API_KEY=dev-key-for-local
SESSION=myapp-feature

Every worktree gets unique ports. The feature branch and fix-123 branch will never collide.

Example: docker-compose.yml.herd

# docker-compose.yml.herdservices:
postgres:
image: postgres:16ports:
- "{{ port "postgres" }}:5432"environment:
POSTGRES_DB: {{ .Project }}_{{ .Branch }}POSTGRES_PASSWORD: {{ env "PG_PASSWORD" "devpass" }}redis:
image: redis:7ports:
- "{{ port "redis" }}:6379"

Hooks

Hooks are shell commands that run at each lifecycle step. Configure them per-project:

[projects.myapp.hooks]
pre-clone = "echo preparing to clone"post-clone = "make deps"pre-worktree = "echo creating worktree"post-worktree = "npm install"pre-copy = "echo copying files"post-copy = "chmod 600 .env"pre-template = "vault read secret/myapp > .secrets"post-template = "echo templates rendered"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"

Hooks receive context as environment variables (CODEHERD_PROJECT, CODEHERD_BRANCH, CODEHERD_WORKTREE_PATH, etc.). A non-zero exit code stops the workflow. Omit a hook to skip it.

See docs/hooks.md for the full reference including environment variables per step, working directory rules, and error handling.

Install

Via mise

mise use github:xico42/codeherd@latest

Once codeherd lands in the mise official registry, this becomes mise use codeherd@latest.

Manual

Download the appropriate archive from the latest release, extract, and place ch on your PATH. Each release ships archives for linux and darwin on amd64 and arm64, with sigstore signatures and a checksums.txt.

From source

Requires Go 1.22+, git, and tmux.

make install # builds and installs to ~/.local/bin/ch

Shell Completion

ch ships dynamic completion for agents, projects, profiles, and worktree branches. Cobra generates per-shell scripts via ch completion <shell>.

# zsh — ensure the dir is on your $fpath, then reload
ch completion zsh >"${fpath[1]}/_ch"# bash
ch completion bash | sudo tee /etc/bash_completion.d/ch > /dev/null
# fish
ch completion fish >~/.config/fish/completions/ch.fish

Completion respects the active profile: inside a profile-mode session ($CODEHERD_PROFILE set) or with -p <profile>, branch suggestions come from that profile's worktrees.

Quick Start

# Configure a project (edit ~/.config/codeherd/config.toml)# See the Configuration section above for the full config format# Clone the project
ch clone project myapp
# Create a worktree and start a session
ch create session myapp feature --agent claude --attach

Or use the interactive TUI:

ch

Commands

CommandDescription
chLaunch the TUI dashboard
ch list projectList configured projects
ch show project <name>Show project details
ch clone project <name>Clone a project
ch list worktreeList all worktrees
ch create worktree <project> <branch>Create a worktree
ch delete worktree <project> <branch>Delete a worktree
ch list sessionList active sessions
ch create session <project> <branch>Start an agent session (use --shell for a plain shell)
ch attach session <project> <branch>Attach to a session
ch show session <project> <branch>Show session details
ch delete session <project> <branch>Stop a session
ch run <agent> [-- <args>]Run a registered agent in the current shell; args after -- are forwarded to it
ch versionPrint the installed version

Development

make build # build ./ch binary
make test# run unit tests
make lint # run linter
make check # coverage (80%+) + integration tests + lint + build

Documentation

About

Like a shepherd, but for coding agents :)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

codeherd

License: Apache 2.0

A CLI for managing parallel agentic coding sessions. It organizes projects and git worktrees, configures per-agent environments with deterministic port allocation, and orchestrates tmux sessions where AI coding agents run independently.

It is like a shepherd, but for coding agents.

Why

Running multiple AI coding agents in parallel requires isolated workspaces, separate environments, and session management. codeherd handles the infrastructure -- git worktrees for isolation, tmux sessions for persistence, deterministic ports to avoid conflicts -- so each agent gets a clean, independent workspace without manual setup.

How It Works

codeherd manages a lifecycle that takes a project from configuration to a running agent session:

Clone -> Worktree -> File Copy -> Template Processing -> Session Start

Each step has pre/post hooks for custom automation (install dependencies, start services, notify external systems). See docs/hooks.md for the full hook lifecycle reference.

Configuration

All configuration lives in ~/.config/codeherd/config.toml. Here is a full example:

[defaults]
projects_dir = "~/projects"# base directory for clones and worktreesagent = "claude"# default agent for new sessions# ── Agents ──────────────────────────────────────────────────────────────────
[agents.claude]
cmd = "claude"args = ["--dangerously-skip-permissions"]
[agents.claude.env]
CLAUDE_CONFIG_DIR = "/home/user/.config/claude"
[agents.aider]
cmd = "aider"args = ["--model", "opus"]
[agents.codex]
cmd = "codex"args = ["--approval-mode", "full-auto"]
# ── Projects ────────────────────────────────────────────────────────────────
[projects.myapp]
repo = "git@github.com:user/myapp.git"default_branch = "main"# Files copied into every new worktree (see File Copy section)files = [
"CLAUDE.md", # same path in worktree".cursorrules", # same path in worktree"~/.config/codeherd/prompts/safety.md:RULES.md", # absolute source, custom destination
]
# Hooks run at each lifecycle step (see Hooks section)
[projects.myapp.hooks]
post-clone = "make deps"post-worktree = "npm install"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"
[projects.api]
repo = "git@github.com:user/api.git"default_branch = "develop"files = [".envrc"]
[projects.api.hooks]
post-worktree = "bundle install"

Projects

Each project points to a git repository and a default branch. Clone paths mirror the repo URL under projects_dir:

~/projects/
github.com/user/
myapp/ # main clone
myapp__worktrees/
feature/ # worktree for "feature" branch
fix-123/ # worktree for "fix-123" branch
api/ # another project

Agents

Agents are CLI tools configured once and selected at session start. Any command-line tool works -- Claude Code, Aider, Codex, or a custom script. Each agent defines a command, optional arguments, and optional environment variables.

Run an agent in the current shell with ch run <agent>. Arguments after -- are forwarded to the agent's command verbatim, appended after its configured args -- for example ch run claude -- --model opus.

Profiles

Profiles let one machine carry several independent codeherd configs — e.g. a personal and a work set of projects and agents. Enable them in the main config and keep each profile as its own TOML file under profiles_dir:

[defaults]
profiles_enabled = trueprofiles_dir = "~/.config/codeherd/profiles"# default: <config dir>/profilesmain_profile = "personal"# used when nothing else selects one

Each <profiles_dir>/<name>.toml is a full config (its own projects, agents, projects_dir). When profiles_enabled = true, projects/agents/projects_dir in the main config are ignored (with a warning).

The active profile is resolved by precedence, lowest to highest:

  1. defaults.main_profile in the main config
  2. the CODEHERD_PROFILE environment variable
  3. the --profile / -p flag

codeherd stamps CODEHERD_PROFILE into every profile-mode session, so a nested ch call inside a session (for example ch run <agent>) defaults to that session's profile without needing --profile.

Sessions

Sessions are tmux sessions anchored to a project and branch. They run independently and persist across disconnects:

tmux sessions:
codeherd # TUI dashboard
myapp-feature # Claude Code working on feature
myapp-fix-123 # Aider fixing a bug
api-experiment # another agent exploring an idea

Session environment

codeherd stamps a fixed set of CODEHERD_* environment variables on every session it starts (both agent and shell). Use them in the agent's cmd/args or in scripts the agent invokes:

VariableValueNotes
CODEHERD_SESSIONCanonical session name, e.g. myapp-featureAlways set
CODEHERD_PROJECTProject name from configAlways set
CODEHERD_BRANCHBranch nameAlways set
CODEHERD_CLONE_DIRAbsolute path to the main git cloneSet whenever the project has a valid repo URL. Needed by anything that runs git inside a worktree — git worktrees keep .git as a file that points back to the main clone
CODEHERD_WORKTREE_PATHAbsolute path to the worktree rootAlways set
CODEHERD_PROFILEActive profile nameOnly set when a profile is active. Nested ch calls inherit it as the default profile (see Profiles)

These values win over any conflicting keys in [agents.<name>].env — the agent-level env is applied first, then codeherd's values are stamped on top.

Example: sandbox an agent with ai-jail while keeping git operations working:

[agents.claude-sandboxed]
cmd = "ai-jail"args = ["--rw-map", "$CODEHERD_WORKTREE_PATH", "--rw-map", "$CODEHERD_CLONE_DIR", "--", "claude"]

Lifecycle

When you create a worktree and start a session, codeherd runs through a five-step lifecycle. Each step has optional pre/post hooks.

 Clone ──> Worktree ──> File Copy ──> Template Processing ──> Session Start
│ │ │ │ │ │ │ │ │ │
pre post pre post pre post pre post pre post

File Copy

The files list in project config copies files into new worktrees. This is useful for shared configuration (editor rules, prompt files, env configs) that should exist in every worktree but isn't tracked in git.

Entry formatSourceDestination
"CLAUDE.md"Clone dir / CLAUDE.mdWorktree / CLAUDE.md
"src/config.json"Clone dir / src/config.jsonWorktree / src/config.json
"~/.config/prompts/safety.md:RULES.md"~/.config/prompts/safety.mdWorktree / RULES.md
"/absolute/path/file.txt:subdir/file.txt"/absolute/path/file.txtWorktree / subdir/file.txt

Relative paths resolve from the clone directory. Absolute paths and ~/ paths copy from the filesystem. Intermediate directories are created automatically.

Template Processing

After files are copied, codeherd scans the worktree for .herd files and renders them using Go's text/template engine. The rendered output is written as a sibling file without the .herd suffix:

  • .env.herd renders to .env
  • docker-compose.yml.herd renders to docker-compose.yml
  • nginx.conf.herd renders to nginx.conf

This is how you generate per-worktree configuration with unique ports and branch-specific values.

Template Variables

Templates receive a context object with these fields:

VariableTypeDescriptionExample value
.ProjectstringProject name from configmyapp
.BranchstringBranch namefeature
.WorktreePathstringAbsolute path to the worktree/home/user/projects/github.com/user/myapp__worktrees/feature
.SessionNamestringDerived session namemyapp-feature

Template Functions

FunctionDescriptionExample
port "name"Deterministic port (10000-59999) derived from project + branch + name. Same inputs always produce the same port, different branches get different ports.{{ port "http" }}
env "VAR" "default"Read an environment variable, with an optional fallback value.{{ env "API_KEY" "dev-key" }}

Example: .env.herd

# .env.herd — place this in your repo or copy it via the files list
APP_PORT={{ port "http" }}
GRPC_PORT={{ port "grpc" }}
DEBUG_PORT={{ port "debug" }}
DATABASE_URL=postgres://localhost:5432/{{ .Project }}_{{ .Branch }}
API_KEY={{ env "API_KEY" "dev-key-for-local" }}
SESSION={{ .SessionName }}

Running ch create worktree myapp feature renders this to .env:

# .env (generated)
APP_PORT=34521
GRPC_PORT=18973
DEBUG_PORT=42810
DATABASE_URL=postgres://localhost:5432/myapp_feature
API_KEY=dev-key-for-local
SESSION=myapp-feature

Every worktree gets unique ports. The feature branch and fix-123 branch will never collide.

Example: docker-compose.yml.herd

# docker-compose.yml.herdservices:
postgres:
image: postgres:16ports:
- "{{ port "postgres" }}:5432"environment:
POSTGRES_DB: {{ .Project }}_{{ .Branch }}POSTGRES_PASSWORD: {{ env "PG_PASSWORD" "devpass" }}redis:
image: redis:7ports:
- "{{ port "redis" }}:6379"

Hooks

Hooks are shell commands that run at each lifecycle step. Configure them per-project:

[projects.myapp.hooks]
pre-clone = "echo preparing to clone"post-clone = "make deps"pre-worktree = "echo creating worktree"post-worktree = "npm install"pre-copy = "echo copying files"post-copy = "chmod 600 .env"pre-template = "vault read secret/myapp > .secrets"post-template = "echo templates rendered"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"

Hooks receive context as environment variables (CODEHERD_PROJECT, CODEHERD_BRANCH, CODEHERD_WORKTREE_PATH, etc.). A non-zero exit code stops the workflow. Omit a hook to skip it.

See docs/hooks.md for the full reference including environment variables per step, working directory rules, and error handling.

Install

Via mise

mise use github:xico42/codeherd@latest

Once codeherd lands in the mise official registry, this becomes mise use codeherd@latest.

Manual

Download the appropriate archive from the latest release, extract, and place ch on your PATH. Each release ships archives for linux and darwin on amd64 and arm64, with sigstore signatures and a checksums.txt.

From source

Requires Go 1.22+, git, and tmux.

make install # builds and installs to ~/.local/bin/ch

Shell Completion

ch ships dynamic completion for agents, projects, profiles, and worktree branches. Cobra generates per-shell scripts via ch completion <shell>.

# zsh — ensure the dir is on your $fpath, then reload
ch completion zsh >"${fpath[1]}/_ch"# bash
ch completion bash | sudo tee /etc/bash_completion.d/ch > /dev/null
# fish
ch completion fish >~/.config/fish/completions/ch.fish

Completion respects the active profile: inside a profile-mode session ($CODEHERD_PROFILE set) or with -p <profile>, branch suggestions come from that profile's worktrees.

Quick Start

# Configure a project (edit ~/.config/codeherd/config.toml)# See the Configuration section above for the full config format# Clone the project
ch clone project myapp
# Create a worktree and start a session
ch create session myapp feature --agent claude --attach

Or use the interactive TUI:

ch

Commands

CommandDescription
chLaunch the TUI dashboard
ch list projectList configured projects
ch show project <name>Show project details
ch clone project <name>Clone a project
ch list worktreeList all worktrees
ch create worktree <project> <branch>Create a worktree
ch delete worktree <project> <branch>Delete a worktree
ch list sessionList active sessions
ch create session <project> <branch>Start an agent session (use --shell for a plain shell)
ch attach session <project> <branch>Attach to a session
ch show session <project> <branch>Show session details
ch delete session <project> <branch>Stop a session
ch run <agent> [-- <args>]Run a registered agent in the current shell; args after -- are forwarded to it
ch versionPrint the installed version

Development

make build # build ./ch binary
make test# run unit tests
make lint # run linter
make check # coverage (80%+) + integration tests + lint + build

Documentation

About

Like a shepherd, but for coding agents :)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

codeherd

License: Apache 2.0

A CLI for managing parallel agentic coding sessions. It organizes projects and git worktrees, configures per-agent environments with deterministic port allocation, and orchestrates tmux sessions where AI coding agents run independently.

It is like a shepherd, but for coding agents.

Why

Running multiple AI coding agents in parallel requires isolated workspaces, separate environments, and session management. codeherd handles the infrastructure -- git worktrees for isolation, tmux sessions for persistence, deterministic ports to avoid conflicts -- so each agent gets a clean, independent workspace without manual setup.

How It Works

codeherd manages a lifecycle that takes a project from configuration to a running agent session:

Clone -> Worktree -> File Copy -> Template Processing -> Session Start

Each step has pre/post hooks for custom automation (install dependencies, start services, notify external systems). See docs/hooks.md for the full hook lifecycle reference.

Configuration

All configuration lives in ~/.config/codeherd/config.toml. Here is a full example:

[defaults]
projects_dir = "~/projects"# base directory for clones and worktreesagent = "claude"# default agent for new sessions# ── Agents ──────────────────────────────────────────────────────────────────
[agents.claude]
cmd = "claude"args = ["--dangerously-skip-permissions"]
[agents.claude.env]
CLAUDE_CONFIG_DIR = "/home/user/.config/claude"
[agents.aider]
cmd = "aider"args = ["--model", "opus"]
[agents.codex]
cmd = "codex"args = ["--approval-mode", "full-auto"]
# ── Projects ────────────────────────────────────────────────────────────────
[projects.myapp]
repo = "git@github.com:user/myapp.git"default_branch = "main"# Files copied into every new worktree (see File Copy section)files = [
"CLAUDE.md", # same path in worktree".cursorrules", # same path in worktree"~/.config/codeherd/prompts/safety.md:RULES.md", # absolute source, custom destination
]
# Hooks run at each lifecycle step (see Hooks section)
[projects.myapp.hooks]
post-clone = "make deps"post-worktree = "npm install"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"
[projects.api]
repo = "git@github.com:user/api.git"default_branch = "develop"files = [".envrc"]
[projects.api.hooks]
post-worktree = "bundle install"

Projects

Each project points to a git repository and a default branch. Clone paths mirror the repo URL under projects_dir:

~/projects/
github.com/user/
myapp/ # main clone
myapp__worktrees/
feature/ # worktree for "feature" branch
fix-123/ # worktree for "fix-123" branch
api/ # another project

Agents

Agents are CLI tools configured once and selected at session start. Any command-line tool works -- Claude Code, Aider, Codex, or a custom script. Each agent defines a command, optional arguments, and optional environment variables.

Run an agent in the current shell with ch run <agent>. Arguments after -- are forwarded to the agent's command verbatim, appended after its configured args -- for example ch run claude -- --model opus.

Profiles

Profiles let one machine carry several independent codeherd configs — e.g. a personal and a work set of projects and agents. Enable them in the main config and keep each profile as its own TOML file under profiles_dir:

[defaults]
profiles_enabled = trueprofiles_dir = "~/.config/codeherd/profiles"# default: <config dir>/profilesmain_profile = "personal"# used when nothing else selects one

Each <profiles_dir>/<name>.toml is a full config (its own projects, agents, projects_dir). When profiles_enabled = true, projects/agents/projects_dir in the main config are ignored (with a warning).

The active profile is resolved by precedence, lowest to highest:

  1. defaults.main_profile in the main config
  2. the CODEHERD_PROFILE environment variable
  3. the --profile / -p flag

codeherd stamps CODEHERD_PROFILE into every profile-mode session, so a nested ch call inside a session (for example ch run <agent>) defaults to that session's profile without needing --profile.

Sessions

Sessions are tmux sessions anchored to a project and branch. They run independently and persist across disconnects:

tmux sessions:
codeherd # TUI dashboard
myapp-feature # Claude Code working on feature
myapp-fix-123 # Aider fixing a bug
api-experiment # another agent exploring an idea

Session environment

codeherd stamps a fixed set of CODEHERD_* environment variables on every session it starts (both agent and shell). Use them in the agent's cmd/args or in scripts the agent invokes:

VariableValueNotes
CODEHERD_SESSIONCanonical session name, e.g. myapp-featureAlways set
CODEHERD_PROJECTProject name from configAlways set
CODEHERD_BRANCHBranch nameAlways set
CODEHERD_CLONE_DIRAbsolute path to the main git cloneSet whenever the project has a valid repo URL. Needed by anything that runs git inside a worktree — git worktrees keep .git as a file that points back to the main clone
CODEHERD_WORKTREE_PATHAbsolute path to the worktree rootAlways set
CODEHERD_PROFILEActive profile nameOnly set when a profile is active. Nested ch calls inherit it as the default profile (see Profiles)

These values win over any conflicting keys in [agents.<name>].env — the agent-level env is applied first, then codeherd's values are stamped on top.

Example: sandbox an agent with ai-jail while keeping git operations working:

[agents.claude-sandboxed]
cmd = "ai-jail"args = ["--rw-map", "$CODEHERD_WORKTREE_PATH", "--rw-map", "$CODEHERD_CLONE_DIR", "--", "claude"]

Lifecycle

When you create a worktree and start a session, codeherd runs through a five-step lifecycle. Each step has optional pre/post hooks.

 Clone ──> Worktree ──> File Copy ──> Template Processing ──> Session Start
│ │ │ │ │ │ │ │ │ │
pre post pre post pre post pre post pre post

File Copy

The files list in project config copies files into new worktrees. This is useful for shared configuration (editor rules, prompt files, env configs) that should exist in every worktree but isn't tracked in git.

Entry formatSourceDestination
"CLAUDE.md"Clone dir / CLAUDE.mdWorktree / CLAUDE.md
"src/config.json"Clone dir / src/config.jsonWorktree / src/config.json
"~/.config/prompts/safety.md:RULES.md"~/.config/prompts/safety.mdWorktree / RULES.md
"/absolute/path/file.txt:subdir/file.txt"/absolute/path/file.txtWorktree / subdir/file.txt

Relative paths resolve from the clone directory. Absolute paths and ~/ paths copy from the filesystem. Intermediate directories are created automatically.

Template Processing

After files are copied, codeherd scans the worktree for .herd files and renders them using Go's text/template engine. The rendered output is written as a sibling file without the .herd suffix:

  • .env.herd renders to .env
  • docker-compose.yml.herd renders to docker-compose.yml
  • nginx.conf.herd renders to nginx.conf

This is how you generate per-worktree configuration with unique ports and branch-specific values.

Template Variables

Templates receive a context object with these fields:

VariableTypeDescriptionExample value
.ProjectstringProject name from configmyapp
.BranchstringBranch namefeature
.WorktreePathstringAbsolute path to the worktree/home/user/projects/github.com/user/myapp__worktrees/feature
.SessionNamestringDerived session namemyapp-feature

Template Functions

FunctionDescriptionExample
port "name"Deterministic port (10000-59999) derived from project + branch + name. Same inputs always produce the same port, different branches get different ports.{{ port "http" }}
env "VAR" "default"Read an environment variable, with an optional fallback value.{{ env "API_KEY" "dev-key" }}

Example: .env.herd

# .env.herd — place this in your repo or copy it via the files list
APP_PORT={{ port "http" }}
GRPC_PORT={{ port "grpc" }}
DEBUG_PORT={{ port "debug" }}
DATABASE_URL=postgres://localhost:5432/{{ .Project }}_{{ .Branch }}
API_KEY={{ env "API_KEY" "dev-key-for-local" }}
SESSION={{ .SessionName }}

Running ch create worktree myapp feature renders this to .env:

# .env (generated)
APP_PORT=34521
GRPC_PORT=18973
DEBUG_PORT=42810
DATABASE_URL=postgres://localhost:5432/myapp_feature
API_KEY=dev-key-for-local
SESSION=myapp-feature

Every worktree gets unique ports. The feature branch and fix-123 branch will never collide.

Example: docker-compose.yml.herd

# docker-compose.yml.herdservices:
postgres:
image: postgres:16ports:
- "{{ port "postgres" }}:5432"environment:
POSTGRES_DB: {{ .Project }}_{{ .Branch }}POSTGRES_PASSWORD: {{ env "PG_PASSWORD" "devpass" }}redis:
image: redis:7ports:
- "{{ port "redis" }}:6379"

Hooks

Hooks are shell commands that run at each lifecycle step. Configure them per-project:

[projects.myapp.hooks]
pre-clone = "echo preparing to clone"post-clone = "make deps"pre-worktree = "echo creating worktree"post-worktree = "npm install"pre-copy = "echo copying files"post-copy = "chmod 600 .env"pre-template = "vault read secret/myapp > .secrets"post-template = "echo templates rendered"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"

Hooks receive context as environment variables (CODEHERD_PROJECT, CODEHERD_BRANCH, CODEHERD_WORKTREE_PATH, etc.). A non-zero exit code stops the workflow. Omit a hook to skip it.

See docs/hooks.md for the full reference including environment variables per step, working directory rules, and error handling.

Install

Via mise

mise use github:xico42/codeherd@latest

Once codeherd lands in the mise official registry, this becomes mise use codeherd@latest.

Manual

Download the appropriate archive from the latest release, extract, and place ch on your PATH. Each release ships archives for linux and darwin on amd64 and arm64, with sigstore signatures and a checksums.txt.

From source

Requires Go 1.22+, git, and tmux.

make install # builds and installs to ~/.local/bin/ch

Shell Completion

ch ships dynamic completion for agents, projects, profiles, and worktree branches. Cobra generates per-shell scripts via ch completion <shell>.

# zsh — ensure the dir is on your $fpath, then reload
ch completion zsh >"${fpath[1]}/_ch"# bash
ch completion bash | sudo tee /etc/bash_completion.d/ch > /dev/null
# fish
ch completion fish >~/.config/fish/completions/ch.fish

Completion respects the active profile: inside a profile-mode session ($CODEHERD_PROFILE set) or with -p <profile>, branch suggestions come from that profile's worktrees.

Quick Start

# Configure a project (edit ~/.config/codeherd/config.toml)# See the Configuration section above for the full config format# Clone the project
ch clone project myapp
# Create a worktree and start a session
ch create session myapp feature --agent claude --attach

Or use the interactive TUI:

ch

Commands

CommandDescription
chLaunch the TUI dashboard
ch list projectList configured projects
ch show project <name>Show project details
ch clone project <name>Clone a project
ch list worktreeList all worktrees
ch create worktree <project> <branch>Create a worktree
ch delete worktree <project> <branch>Delete a worktree
ch list sessionList active sessions
ch create session <project> <branch>Start an agent session (use --shell for a plain shell)
ch attach session <project> <branch>Attach to a session
ch show session <project> <branch>Show session details
ch delete session <project> <branch>Stop a session
ch run <agent> [-- <args>]Run a registered agent in the current shell; args after -- are forwarded to it
ch versionPrint the installed version

Development

make build # build ./ch binary
make test# run unit tests
make lint # run linter
make check # coverage (80%+) + integration tests + lint + build

Documentation

About

Like a shepherd, but for coding agents :)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

codeherd

License: Apache 2.0

A CLI for managing parallel agentic coding sessions. It organizes projects and git worktrees, configures per-agent environments with deterministic port allocation, and orchestrates tmux sessions where AI coding agents run independently.

It is like a shepherd, but for coding agents.

Why

Running multiple AI coding agents in parallel requires isolated workspaces, separate environments, and session management. codeherd handles the infrastructure -- git worktrees for isolation, tmux sessions for persistence, deterministic ports to avoid conflicts -- so each agent gets a clean, independent workspace without manual setup.

How It Works

codeherd manages a lifecycle that takes a project from configuration to a running agent session:

Clone -> Worktree -> File Copy -> Template Processing -> Session Start

Each step has pre/post hooks for custom automation (install dependencies, start services, notify external systems). See docs/hooks.md for the full hook lifecycle reference.

Configuration

All configuration lives in ~/.config/codeherd/config.toml. Here is a full example:

[defaults]
projects_dir = "~/projects"# base directory for clones and worktreesagent = "claude"# default agent for new sessions# ── Agents ──────────────────────────────────────────────────────────────────
[agents.claude]
cmd = "claude"args = ["--dangerously-skip-permissions"]
[agents.claude.env]
CLAUDE_CONFIG_DIR = "/home/user/.config/claude"
[agents.aider]
cmd = "aider"args = ["--model", "opus"]
[agents.codex]
cmd = "codex"args = ["--approval-mode", "full-auto"]
# ── Projects ────────────────────────────────────────────────────────────────
[projects.myapp]
repo = "git@github.com:user/myapp.git"default_branch = "main"# Files copied into every new worktree (see File Copy section)files = [
"CLAUDE.md", # same path in worktree".cursorrules", # same path in worktree"~/.config/codeherd/prompts/safety.md:RULES.md", # absolute source, custom destination
]
# Hooks run at each lifecycle step (see Hooks section)
[projects.myapp.hooks]
post-clone = "make deps"post-worktree = "npm install"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"
[projects.api]
repo = "git@github.com:user/api.git"default_branch = "develop"files = [".envrc"]
[projects.api.hooks]
post-worktree = "bundle install"

Projects

Each project points to a git repository and a default branch. Clone paths mirror the repo URL under projects_dir:

~/projects/
github.com/user/
myapp/ # main clone
myapp__worktrees/
feature/ # worktree for "feature" branch
fix-123/ # worktree for "fix-123" branch
api/ # another project

Agents

Agents are CLI tools configured once and selected at session start. Any command-line tool works -- Claude Code, Aider, Codex, or a custom script. Each agent defines a command, optional arguments, and optional environment variables.

Run an agent in the current shell with ch run <agent>. Arguments after -- are forwarded to the agent's command verbatim, appended after its configured args -- for example ch run claude -- --model opus.

Profiles

Profiles let one machine carry several independent codeherd configs — e.g. a personal and a work set of projects and agents. Enable them in the main config and keep each profile as its own TOML file under profiles_dir:

[defaults]
profiles_enabled = trueprofiles_dir = "~/.config/codeherd/profiles"# default: <config dir>/profilesmain_profile = "personal"# used when nothing else selects one

Each <profiles_dir>/<name>.toml is a full config (its own projects, agents, projects_dir). When profiles_enabled = true, projects/agents/projects_dir in the main config are ignored (with a warning).

The active profile is resolved by precedence, lowest to highest:

  1. defaults.main_profile in the main config
  2. the CODEHERD_PROFILE environment variable
  3. the --profile / -p flag

codeherd stamps CODEHERD_PROFILE into every profile-mode session, so a nested ch call inside a session (for example ch run <agent>) defaults to that session's profile without needing --profile.

Sessions

Sessions are tmux sessions anchored to a project and branch. They run independently and persist across disconnects:

tmux sessions:
codeherd # TUI dashboard
myapp-feature # Claude Code working on feature
myapp-fix-123 # Aider fixing a bug
api-experiment # another agent exploring an idea

Session environment

codeherd stamps a fixed set of CODEHERD_* environment variables on every session it starts (both agent and shell). Use them in the agent's cmd/args or in scripts the agent invokes:

VariableValueNotes
CODEHERD_SESSIONCanonical session name, e.g. myapp-featureAlways set
CODEHERD_PROJECTProject name from configAlways set
CODEHERD_BRANCHBranch nameAlways set
CODEHERD_CLONE_DIRAbsolute path to the main git cloneSet whenever the project has a valid repo URL. Needed by anything that runs git inside a worktree — git worktrees keep .git as a file that points back to the main clone
CODEHERD_WORKTREE_PATHAbsolute path to the worktree rootAlways set
CODEHERD_PROFILEActive profile nameOnly set when a profile is active. Nested ch calls inherit it as the default profile (see Profiles)

These values win over any conflicting keys in [agents.<name>].env — the agent-level env is applied first, then codeherd's values are stamped on top.

Example: sandbox an agent with ai-jail while keeping git operations working:

[agents.claude-sandboxed]
cmd = "ai-jail"args = ["--rw-map", "$CODEHERD_WORKTREE_PATH", "--rw-map", "$CODEHERD_CLONE_DIR", "--", "claude"]

Lifecycle

When you create a worktree and start a session, codeherd runs through a five-step lifecycle. Each step has optional pre/post hooks.

 Clone ──> Worktree ──> File Copy ──> Template Processing ──> Session Start
│ │ │ │ │ │ │ │ │ │
pre post pre post pre post pre post pre post

File Copy

The files list in project config copies files into new worktrees. This is useful for shared configuration (editor rules, prompt files, env configs) that should exist in every worktree but isn't tracked in git.

Entry formatSourceDestination
"CLAUDE.md"Clone dir / CLAUDE.mdWorktree / CLAUDE.md
"src/config.json"Clone dir / src/config.jsonWorktree / src/config.json
"~/.config/prompts/safety.md:RULES.md"~/.config/prompts/safety.mdWorktree / RULES.md
"/absolute/path/file.txt:subdir/file.txt"/absolute/path/file.txtWorktree / subdir/file.txt

Relative paths resolve from the clone directory. Absolute paths and ~/ paths copy from the filesystem. Intermediate directories are created automatically.

Template Processing

After files are copied, codeherd scans the worktree for .herd files and renders them using Go's text/template engine. The rendered output is written as a sibling file without the .herd suffix:

  • .env.herd renders to .env
  • docker-compose.yml.herd renders to docker-compose.yml
  • nginx.conf.herd renders to nginx.conf

This is how you generate per-worktree configuration with unique ports and branch-specific values.

Template Variables

Templates receive a context object with these fields:

VariableTypeDescriptionExample value
.ProjectstringProject name from configmyapp
.BranchstringBranch namefeature
.WorktreePathstringAbsolute path to the worktree/home/user/projects/github.com/user/myapp__worktrees/feature
.SessionNamestringDerived session namemyapp-feature

Template Functions

FunctionDescriptionExample
port "name"Deterministic port (10000-59999) derived from project + branch + name. Same inputs always produce the same port, different branches get different ports.{{ port "http" }}
env "VAR" "default"Read an environment variable, with an optional fallback value.{{ env "API_KEY" "dev-key" }}

Example: .env.herd

# .env.herd — place this in your repo or copy it via the files list
APP_PORT={{ port "http" }}
GRPC_PORT={{ port "grpc" }}
DEBUG_PORT={{ port "debug" }}
DATABASE_URL=postgres://localhost:5432/{{ .Project }}_{{ .Branch }}
API_KEY={{ env "API_KEY" "dev-key-for-local" }}
SESSION={{ .SessionName }}

Running ch create worktree myapp feature renders this to .env:

# .env (generated)
APP_PORT=34521
GRPC_PORT=18973
DEBUG_PORT=42810
DATABASE_URL=postgres://localhost:5432/myapp_feature
API_KEY=dev-key-for-local
SESSION=myapp-feature

Every worktree gets unique ports. The feature branch and fix-123 branch will never collide.

Example: docker-compose.yml.herd

# docker-compose.yml.herdservices:
postgres:
image: postgres:16ports:
- "{{ port "postgres" }}:5432"environment:
POSTGRES_DB: {{ .Project }}_{{ .Branch }}POSTGRES_PASSWORD: {{ env "PG_PASSWORD" "devpass" }}redis:
image: redis:7ports:
- "{{ port "redis" }}:6379"

Hooks

Hooks are shell commands that run at each lifecycle step. Configure them per-project:

[projects.myapp.hooks]
pre-clone = "echo preparing to clone"post-clone = "make deps"pre-worktree = "echo creating worktree"post-worktree = "npm install"pre-copy = "echo copying files"post-copy = "chmod 600 .env"pre-template = "vault read secret/myapp > .secrets"post-template = "echo templates rendered"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"

Hooks receive context as environment variables (CODEHERD_PROJECT, CODEHERD_BRANCH, CODEHERD_WORKTREE_PATH, etc.). A non-zero exit code stops the workflow. Omit a hook to skip it.

See docs/hooks.md for the full reference including environment variables per step, working directory rules, and error handling.

Install

Via mise

mise use github:xico42/codeherd@latest

Once codeherd lands in the mise official registry, this becomes mise use codeherd@latest.

Manual

Download the appropriate archive from the latest release, extract, and place ch on your PATH. Each release ships archives for linux and darwin on amd64 and arm64, with sigstore signatures and a checksums.txt.

From source

Requires Go 1.22+, git, and tmux.

make install # builds and installs to ~/.local/bin/ch

Shell Completion

ch ships dynamic completion for agents, projects, profiles, and worktree branches. Cobra generates per-shell scripts via ch completion <shell>.

# zsh — ensure the dir is on your $fpath, then reload
ch completion zsh >"${fpath[1]}/_ch"# bash
ch completion bash | sudo tee /etc/bash_completion.d/ch > /dev/null
# fish
ch completion fish >~/.config/fish/completions/ch.fish

Completion respects the active profile: inside a profile-mode session ($CODEHERD_PROFILE set) or with -p <profile>, branch suggestions come from that profile's worktrees.

Quick Start

# Configure a project (edit ~/.config/codeherd/config.toml)# See the Configuration section above for the full config format# Clone the project
ch clone project myapp
# Create a worktree and start a session
ch create session myapp feature --agent claude --attach

Or use the interactive TUI:

ch

Commands

CommandDescription
chLaunch the TUI dashboard
ch list projectList configured projects
ch show project <name>Show project details
ch clone project <name>Clone a project
ch list worktreeList all worktrees
ch create worktree <project> <branch>Create a worktree
ch delete worktree <project> <branch>Delete a worktree
ch list sessionList active sessions
ch create session <project> <branch>Start an agent session (use --shell for a plain shell)
ch attach session <project> <branch>Attach to a session
ch show session <project> <branch>Show session details
ch delete session <project> <branch>Stop a session
ch run <agent> [-- <args>]Run a registered agent in the current shell; args after -- are forwarded to it
ch versionPrint the installed version

Development

make build # build ./ch binary
make test# run unit tests
make lint # run linter
make check # coverage (80%+) + integration tests + lint + build

Documentation

About

Like a shepherd, but for coding agents :)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

codeherd

License: Apache 2.0

A CLI for managing parallel agentic coding sessions. It organizes projects and git worktrees, configures per-agent environments with deterministic port allocation, and orchestrates tmux sessions where AI coding agents run independently.

It is like a shepherd, but for coding agents.

Why

Running multiple AI coding agents in parallel requires isolated workspaces, separate environments, and session management. codeherd handles the infrastructure -- git worktrees for isolation, tmux sessions for persistence, deterministic ports to avoid conflicts -- so each agent gets a clean, independent workspace without manual setup.

How It Works

codeherd manages a lifecycle that takes a project from configuration to a running agent session:

Clone -> Worktree -> File Copy -> Template Processing -> Session Start

Each step has pre/post hooks for custom automation (install dependencies, start services, notify external systems). See docs/hooks.md for the full hook lifecycle reference.

Configuration

All configuration lives in ~/.config/codeherd/config.toml. Here is a full example:

[defaults]
projects_dir = "~/projects"# base directory for clones and worktreesagent = "claude"# default agent for new sessions# ── Agents ──────────────────────────────────────────────────────────────────
[agents.claude]
cmd = "claude"args = ["--dangerously-skip-permissions"]
[agents.claude.env]
CLAUDE_CONFIG_DIR = "/home/user/.config/claude"
[agents.aider]
cmd = "aider"args = ["--model", "opus"]
[agents.codex]
cmd = "codex"args = ["--approval-mode", "full-auto"]
# ── Projects ────────────────────────────────────────────────────────────────
[projects.myapp]
repo = "git@github.com:user/myapp.git"default_branch = "main"# Files copied into every new worktree (see File Copy section)files = [
"CLAUDE.md", # same path in worktree".cursorrules", # same path in worktree"~/.config/codeherd/prompts/safety.md:RULES.md", # absolute source, custom destination
]
# Hooks run at each lifecycle step (see Hooks section)
[projects.myapp.hooks]
post-clone = "make deps"post-worktree = "npm install"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"
[projects.api]
repo = "git@github.com:user/api.git"default_branch = "develop"files = [".envrc"]
[projects.api.hooks]
post-worktree = "bundle install"

Projects

Each project points to a git repository and a default branch. Clone paths mirror the repo URL under projects_dir:

~/projects/
github.com/user/
myapp/ # main clone
myapp__worktrees/
feature/ # worktree for "feature" branch
fix-123/ # worktree for "fix-123" branch
api/ # another project

Agents

Agents are CLI tools configured once and selected at session start. Any command-line tool works -- Claude Code, Aider, Codex, or a custom script. Each agent defines a command, optional arguments, and optional environment variables.

Run an agent in the current shell with ch run <agent>. Arguments after -- are forwarded to the agent's command verbatim, appended after its configured args -- for example ch run claude -- --model opus.

Profiles

Profiles let one machine carry several independent codeherd configs — e.g. a personal and a work set of projects and agents. Enable them in the main config and keep each profile as its own TOML file under profiles_dir:

[defaults]
profiles_enabled = trueprofiles_dir = "~/.config/codeherd/profiles"# default: <config dir>/profilesmain_profile = "personal"# used when nothing else selects one

Each <profiles_dir>/<name>.toml is a full config (its own projects, agents, projects_dir). When profiles_enabled = true, projects/agents/projects_dir in the main config are ignored (with a warning).

The active profile is resolved by precedence, lowest to highest:

  1. defaults.main_profile in the main config
  2. the CODEHERD_PROFILE environment variable
  3. the --profile / -p flag

codeherd stamps CODEHERD_PROFILE into every profile-mode session, so a nested ch call inside a session (for example ch run <agent>) defaults to that session's profile without needing --profile.

Sessions

Sessions are tmux sessions anchored to a project and branch. They run independently and persist across disconnects:

tmux sessions:
codeherd # TUI dashboard
myapp-feature # Claude Code working on feature
myapp-fix-123 # Aider fixing a bug
api-experiment # another agent exploring an idea

Session environment

codeherd stamps a fixed set of CODEHERD_* environment variables on every session it starts (both agent and shell). Use them in the agent's cmd/args or in scripts the agent invokes:

VariableValueNotes
CODEHERD_SESSIONCanonical session name, e.g. myapp-featureAlways set
CODEHERD_PROJECTProject name from configAlways set
CODEHERD_BRANCHBranch nameAlways set
CODEHERD_CLONE_DIRAbsolute path to the main git cloneSet whenever the project has a valid repo URL. Needed by anything that runs git inside a worktree — git worktrees keep .git as a file that points back to the main clone
CODEHERD_WORKTREE_PATHAbsolute path to the worktree rootAlways set
CODEHERD_PROFILEActive profile nameOnly set when a profile is active. Nested ch calls inherit it as the default profile (see Profiles)

These values win over any conflicting keys in [agents.<name>].env — the agent-level env is applied first, then codeherd's values are stamped on top.

Example: sandbox an agent with ai-jail while keeping git operations working:

[agents.claude-sandboxed]
cmd = "ai-jail"args = ["--rw-map", "$CODEHERD_WORKTREE_PATH", "--rw-map", "$CODEHERD_CLONE_DIR", "--", "claude"]

Lifecycle

When you create a worktree and start a session, codeherd runs through a five-step lifecycle. Each step has optional pre/post hooks.

 Clone ──> Worktree ──> File Copy ──> Template Processing ──> Session Start
│ │ │ │ │ │ │ │ │ │
pre post pre post pre post pre post pre post

File Copy

The files list in project config copies files into new worktrees. This is useful for shared configuration (editor rules, prompt files, env configs) that should exist in every worktree but isn't tracked in git.

Entry formatSourceDestination
"CLAUDE.md"Clone dir / CLAUDE.mdWorktree / CLAUDE.md
"src/config.json"Clone dir / src/config.jsonWorktree / src/config.json
"~/.config/prompts/safety.md:RULES.md"~/.config/prompts/safety.mdWorktree / RULES.md
"/absolute/path/file.txt:subdir/file.txt"/absolute/path/file.txtWorktree / subdir/file.txt

Relative paths resolve from the clone directory. Absolute paths and ~/ paths copy from the filesystem. Intermediate directories are created automatically.

Template Processing

After files are copied, codeherd scans the worktree for .herd files and renders them using Go's text/template engine. The rendered output is written as a sibling file without the .herd suffix:

  • .env.herd renders to .env
  • docker-compose.yml.herd renders to docker-compose.yml
  • nginx.conf.herd renders to nginx.conf

This is how you generate per-worktree configuration with unique ports and branch-specific values.

Template Variables

Templates receive a context object with these fields:

VariableTypeDescriptionExample value
.ProjectstringProject name from configmyapp
.BranchstringBranch namefeature
.WorktreePathstringAbsolute path to the worktree/home/user/projects/github.com/user/myapp__worktrees/feature
.SessionNamestringDerived session namemyapp-feature

Template Functions

FunctionDescriptionExample
port "name"Deterministic port (10000-59999) derived from project + branch + name. Same inputs always produce the same port, different branches get different ports.{{ port "http" }}
env "VAR" "default"Read an environment variable, with an optional fallback value.{{ env "API_KEY" "dev-key" }}

Example: .env.herd

# .env.herd — place this in your repo or copy it via the files list
APP_PORT={{ port "http" }}
GRPC_PORT={{ port "grpc" }}
DEBUG_PORT={{ port "debug" }}
DATABASE_URL=postgres://localhost:5432/{{ .Project }}_{{ .Branch }}
API_KEY={{ env "API_KEY" "dev-key-for-local" }}
SESSION={{ .SessionName }}

Running ch create worktree myapp feature renders this to .env:

# .env (generated)
APP_PORT=34521
GRPC_PORT=18973
DEBUG_PORT=42810
DATABASE_URL=postgres://localhost:5432/myapp_feature
API_KEY=dev-key-for-local
SESSION=myapp-feature

Every worktree gets unique ports. The feature branch and fix-123 branch will never collide.

Example: docker-compose.yml.herd

# docker-compose.yml.herdservices:
postgres:
image: postgres:16ports:
- "{{ port "postgres" }}:5432"environment:
POSTGRES_DB: {{ .Project }}_{{ .Branch }}POSTGRES_PASSWORD: {{ env "PG_PASSWORD" "devpass" }}redis:
image: redis:7ports:
- "{{ port "redis" }}:6379"

Hooks

Hooks are shell commands that run at each lifecycle step. Configure them per-project:

[projects.myapp.hooks]
pre-clone = "echo preparing to clone"post-clone = "make deps"pre-worktree = "echo creating worktree"post-worktree = "npm install"pre-copy = "echo copying files"post-copy = "chmod 600 .env"pre-template = "vault read secret/myapp > .secrets"post-template = "echo templates rendered"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"

Hooks receive context as environment variables (CODEHERD_PROJECT, CODEHERD_BRANCH, CODEHERD_WORKTREE_PATH, etc.). A non-zero exit code stops the workflow. Omit a hook to skip it.

See docs/hooks.md for the full reference including environment variables per step, working directory rules, and error handling.

Install

Via mise

mise use github:xico42/codeherd@latest

Once codeherd lands in the mise official registry, this becomes mise use codeherd@latest.

Manual

Download the appropriate archive from the latest release, extract, and place ch on your PATH. Each release ships archives for linux and darwin on amd64 and arm64, with sigstore signatures and a checksums.txt.

From source

Requires Go 1.22+, git, and tmux.

make install # builds and installs to ~/.local/bin/ch

Shell Completion

ch ships dynamic completion for agents, projects, profiles, and worktree branches. Cobra generates per-shell scripts via ch completion <shell>.

# zsh — ensure the dir is on your $fpath, then reload
ch completion zsh >"${fpath[1]}/_ch"# bash
ch completion bash | sudo tee /etc/bash_completion.d/ch > /dev/null
# fish
ch completion fish >~/.config/fish/completions/ch.fish

Completion respects the active profile: inside a profile-mode session ($CODEHERD_PROFILE set) or with -p <profile>, branch suggestions come from that profile's worktrees.

Quick Start

# Configure a project (edit ~/.config/codeherd/config.toml)# See the Configuration section above for the full config format# Clone the project
ch clone project myapp
# Create a worktree and start a session
ch create session myapp feature --agent claude --attach

Or use the interactive TUI:

ch

Commands

CommandDescription
chLaunch the TUI dashboard
ch list projectList configured projects
ch show project <name>Show project details
ch clone project <name>Clone a project
ch list worktreeList all worktrees
ch create worktree <project> <branch>Create a worktree
ch delete worktree <project> <branch>Delete a worktree
ch list sessionList active sessions
ch create session <project> <branch>Start an agent session (use --shell for a plain shell)
ch attach session <project> <branch>Attach to a session
ch show session <project> <branch>Show session details
ch delete session <project> <branch>Stop a session
ch run <agent> [-- <args>]Run a registered agent in the current shell; args after -- are forwarded to it
ch versionPrint the installed version

Development

make build # build ./ch binary
make test# run unit tests
make lint # run linter
make check # coverage (80%+) + integration tests + lint + build

Documentation

About

Like a shepherd, but for coding agents :)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

codeherd

License: Apache 2.0

A CLI for managing parallel agentic coding sessions. It organizes projects and git worktrees, configures per-agent environments with deterministic port allocation, and orchestrates tmux sessions where AI coding agents run independently.

It is like a shepherd, but for coding agents.

Why

Running multiple AI coding agents in parallel requires isolated workspaces, separate environments, and session management. codeherd handles the infrastructure -- git worktrees for isolation, tmux sessions for persistence, deterministic ports to avoid conflicts -- so each agent gets a clean, independent workspace without manual setup.

How It Works

codeherd manages a lifecycle that takes a project from configuration to a running agent session:

Clone -> Worktree -> File Copy -> Template Processing -> Session Start

Each step has pre/post hooks for custom automation (install dependencies, start services, notify external systems). See docs/hooks.md for the full hook lifecycle reference.

Configuration

All configuration lives in ~/.config/codeherd/config.toml. Here is a full example:

[defaults]
projects_dir = "~/projects"# base directory for clones and worktreesagent = "claude"# default agent for new sessions# ── Agents ──────────────────────────────────────────────────────────────────
[agents.claude]
cmd = "claude"args = ["--dangerously-skip-permissions"]
[agents.claude.env]
CLAUDE_CONFIG_DIR = "/home/user/.config/claude"
[agents.aider]
cmd = "aider"args = ["--model", "opus"]
[agents.codex]
cmd = "codex"args = ["--approval-mode", "full-auto"]
# ── Projects ────────────────────────────────────────────────────────────────
[projects.myapp]
repo = "git@github.com:user/myapp.git"default_branch = "main"# Files copied into every new worktree (see File Copy section)files = [
"CLAUDE.md", # same path in worktree".cursorrules", # same path in worktree"~/.config/codeherd/prompts/safety.md:RULES.md", # absolute source, custom destination
]
# Hooks run at each lifecycle step (see Hooks section)
[projects.myapp.hooks]
post-clone = "make deps"post-worktree = "npm install"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"
[projects.api]
repo = "git@github.com:user/api.git"default_branch = "develop"files = [".envrc"]
[projects.api.hooks]
post-worktree = "bundle install"

Projects

Each project points to a git repository and a default branch. Clone paths mirror the repo URL under projects_dir:

~/projects/
github.com/user/
myapp/ # main clone
myapp__worktrees/
feature/ # worktree for "feature" branch
fix-123/ # worktree for "fix-123" branch
api/ # another project

Agents

Agents are CLI tools configured once and selected at session start. Any command-line tool works -- Claude Code, Aider, Codex, or a custom script. Each agent defines a command, optional arguments, and optional environment variables.

Run an agent in the current shell with ch run <agent>. Arguments after -- are forwarded to the agent's command verbatim, appended after its configured args -- for example ch run claude -- --model opus.

Profiles

Profiles let one machine carry several independent codeherd configs — e.g. a personal and a work set of projects and agents. Enable them in the main config and keep each profile as its own TOML file under profiles_dir:

[defaults]
profiles_enabled = trueprofiles_dir = "~/.config/codeherd/profiles"# default: <config dir>/profilesmain_profile = "personal"# used when nothing else selects one

Each <profiles_dir>/<name>.toml is a full config (its own projects, agents, projects_dir). When profiles_enabled = true, projects/agents/projects_dir in the main config are ignored (with a warning).

The active profile is resolved by precedence, lowest to highest:

  1. defaults.main_profile in the main config
  2. the CODEHERD_PROFILE environment variable
  3. the --profile / -p flag

codeherd stamps CODEHERD_PROFILE into every profile-mode session, so a nested ch call inside a session (for example ch run <agent>) defaults to that session's profile without needing --profile.

Sessions

Sessions are tmux sessions anchored to a project and branch. They run independently and persist across disconnects:

tmux sessions:
codeherd # TUI dashboard
myapp-feature # Claude Code working on feature
myapp-fix-123 # Aider fixing a bug
api-experiment # another agent exploring an idea

Session environment

codeherd stamps a fixed set of CODEHERD_* environment variables on every session it starts (both agent and shell). Use them in the agent's cmd/args or in scripts the agent invokes:

VariableValueNotes
CODEHERD_SESSIONCanonical session name, e.g. myapp-featureAlways set
CODEHERD_PROJECTProject name from configAlways set
CODEHERD_BRANCHBranch nameAlways set
CODEHERD_CLONE_DIRAbsolute path to the main git cloneSet whenever the project has a valid repo URL. Needed by anything that runs git inside a worktree — git worktrees keep .git as a file that points back to the main clone
CODEHERD_WORKTREE_PATHAbsolute path to the worktree rootAlways set
CODEHERD_PROFILEActive profile nameOnly set when a profile is active. Nested ch calls inherit it as the default profile (see Profiles)

These values win over any conflicting keys in [agents.<name>].env — the agent-level env is applied first, then codeherd's values are stamped on top.

Example: sandbox an agent with ai-jail while keeping git operations working:

[agents.claude-sandboxed]
cmd = "ai-jail"args = ["--rw-map", "$CODEHERD_WORKTREE_PATH", "--rw-map", "$CODEHERD_CLONE_DIR", "--", "claude"]

Lifecycle

When you create a worktree and start a session, codeherd runs through a five-step lifecycle. Each step has optional pre/post hooks.

 Clone ──> Worktree ──> File Copy ──> Template Processing ──> Session Start
│ │ │ │ │ │ │ │ │ │
pre post pre post pre post pre post pre post

File Copy

The files list in project config copies files into new worktrees. This is useful for shared configuration (editor rules, prompt files, env configs) that should exist in every worktree but isn't tracked in git.

Entry formatSourceDestination
"CLAUDE.md"Clone dir / CLAUDE.mdWorktree / CLAUDE.md
"src/config.json"Clone dir / src/config.jsonWorktree / src/config.json
"~/.config/prompts/safety.md:RULES.md"~/.config/prompts/safety.mdWorktree / RULES.md
"/absolute/path/file.txt:subdir/file.txt"/absolute/path/file.txtWorktree / subdir/file.txt

Relative paths resolve from the clone directory. Absolute paths and ~/ paths copy from the filesystem. Intermediate directories are created automatically.

Template Processing

After files are copied, codeherd scans the worktree for .herd files and renders them using Go's text/template engine. The rendered output is written as a sibling file without the .herd suffix:

  • .env.herd renders to .env
  • docker-compose.yml.herd renders to docker-compose.yml
  • nginx.conf.herd renders to nginx.conf

This is how you generate per-worktree configuration with unique ports and branch-specific values.

Template Variables

Templates receive a context object with these fields:

VariableTypeDescriptionExample value
.ProjectstringProject name from configmyapp
.BranchstringBranch namefeature
.WorktreePathstringAbsolute path to the worktree/home/user/projects/github.com/user/myapp__worktrees/feature
.SessionNamestringDerived session namemyapp-feature

Template Functions

FunctionDescriptionExample
port "name"Deterministic port (10000-59999) derived from project + branch + name. Same inputs always produce the same port, different branches get different ports.{{ port "http" }}
env "VAR" "default"Read an environment variable, with an optional fallback value.{{ env "API_KEY" "dev-key" }}

Example: .env.herd

# .env.herd — place this in your repo or copy it via the files list
APP_PORT={{ port "http" }}
GRPC_PORT={{ port "grpc" }}
DEBUG_PORT={{ port "debug" }}
DATABASE_URL=postgres://localhost:5432/{{ .Project }}_{{ .Branch }}
API_KEY={{ env "API_KEY" "dev-key-for-local" }}
SESSION={{ .SessionName }}

Running ch create worktree myapp feature renders this to .env:

# .env (generated)
APP_PORT=34521
GRPC_PORT=18973
DEBUG_PORT=42810
DATABASE_URL=postgres://localhost:5432/myapp_feature
API_KEY=dev-key-for-local
SESSION=myapp-feature

Every worktree gets unique ports. The feature branch and fix-123 branch will never collide.

Example: docker-compose.yml.herd

# docker-compose.yml.herdservices:
postgres:
image: postgres:16ports:
- "{{ port "postgres" }}:5432"environment:
POSTGRES_DB: {{ .Project }}_{{ .Branch }}POSTGRES_PASSWORD: {{ env "PG_PASSWORD" "devpass" }}redis:
image: redis:7ports:
- "{{ port "redis" }}:6379"

Hooks

Hooks are shell commands that run at each lifecycle step. Configure them per-project:

[projects.myapp.hooks]
pre-clone = "echo preparing to clone"post-clone = "make deps"pre-worktree = "echo creating worktree"post-worktree = "npm install"pre-copy = "echo copying files"post-copy = "chmod 600 .env"pre-template = "vault read secret/myapp > .secrets"post-template = "echo templates rendered"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"

Hooks receive context as environment variables (CODEHERD_PROJECT, CODEHERD_BRANCH, CODEHERD_WORKTREE_PATH, etc.). A non-zero exit code stops the workflow. Omit a hook to skip it.

See docs/hooks.md for the full reference including environment variables per step, working directory rules, and error handling.

Install

Via mise

mise use github:xico42/codeherd@latest

Once codeherd lands in the mise official registry, this becomes mise use codeherd@latest.

Manual

Download the appropriate archive from the latest release, extract, and place ch on your PATH. Each release ships archives for linux and darwin on amd64 and arm64, with sigstore signatures and a checksums.txt.

From source

Requires Go 1.22+, git, and tmux.

make install # builds and installs to ~/.local/bin/ch

Shell Completion

ch ships dynamic completion for agents, projects, profiles, and worktree branches. Cobra generates per-shell scripts via ch completion <shell>.

# zsh — ensure the dir is on your $fpath, then reload
ch completion zsh >"${fpath[1]}/_ch"# bash
ch completion bash | sudo tee /etc/bash_completion.d/ch > /dev/null
# fish
ch completion fish >~/.config/fish/completions/ch.fish

Completion respects the active profile: inside a profile-mode session ($CODEHERD_PROFILE set) or with -p <profile>, branch suggestions come from that profile's worktrees.

Quick Start

# Configure a project (edit ~/.config/codeherd/config.toml)# See the Configuration section above for the full config format# Clone the project
ch clone project myapp
# Create a worktree and start a session
ch create session myapp feature --agent claude --attach

Or use the interactive TUI:

ch

Commands

CommandDescription
chLaunch the TUI dashboard
ch list projectList configured projects
ch show project <name>Show project details
ch clone project <name>Clone a project
ch list worktreeList all worktrees
ch create worktree <project> <branch>Create a worktree
ch delete worktree <project> <branch>Delete a worktree
ch list sessionList active sessions
ch create session <project> <branch>Start an agent session (use --shell for a plain shell)
ch attach session <project> <branch>Attach to a session
ch show session <project> <branch>Show session details
ch delete session <project> <branch>Stop a session
ch run <agent> [-- <args>]Run a registered agent in the current shell; args after -- are forwarded to it
ch versionPrint the installed version

Development

make build # build ./ch binary
make test# run unit tests
make lint # run linter
make check # coverage (80%+) + integration tests + lint + build

Documentation

About

Like a shepherd, but for coding agents :)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

codeherd

License: Apache 2.0

A CLI for managing parallel agentic coding sessions. It organizes projects and git worktrees, configures per-agent environments with deterministic port allocation, and orchestrates tmux sessions where AI coding agents run independently.

It is like a shepherd, but for coding agents.

Why

Running multiple AI coding agents in parallel requires isolated workspaces, separate environments, and session management. codeherd handles the infrastructure -- git worktrees for isolation, tmux sessions for persistence, deterministic ports to avoid conflicts -- so each agent gets a clean, independent workspace without manual setup.

How It Works

codeherd manages a lifecycle that takes a project from configuration to a running agent session:

Clone -> Worktree -> File Copy -> Template Processing -> Session Start

Each step has pre/post hooks for custom automation (install dependencies, start services, notify external systems). See docs/hooks.md for the full hook lifecycle reference.

Configuration

All configuration lives in ~/.config/codeherd/config.toml. Here is a full example:

[defaults]
projects_dir = "~/projects"# base directory for clones and worktreesagent = "claude"# default agent for new sessions# ── Agents ──────────────────────────────────────────────────────────────────
[agents.claude]
cmd = "claude"args = ["--dangerously-skip-permissions"]
[agents.claude.env]
CLAUDE_CONFIG_DIR = "/home/user/.config/claude"
[agents.aider]
cmd = "aider"args = ["--model", "opus"]
[agents.codex]
cmd = "codex"args = ["--approval-mode", "full-auto"]
# ── Projects ────────────────────────────────────────────────────────────────
[projects.myapp]
repo = "git@github.com:user/myapp.git"default_branch = "main"# Files copied into every new worktree (see File Copy section)files = [
"CLAUDE.md", # same path in worktree".cursorrules", # same path in worktree"~/.config/codeherd/prompts/safety.md:RULES.md", # absolute source, custom destination
]
# Hooks run at each lifecycle step (see Hooks section)
[projects.myapp.hooks]
post-clone = "make deps"post-worktree = "npm install"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"
[projects.api]
repo = "git@github.com:user/api.git"default_branch = "develop"files = [".envrc"]
[projects.api.hooks]
post-worktree = "bundle install"

Projects

Each project points to a git repository and a default branch. Clone paths mirror the repo URL under projects_dir:

~/projects/
github.com/user/
myapp/ # main clone
myapp__worktrees/
feature/ # worktree for "feature" branch
fix-123/ # worktree for "fix-123" branch
api/ # another project

Agents

Agents are CLI tools configured once and selected at session start. Any command-line tool works -- Claude Code, Aider, Codex, or a custom script. Each agent defines a command, optional arguments, and optional environment variables.

Run an agent in the current shell with ch run <agent>. Arguments after -- are forwarded to the agent's command verbatim, appended after its configured args -- for example ch run claude -- --model opus.

Profiles

Profiles let one machine carry several independent codeherd configs — e.g. a personal and a work set of projects and agents. Enable them in the main config and keep each profile as its own TOML file under profiles_dir:

[defaults]
profiles_enabled = trueprofiles_dir = "~/.config/codeherd/profiles"# default: <config dir>/profilesmain_profile = "personal"# used when nothing else selects one

Each <profiles_dir>/<name>.toml is a full config (its own projects, agents, projects_dir). When profiles_enabled = true, projects/agents/projects_dir in the main config are ignored (with a warning).

The active profile is resolved by precedence, lowest to highest:

  1. defaults.main_profile in the main config
  2. the CODEHERD_PROFILE environment variable
  3. the --profile / -p flag

codeherd stamps CODEHERD_PROFILE into every profile-mode session, so a nested ch call inside a session (for example ch run <agent>) defaults to that session's profile without needing --profile.

Sessions

Sessions are tmux sessions anchored to a project and branch. They run independently and persist across disconnects:

tmux sessions:
codeherd # TUI dashboard
myapp-feature # Claude Code working on feature
myapp-fix-123 # Aider fixing a bug
api-experiment # another agent exploring an idea

Session environment

codeherd stamps a fixed set of CODEHERD_* environment variables on every session it starts (both agent and shell). Use them in the agent's cmd/args or in scripts the agent invokes:

VariableValueNotes
CODEHERD_SESSIONCanonical session name, e.g. myapp-featureAlways set
CODEHERD_PROJECTProject name from configAlways set
CODEHERD_BRANCHBranch nameAlways set
CODEHERD_CLONE_DIRAbsolute path to the main git cloneSet whenever the project has a valid repo URL. Needed by anything that runs git inside a worktree — git worktrees keep .git as a file that points back to the main clone
CODEHERD_WORKTREE_PATHAbsolute path to the worktree rootAlways set
CODEHERD_PROFILEActive profile nameOnly set when a profile is active. Nested ch calls inherit it as the default profile (see Profiles)

These values win over any conflicting keys in [agents.<name>].env — the agent-level env is applied first, then codeherd's values are stamped on top.

Example: sandbox an agent with ai-jail while keeping git operations working:

[agents.claude-sandboxed]
cmd = "ai-jail"args = ["--rw-map", "$CODEHERD_WORKTREE_PATH", "--rw-map", "$CODEHERD_CLONE_DIR", "--", "claude"]

Lifecycle

When you create a worktree and start a session, codeherd runs through a five-step lifecycle. Each step has optional pre/post hooks.

 Clone ──> Worktree ──> File Copy ──> Template Processing ──> Session Start
│ │ │ │ │ │ │ │ │ │
pre post pre post pre post pre post pre post

File Copy

The files list in project config copies files into new worktrees. This is useful for shared configuration (editor rules, prompt files, env configs) that should exist in every worktree but isn't tracked in git.

Entry formatSourceDestination
"CLAUDE.md"Clone dir / CLAUDE.mdWorktree / CLAUDE.md
"src/config.json"Clone dir / src/config.jsonWorktree / src/config.json
"~/.config/prompts/safety.md:RULES.md"~/.config/prompts/safety.mdWorktree / RULES.md
"/absolute/path/file.txt:subdir/file.txt"/absolute/path/file.txtWorktree / subdir/file.txt

Relative paths resolve from the clone directory. Absolute paths and ~/ paths copy from the filesystem. Intermediate directories are created automatically.

Template Processing

After files are copied, codeherd scans the worktree for .herd files and renders them using Go's text/template engine. The rendered output is written as a sibling file without the .herd suffix:

  • .env.herd renders to .env
  • docker-compose.yml.herd renders to docker-compose.yml
  • nginx.conf.herd renders to nginx.conf

This is how you generate per-worktree configuration with unique ports and branch-specific values.

Template Variables

Templates receive a context object with these fields:

VariableTypeDescriptionExample value
.ProjectstringProject name from configmyapp
.BranchstringBranch namefeature
.WorktreePathstringAbsolute path to the worktree/home/user/projects/github.com/user/myapp__worktrees/feature
.SessionNamestringDerived session namemyapp-feature

Template Functions

FunctionDescriptionExample
port "name"Deterministic port (10000-59999) derived from project + branch + name. Same inputs always produce the same port, different branches get different ports.{{ port "http" }}
env "VAR" "default"Read an environment variable, with an optional fallback value.{{ env "API_KEY" "dev-key" }}

Example: .env.herd

# .env.herd — place this in your repo or copy it via the files list
APP_PORT={{ port "http" }}
GRPC_PORT={{ port "grpc" }}
DEBUG_PORT={{ port "debug" }}
DATABASE_URL=postgres://localhost:5432/{{ .Project }}_{{ .Branch }}
API_KEY={{ env "API_KEY" "dev-key-for-local" }}
SESSION={{ .SessionName }}

Running ch create worktree myapp feature renders this to .env:

# .env (generated)
APP_PORT=34521
GRPC_PORT=18973
DEBUG_PORT=42810
DATABASE_URL=postgres://localhost:5432/myapp_feature
API_KEY=dev-key-for-local
SESSION=myapp-feature

Every worktree gets unique ports. The feature branch and fix-123 branch will never collide.

Example: docker-compose.yml.herd

# docker-compose.yml.herdservices:
postgres:
image: postgres:16ports:
- "{{ port "postgres" }}:5432"environment:
POSTGRES_DB: {{ .Project }}_{{ .Branch }}POSTGRES_PASSWORD: {{ env "PG_PASSWORD" "devpass" }}redis:
image: redis:7ports:
- "{{ port "redis" }}:6379"

Hooks

Hooks are shell commands that run at each lifecycle step. Configure them per-project:

[projects.myapp.hooks]
pre-clone = "echo preparing to clone"post-clone = "make deps"pre-worktree = "echo creating worktree"post-worktree = "npm install"pre-copy = "echo copying files"post-copy = "chmod 600 .env"pre-template = "vault read secret/myapp > .secrets"post-template = "echo templates rendered"pre-session = "docker compose up -d"post-session = "curl -s https://hooks.example.com/started"

Hooks receive context as environment variables (CODEHERD_PROJECT, CODEHERD_BRANCH, CODEHERD_WORKTREE_PATH, etc.). A non-zero exit code stops the workflow. Omit a hook to skip it.

See docs/hooks.md for the full reference including environment variables per step, working directory rules, and error handling.

Install

Via mise

mise use github:xico42/codeherd@latest

Once codeherd lands in the mise official registry, this becomes mise use codeherd@latest.

Manual

Download the appropriate archive from the latest release, extract, and place ch on your PATH. Each release ships archives for linux and darwin on amd64 and arm64, with sigstore signatures and a checksums.txt.

From source

Requires Go 1.22+, git, and tmux.

make install # builds and installs to ~/.local/bin/ch

Shell Completion

ch ships dynamic completion for agents, projects, profiles, and worktree branches. Cobra generates per-shell scripts via ch completion <shell>.

# zsh — ensure the dir is on your $fpath, then reload
ch completion zsh >"${fpath[1]}/_ch"# bash
ch completion bash | sudo tee /etc/bash_completion.d/ch > /dev/null
# fish
ch completion fish >~/.config/fish/completions/ch.fish

Completion respects the active profile: inside a profile-mode session ($CODEHERD_PROFILE set) or with -p <profile>, branch suggestions come from that profile's worktrees.

Quick Start

# Configure a project (edit ~/.config/codeherd/config.toml)# See the Configuration section above for the full config format# Clone the project
ch clone project myapp
# Create a worktree and start a session
ch create session myapp feature --agent claude --attach

Or use the interactive TUI:

ch

Commands

CommandDescription
chLaunch the TUI dashboard
ch list projectList configured projects
ch show project <name>Show project details
ch clone project <name>Clone a project
ch list worktreeList all worktrees
ch create worktree <project> <branch>Create a worktree
ch delete worktree <project> <branch>Delete a worktree
ch list sessionList active sessions
ch create session <project> <branch>Start an agent session (use --shell for a plain shell)
ch attach session <project> <branch>Attach to a session
ch show session <project> <branch>Show session details
ch delete session <project> <branch>Stop a session
ch run <agent> [-- <args>]Run a registered agent in the current shell; args after -- are forwarded to it
ch versionPrint the installed version

Development

make build # build ./ch binary
make test# run unit tests
make lint # run linter
make check # coverage (80%+) + integration tests + lint + build

Documentation

About

Like a shepherd, but for coding agents :)

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages