Skip to content
siyamsarkerPublic

About

Self-hosted monitoring and policy layer for teams sharing a Claude Team subscription: track Claude Code usage per person, device and model, turn models on or off, suspend access, detect tampering, and audit every admin action.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Codeward

See and control how Claude Code is used across your organization.

Go Docker Devices License

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.ai logins 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


1. Quick start

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 link

In .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.

2. Deploy

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.

3. Enroll devices

  1. 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.

  2. Staff member (needs sudo; Claude Code already installed):

    curl -fsSL https://codeward.example.com/install.sh | sudo bash -s -- CW-ENR-xxxxxxxxxxxx

    The installer downloads and checksum-verifies the codeward binary for their OS and CPU, registers the device, writes the managed settings file and starts the background service.

  3. 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.

4. Use the dashboard

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.

5. Operate

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 start

Restore 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

6. How it works

 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
  1. The install command puts the codeward binary on the device, registers it, and writes Claude Code's official managed settings file.
  2. 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.
  3. A small hook runs before every prompt and model switch, so suspensions and disabled models apply even to sessions that are already open.
  4. 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.

7. Configuration

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.

8. Development

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 link

The 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

9. Security and limits

  • 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_TOKEN or apiKeyHelper; 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:

  1. 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.
  2. 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.
  3. Unenrolled devices are invisible. Compare against Claude Code's analytics on claude.ai.
  4. Some login paths can't be blocked by Claude Code itself (a Console login, claude setup-token, cloud-provider credentials). Codeward raises a foreign_org alert when it sees them.
  5. Costs are Claude Code's list-price estimate, not your invoice.
  6. Backups and device updates are manual (see Operate; re-run the install command to update a device).

10. Troubleshooting

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

Contributing

  1. Fork, branch, and change one thing at a time, with tests.
  2. Before opening a pull request, run make lint test smoke and cd web && npx tsc -b && npm test.
  3. Keep dependencies minimal; discuss any new one in the pull request.
  4. Write commit messages that say what changed and why, in plain English.

License

MIT. Copyright (c) 2026 Siyam Sarker.

About

Self-hosted monitoring and policy layer for teams sharing a Claude Team subscription: track Claude Code usage per person, device and model, turn models on or off, suspend access, detect tampering, and audit every admin action.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages