Codeward is a self-hosted monitoring and policy layer for teams sharing a Claude Team subscription. It never proxies traffic and never touches API credentials: if the server is down, Claude Code keeps working with the last policy it received.
- See usage per person, device and model: tokens, sessions, cost estimate, activity.
- Control models: turn Fable, Opus, Sonnet or Haiku on or off for everyone, one person or one device.
- Enforce company login: enrolled devices refuse
claude.ailogins from outside your organization. - Suspend a person or device, and detect tampering with Codeward's files.
- Audit every admin action, with before/after state.
Contents: 1. Quick start · 2. Deploy · 3. Enroll devices · 4. Use the dashboard · 5. Operate · 6. How it works · 7. Configuration · 8. Development · 9. Security and limits · 10. Troubleshooting · Contributing · License
Requires Docker with Compose v2 and your Claude Team organization ID (claude.ai → Admin settings →
Organization).
git clone https://github.com/siyamsarker/codeward.git && cd codeward
cp .env.example .env # then set the three values below
docker compose up -d --build
docker compose logs codeward # prints the one-time owner setup linkIn .env set CODEWARD_PUBLIC_URL (http://localhost:8080 for a trial), CODEWARD_ORG_ID and
CODEWARD_OWNER_EMAIL.
Open the setup link, choose a password, scan the QR code with an authenticator app and enter the 6-digit code to confirm it. Two-factor authentication is mandatory for every admin. Then sign in.
Compose publishes the app only on 127.0.0.1:8080, over plain HTTP. For production, run a TLS reverse
proxy on the same host and set CODEWARD_PUBLIC_URL to its https:// address (login cookies are Secure,
so browsers won't send them over plain http except on localhost).
The proxy must:
- forward to
http://127.0.0.1:<CODEWARD_HOST_PORT>; - set
X-Forwarded-For, so real client IPs reach the logs; - allow responses to stay open 75 seconds or more: devices long-poll for policy changes for up to 55 s.
# Caddy: certificates and X-Forwarded-For are automatic
codeward.example.com {
reverse_proxy 127.0.0.1:8080
}# nginx (TLS certificate configuration omitted)
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120s;
}The server trusts X-Forwarded-For only from the Docker network gateway (172.29.0.1), which is how the
proxy reaches the container. If 172.29.0.0/24 collides with your network, set CODEWARD_SUBNET and
CODEWARD_GATEWAY in .env.
The image is a small distroless build that runs as a non-root user with a read-only filesystem and no Linux capabilities. Migrations run automatically on every start.
-
Admin: open Settings → Enrollment tokens, enter the person's e-mail and click Create. Copy the install command shown; it appears once. Tokens are single-use, tied to that e-mail, and expire after 7 days.
-
Staff member (needs
sudo; Claude Code already installed):curl -fsSL https://codeward.example.com/install.sh | sudo bash -s -- CW-ENR-xxxxxxxxxxxxThe installer downloads and checksum-verifies the
codewardbinary for their OS and CPU, registers the device, writes the managed settings file and starts the background service. -
Staff member: quit and restart Claude Code (or reload the VS Code window), then check:
sudo codeward status sudo codeward doctor
Re-running the install command on an enrolled machine is safe; it replaces the old enrollment. To remove
Codeward from a device: sudo codeward uninstall.
Open your CODEWARD_PUBLIC_URL. Data refreshes automatically and pauses while the tab is hidden.
| Page | What it's for |
|---|---|
| Overview | Tokens, cost, requests, online devices and open alerts for today, 7 or 30 days; usage by model; top accounts and devices; hourly activity |
| Accounts, Devices | Search; per-item usage, policy, IP history and linked records; suspend, resume, revoke (devices) |
| Sessions | Each session's timeline of requests and prompts; filter by device or account |
| Prompts | Full-text search of redacted prompt text (owners and admins only; searches are audit-logged) |
| Models | Turn each model family on or off globally, with a live "devices updated" counter |
| Alerts | Review and acknowledge tamper, foreign_org and disabled_model_used alerts |
| Audit | Every admin action with before/after state |
| Settings | Data retention, enrollment tokens, and admins (invite, change role, disable, reset 2FA) |
Roles: owner can do everything, including managing admins and retention; admin manages policies, devices, accounts, alerts and enrollment tokens and can read prompts; viewer is read-only and cannot read prompt text. The server enforces roles; the UI only hides what you can't do.
Lost phone? An owner opens Settings → Admins and clicks Reset 2FA on that person's row. They are signed out everywhere and, at their next sign-in, their password leads to a new QR code and code check. Owners can't reset themselves, so keep at least two owners.
| Task | Command |
|---|---|
| Follow the log | docker compose logs -f codeward (or make logs) |
| Stop, keeping data | docker compose down (or make down) |
| Upgrade | git pull && docker compose up -d --build |
| Issue a new owner setup link | docker compose exec codeward /codeward-server create-owner --email <address> |
| Change the organization ID | docker compose exec codeward /codeward-server set-org-id --org-id <uuid> |
| Delete all data | docker compose down -v |
Data lives in the Docker volume codeward-data. Don't copy the database while the server runs (its
write-ahead log makes that unsafe). Back up with a brief stop:
docker compose stop
docker run --rm -v codeward-data:/data:ro -v "$PWD":/backup alpine \
tar czf /backup/codeward-data-$(date +%F).tar.gz -C /data .
docker compose startRestore into an empty volume, with the container stopped:
docker run --rm -v codeward-data:/data -v "$PWD":/backup alpine \
tar xzf /backup/codeward-data-YYYY-MM-DD.tar.gz -C /data Developer machine Your server (Docker)
┌──────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ Claude Code (CLI, VS Code) │ │ your TLS reverse proxy │
│ ▲ reads managed settings │ │ ▼ http://127.0.0.1:8080 │
│ codeward agent (root service) ───────┼─policy──►│ codeward-server (one Go binary) │
│ ▲ writes settings, detects tamper │ + usage │ dashboard · admin API · telemetry │
│ codeward hook (before each prompt) │ │ SQLite on a volume │
└──────────────────────────────────────┘ └─────────────────────────────────────┘
Claude Code ⇄ Anthropic: direct and unchanged
- The install command puts the
codewardbinary on the device, registers it, and writes Claude Code's official managed settings file. - Claude Code then only offers the model families you allow, only accepts your organization's login, and sends its built-in OpenTelemetry usage data to your server.
- A small hook runs before every prompt and model switch, so suspensions and disabled models apply even to sessions that are already open.
- When an admin changes a setting, each online device's agent picks it up (normally within seconds), rewrites the settings file, and Claude Code reloads it without a restart.
Nothing here uses a private Claude Code feature: only managed settings, hooks and OpenTelemetry export.
Set these in .env (annotated template: .env.example).
| Variable | Default | Purpose |
|---|---|---|
CODEWARD_PUBLIC_URL |
required | Public scheme://host[:port] (no path). Used for the admin Origin check, the telemetry endpoint pushed to devices, and the install command. |
CODEWARD_ORG_ID |
empty | Claude Team organization ID. Stored on first start only if none is stored; a different stored value is kept and a warning is logged (changing it locks out enrolled devices). |
CODEWARD_OWNER_EMAIL |
empty | Creates the first owner on first start (only if no owner exists) and logs a one-time setup link. |
CODEWARD_HOST_PORT |
8080 |
Host port on 127.0.0.1. |
CODEWARD_SUBNET, CODEWARD_GATEWAY |
172.29.0.0/24, 172.29.0.1 |
Docker network; the gateway is the one address whose X-Forwarded-For is trusted. |
VERSION |
dev |
Version label for the image and device CLI. |
The container also sets CODEWARD_DB_PATH, CODEWARD_LISTEN, CODEWARD_BINARIES_DIR and
CODEWARD_INSTALL_SCRIPT_PATH to paths inside the image, and Compose sets CODEWARD_TRUSTED_PROXY.
Needs Go 1.27+ and Node.js 22+.
make build # dashboard, then ./bin/codeward and ./bin/codeward-server
export CODEWARD_PUBLIC_URL=http://localhost:8080 CODEWARD_DB_PATH=./dev.db \
CODEWARD_ORG_ID=<team-org-id> CODEWARD_OWNER_EMAIL=you@example.com
./bin/codeward-server serve # migrates, bootstraps, prints the owner setup linkThe dashboard is embedded in the server binary at compile time, so re-run make build after changing
web/. make help lists every target.
| Check | Command |
|---|---|
| Go tests | make test |
| Format and vet | make lint |
| Dashboard tests and types | cd web && npm test && npx tsc -b |
| End-to-end smoke test (real server, fake device) | make smoke |
internal/server/static/ receives the dashboard build and is git-ignored, except for a placeholder
index.html that keeps go build and go test working on a fresh clone.
cmd/codeward/ device CLI, agent and Claude Code hook (one binary)
cmd/codeward-server/ server: serve, migrate, create-owner, set-org-id, healthcheck, version
internal/ policy · agent · hook · platform · server · store · auth
web/ dashboard (React, TypeScript, Vite, Tailwind)
deploy/ install.sh template served to devices
scripts/ smoke test and its TOTP helper
- Device secrets and session tokens are stored only as hashes; admin passwords use Argon2id.
- Every admin needs a verified authenticator; each code works once. Resets are audit-logged.
- Codeward never writes
ANTHROPIC_BASE_URL,ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKENorapiKeyHelper; a test fails the build if any code path produces them. - Settings files are written atomically. Hooks make no network calls and fail open: a Codeward bug never blocks a developer; only a real policy violation does.
- Prompt text is redacted (keys, tokens, passwords in URLs) before storage, is readable only by owners and admins, and each session view that shows it is audit-logged.
Limits to know before rolling out:
- Not yet verified on a live setup. It is covered by automated tests and the smoke test, but has not been run against a real Claude Code install with a live Team account. Pilot it on one or two machines first.
- Enforcement is client-side. A user with admin rights on their machine can remove Codeward; it detects and restores this within seconds and raises an alert, but cannot prevent it. Server-side model restriction needs an Enterprise plan.
- Unenrolled devices are invisible. Compare against Claude Code's analytics on
claude.ai. - Some login paths can't be blocked by Claude Code itself (a Console login,
claude setup-token, cloud-provider credentials). Codeward raises aforeign_orgalert when it sees them. - Costs are Claude Code's list-price estimate, not your invoice.
- Backups and device updates are manual (see Operate; re-run the install command to update a device).
| Symptom | Fix |
|---|---|
docker compose up says to set CODEWARD_PUBLIC_URL |
Create .env from .env.example |
| Container unhealthy or restarting | docker compose logs codeward: a bad URL, org ID or owner e-mail stops startup with a clear message |
| Can't sign in | Over plain http the login cookie isn't sent; use https through your proxy (localhost is exempt) |
| Every visitor has the same IP in logs | The proxy isn't sending X-Forwarded-For |
| Slow policy changes or flapping devices | Raise the proxy's read timeout to 75 s or more |
| Lost the owner setup link | create-owner command in Operate (works until the owner has signed in once) |
| Someone lost their phone | An owner uses Reset 2FA (Use the dashboard) |
| The only owner lost their phone | Can't be recovered from the command line; keep two owners |
| Enrollment fails with 409 | Organization ID not set: set CODEWARD_ORG_ID or run set-org-id |
| A disabled model still works | sudo codeward status (is the policy version current?), then sudo codeward doctor |
| Device offline | systemctl status codeward (Linux) or sudo launchctl print system/com.codeward.agent (macOS) |
| No usage data | Restart Claude Code once after enrollment |
| Settings file looks wrong | sudo codeward repair |
- Fork, branch, and change one thing at a time, with tests.
- Before opening a pull request, run
make lint test smokeandcd web && npx tsc -b && npm test. - Keep dependencies minimal; discuss any new one in the pull request.
- Write commit messages that say what changed and why, in plain English.
MIT. Copyright (c) 2026 Siyam Sarker.