No description
  • Rust 93.7%
  • Shell 5.2%
  • Dockerfile 1.1%
Find a file
Quik2007 cbf61da06c
All checks were successful
ci / rust (push) Successful in 5m4s
ci / chart (push) Has been skipped
ci / runner-image (push) Has been skipped
ci / images (., images/cli/Containerfile, cli) (push) Successful in 2m33s
ci / images (., images/controller/Containerfile, controller) (push) Successful in 3m31s
ci / images (., images/gateway/Containerfile, gateway) (push) Successful in 3m33s
ci: nest claudify images under repo (qwolff/claudify/{controller,gateway,runner})
Publish container images as nested packages under the claudify repo
instead of flat qwolff/claudify-* so they auto-associate with the repo.
Helm OCI chart + clone URLs unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:24:29 +02:00
.claude chore: gitignore .claude/ harness state 2026-04-28 20:00:59 +02:00
.forgejo/workflows ci: nest claudify images under repo (qwolff/claudify/{controller,gateway,runner}) 2026-05-31 14:24:29 +02:00
crates chore: migrate git host to code.podesta.ai 2026-05-31 14:14:30 +02:00
deploy/chart ci: nest claudify images under repo (qwolff/claudify/{controller,gateway,runner}) 2026-05-31 14:24:29 +02:00
docs ci: nest claudify images under repo (qwolff/claudify/{controller,gateway,runner}) 2026-05-31 14:24:29 +02:00
images ci: nest claudify images under repo (qwolff/claudify/{controller,gateway,runner}) 2026-05-31 14:24:29 +02:00
scripts dev: persistent kind cluster + scriptable SSH attach debugger 2026-05-07 17:41:10 +02:00
.gitignore chore: gitignore .claude/ harness state 2026-04-28 20:00:59 +02:00
Cargo.lock release: 0.6.21 — kube-rs 0.99, bundle all 5 plugins, doc cleanup 2026-05-14 17:15:30 +02:00
Cargo.toml release: 0.6.21 — kube-rs 0.99, bundle all 5 plugins, doc cleanup 2026-05-14 17:15:30 +02:00
README.md chore: migrate git host to code.podesta.ai 2026-05-31 14:14:30 +02:00
rust-toolchain.toml release: 0.6.10 — bump rust toolchain pin 1.85 → 1.88 2026-05-13 21:34:54 +02:00

Claudify

Collaborative Claude Code multiplexer. A small team runs one Claudify deployment on Kubernetes; team members attach from their laptops to per-session pods that hold a long-running Claude Code instance plus the project worktree. Credentials, project repos, and runtime live in the cluster; the client is thin (one SSH connection for the PTY, one HTTPS bearer for the control plane).

Status: v0.6.x — actively used internally. CLI is stable; CRDs are versioned v1alpha1. See docs/deployment-readiness.md for what's proven end-to-end.

What it does

  • One session pod per session. Claude Code runs inside dtach, a single-PTY detach/reattach wrapper. Multiple teammates can attach concurrently and share one canvas; killing the SSH connection leaves Claude running.
  • Projects, accounts, settings as CRDs. A project registers a git repo, an account holds per-engineer Claude OAuth + git SSH + Forgejo MCP credentials, a settings CR carries shared dotfiles/agents/plugins/mcp.json. Sessions pick one of each at create time.
  • Per-session resource limits. Each session pod has RAM/CPU caps so one runaway agent can't starve the cluster.
  • SSH pubkey auth. A flat claudify-trusted-keys ConfigMap lists all allowed clients; anyone listed can do anything. Revocation = remove the key.
  • HTTPS control plane. Login signs a challenge with the user's SSH key (SSHSIG); the server returns a 15-minute bearer used for the rest of the session.

Install the CLI

brew install public/brew-tap/claudify

Shell completions for fish / bash / zsh are installed automatically.

From source

cargo install --git ssh://git@code.podesta.ai/qwolff/claudify.git claudify-cli
# or, from a clone:
git clone ssh://git@code.podesta.ai/qwolff/claudify.git && cd claudify
cargo install --path crates/cli

cargo install does not install shell completions automatically — generate them:

# fish (current shell):       claudify completions fish | source
# fish (persistent):          claudify completions fish > ~/.config/fish/completions/claudify.fish
# bash:                       claudify completions bash > /usr/local/etc/bash_completion.d/claudify
# zsh:                        claudify completions zsh  > "${fpath[1]}/_claudify"

First-run setup

claudify setup

Two questions, both with defaults — hit Enter to accept. The command writes ~/.config/claudify/config.toml, runs a login (signs a challenge with your SSH key), and verifies access with a list-sessions probe. After it succeeds:

claudify status       # cluster health overview
claudify list         # your sessions
claudify create --project PROJECT --account ACCOUNT --settings SETTINGS --name NAME --attach

--yes accepts all defaults non-interactively (for dev-box bootstrapping); --skip-login saves the config but doesn't try to talk to the gateway (useful when configuring before a port-forward is up).

Env vars override the config file for one-off invocations:

Variable Default
CLAUDIFY_API https://claudify.tu-po.com
CLAUDIFY_SSH_HOST host part of CLAUDIFY_API
CLAUDIFY_SSH_PORT 2222
CLAUDIFY_KEY auto-detect ~/.ssh/id_ed25519, then id_rsa

Command reference

Command What it does
claudify setup First-run interactive config + login
claudify login Re-sign a fresh bearer (auto on 401)
claudify status Cluster health + resource counts (cached, fast)
claudify list (ls, ps) List sessions with phase / project / attach count
claudify top Live cpu/memory per session (5s refresh)
claudify create --project P --account A --settings S --name N [--attach] New session; --attach auto-attaches once Running
claudify attach <sess> Attach to the long-running Claude PTY
claudify shell <sess> (sh, bash) Fresh bash -l in the same pod — escape hatch from Claude
claudify exec <sess> -- <cmd> (run) One-shot non-interactive command; pipes work, exit code propagates
claudify logs <sess> [--tail N --previous] Pod kubelet logs
claudify end <sess…> (rm, stop, delete) Delete session(s); idempotent, accepts multiple names
claudify project {list,get,apply,rm} Manage project CRs (the git-repo defs)
claudify account {list,get,apply,rm} Manage account CRs (Claude+git+fgj creds)
claudify settings {list,get,apply,rm} Manage settings CRs (shared dotfiles/agents)
claudify whoami Current bearer's key fingerprint
claudify completions {fish,bash,zsh} Print a shell completion script

All session-name positional args TAB-complete from a live name list (5s cache; the lookup runs in well under 300 ms over WAN). Same for --project, --account, --settings flag values.

See docs/cli.md for per-command details (flag matrix, examples, exit codes).

For operators (deploying the cluster side)

See docs/setup.md. Short version:

  1. Helm-install the chart pointing at a StorageClass on your cluster (--set rwx.storageClassName=...).
  2. Generate + install the gateway's SSH host key as a Secret (or let the chart auto-generate on first install).
  3. Add at least one SSH pubkey to claudify-trusted-keys ConfigMap.
  4. Create projects / accounts / settings via the CLI.

Repo layout

claudify/
├── crates/
│   ├── api/                # CRD types, HTTP wire types, SSHSIG canonical payload
│   ├── controller/         # reconciles ClaudifyProject/Account/Settings/Session CRDs
│   ├── gateway/            # SSH server (:2222) + HTTPS control plane (:8443)
│   ├── cli/                # `claudify` CLI
│   └── integration-tests/  # kind-based smoke scenarios (#[ignore] by default)
├── images/
│   ├── controller/         # distroless, static musl
│   ├── gateway/            # distroless, static musl
│   ├── cli/                # distroless, static musl
│   └── runner/             # session pod image (extends public/runner-image)
├── deploy/chart/           # Helm chart
└── docs/
    ├── setup.md            # operator install guide
    ├── cli.md              # full CLI reference
    ├── architecture.md     # one-page architecture
    └── deployment-readiness.md

Development

cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check

helm lint deploy/chart
helm template claudify deploy/chart > /tmp/render.yaml   # render to inspect

Integration tests against a real kind cluster need kind, helm, kubectl, podman on PATH and run with --ignored:

cargo test -p claudify-integration-tests -- --ignored --test-threads=1

Not run in CI (runners are unprivileged; kind needs nested container runtime).

Releasing

Single semver across the whole monorepo. To cut a release:

  1. Bump version = "X.Y.Z" in all crates/*/Cargo.toml, run cargo update -p claudify-* …, commit as release: X.Y.Z — <one-line>.
  2. Tag vX.Y.Z, push commit + tag.
  3. CI builds + pushes the gateway/controller/cli/runner images and the Helm chart on tag.
  4. Bump the brew formula in public/brew-tap.

Known limitations

  • Multi-attach pair-programming. Concurrent keyboard input interleaves byte-by-byte; use voice to coordinate, or spin separate sessions.
  • Credential rotation requires recreating sessions. Updating an account/settings Secret does not propagate into running pods.
  • Trusting-group threat model. Any user with a trusted key can read every Secret the gateway can read, create sessions under any account, and exec into any session pod. Don't deploy as multi-tenant SaaS.

License

Apache-2.0.