No description
  • Rust 89.1%
  • Shell 10.1%
  • Dockerfile 0.8%
Find a file
Quik2007 0069ba0258
All checks were successful
ci / images (., images/gateway/Containerfile, gateway) (push) Successful in 3m30s
ci / chart (push) Successful in 22s
ci / images (., images/cli/Containerfile, cli) (push) Successful in 2m35s
ci / rust (push) Successful in 1m36s
ci / runner-image (push) Successful in 2m10s
ci / images (., images/controller/Containerfile, controller) (push) Successful in 3m50s
style: cargo fmt
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 13:29:46 +02:00
.claude chore: gitignore .claude/ harness state 2026-04-28 20:00:59 +02:00
.forgejo/workflows ci: enable workflow_dispatch for chart + runner-image; fix chart push org 2026-05-31 17:13:47 +02:00
crates style: cargo fmt 2026-07-18 13:29:46 +02:00
deploy/chart release: 0.7.1 — session pods survive node disk pressure and build Rust 2026-07-18 13:26:51 +02:00
docs docs: plan for presence, sharing, and CLI guidance 2026-07-17 14:46:45 +02:00
images fix(runner): put RUSTUP_HOME on the PVC so pinned toolchains install 2026-07-18 13:26:03 +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.7.1 — session pods survive node disk pressure and build Rust 2026-07-18 13:26:51 +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 docs: describe session worktrees, cp, and teleport 2026-07-17 10:30:11 +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

CLAUDIFY_KEY picks the private key for everything claudify does over SSH: login's SSHSIG challenge signature and the actual ssh connections attach/shell/exec/cp/teleport open (passed as -i <key> -o IdentitiesOnly=yes, so ssh can't silently authenticate as some other key from your agent instead). Set it whenever you have more than one local key, so the key that signs the login challenge is guaranteed to be the same one the SSH connections authenticate with. If unset, login auto-detects ~/.ssh/id_ed25519, then id_rsa; the ssh connections fall back to ssh's own default identity discovery, which isn't guaranteed to pick the same key.

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 cp [-r] <src>… <sess>:<dest> (and the reverse) Copy files to/from a session pod over the claudify-cp SSH subsystem (tar|zstd stream) — the only way to get a file into a pod, since the gateway has no exec channel or sftp for scp/rsync
claudify teleport [--name N --project P --account A --settings S] [--with-artifacts] Move the repo you're standing in onto a fresh session: unpushed commits, uncommitted/untracked files, allowlisted secrets, and the Claude conversation about it — resumed, not started fresh
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.