Skip to content

Architecture

Both CI flavors drive the same rootless, unprivileged sandbox container, which emits OTLP telemetry to a collector sidecar and on to Elastic.

The sandbox is the job container the runner starts — not a wrapper this project adds. The GitLab Runner runs the pipeline job inside the claude-agent image (its container/Kubernetes executor), and claude runs directly as that container's process. What provides the container varies by where the runner lives: rootless Podman on a CI runner, or the Kubernetes CRI (e.g. CRI-O) when the runner is a self-hosted one on OpenShift — where the restricted-v2 SCC enforces the non-root, no-escalation boundary. Claude is never wrapped in a nested podman run.

The diagram traces one run. The CI platform starts a Kubernetes Pod / CI job holding two sibling containers that share localhost: the agent sandbox (the claude-agent image — where the Claude CLI runs) and the OTel Collector sidecar. The agent emits OTLP telemetry to the sidecar, which scrubs secrets and exports to Elastic; an optional LLM Gateway sits between the agent and the creation and review models (which can be different models or vendors).

Two optional pieces are drawn dashed because they are off by default. OpenBao supplies the job's secrets (ANTHROPIC_API_KEY, the Elastic OTLP credentials, GIT_PUSH_TOKEN) via a short-lived OIDC/JWT exchange instead of native CI variables — see Secrets via OpenBao. An HTTPS_PROXY sits on the egress path: when set, claude, git, and curl route outbound traffic through a corporate proxy. localhost/127.0.0.1 are auto-added to NO_PROXY, so the in-Pod OTLP export to the sidecar is never proxied.

Nested Podman isn't drawn because it's incidental to the picture: it's used only where needed — to start the sidecar, and for podman build/run when a task builds or tests app images (rootless-in-rootless). Claude orchestrates those from the sandbox; it never runs inside them. See Nested child containers below.

Editable source

A full, editable version of this diagram lives at architecture.drawio in the repository root and can be opened with draw.io or the draw.io VS Code extension.

Components

Component Role
GitLab CI / GitHub Actions Trigger pipelines; the agent detects which one via environment variables.
Kubernetes Pod / CI job The outer unit the platform schedules — one network namespace holding the sandbox container and the collector sidecar (they reach each other over localhost).
Sandbox container (Container 1) The job container the runner starts from the claude-agent image — rootless, unprivileged. The Claude CLI runs here directly (not in a nested podman run). The runtime is rootless Podman on a CI runner, or the Kubernetes CRI (e.g. CRI-O) on a self-hosted runner on OpenShift. No Docker.
Claude CI Agent The claude process inside the sandbox — plans, edits files, makes atomic git commits, and drives Podman.
Nested child containers podman build/run inside the sandbox spawns rootless-in-rootless containers that build and run the app under test (app-test). Claude orchestrates them; it does not run inside them.
LLM Gateway (optional) Sits between the agent and the Anthropic API via ANTHROPIC_BASE_URL— adds prompt-response caching and request/response guardrails. See LLM Gateway.
HTTPS_PROXY (optional) A corporate egress proxy on the outbound path. When set, claude, git, and curl route through it (mirrored to upper/lower case, forwarded into the sidecar); localhost/127.0.0.1 are auto-added to NO_PROXY so the OTLP export is never proxied. No-op when unset.
OpenBao (optional) Vault-compatible secrets manager. The job authenticates with a short-lived OIDC/JWT and pulls ANTHROPIC_API_KEY, the Elastic OTLP credentials, and GIT_PUSH_TOKEN at run time — no static secret stored in CI. No-op unless BAO_ADDR is set. See Secrets via OpenBao.
OTel Collector sidecar (Container 2) Sibling container in the same Pod; receives OTLP events at localhost:4318, scrubs secrets, exports to Elastic.
Elastic Stores telemetry for audit.

Personalities

The same sandbox image runs in one of two personalities— the CI trigger that started the pipeline decides which one. See CLAUDE.md for the authoritative definition.

Personality Trigger Capability Token scope
Agent (read-write) A @claude … comment on a PR or issue Applies a fix, commits, opens a new PR/MR branch Repository write
Advisor (read-only) A PR/MR open or synchronize event Lints, tests, flags bugs, posts a review comment— never mutates Repository read + review comments

The boundary is enforced by the token, not by trust

The Advisor receives no write token— mutation is impossible even if the prompt is subverted. The Agent's write scope is confined to new branches; it never pushes to the default branch.