Skip to content

engine

View on pkg.go.dev

import "github.com/thesimonho/warden/engine"

Package engine wraps the Docker Engine API for discovering and managing Claude Code project containers.

ContainerHomeDir is the home directory for ContainerUser inside containers.

const ContainerHomeDir = constants.ContainerHomeDir

ContainerUser is the non-root user inside project containers.

const ContainerUser = constants.ContainerUser

DefaultDisconnectKey is the default key to disconnect from a terminal.

const DefaultDisconnectKey = "ctrl+\\"

ErrContainerExists is returned when an existing Warden-managed container occupies the requested name. The caller should prompt for confirmation and retry with ForceReplace to remove it.

var ErrContainerExists = fmt.Errorf("a Warden container already exists with this name")

ErrNameTaken is returned when a container with the requested name already exists and is not a Warden-managed container.

var ErrNameTaken = fmt.Errorf("container name already in use")

TmuxSessionName returns the tmux session name for a worktree.

var TmuxSessionName = constants.TmuxSessionName

TmuxShellSessionName returns the tmux session name for a worktree’s auxiliary bash-shell session, backing the Terminal tab in the UI.

var TmuxShellSessionName = constants.TmuxShellSessionName

func ContainerWorkspaceDir(projectName string) string

ContainerWorkspaceDir computes the container-side workspace path for a project. New containers mount at /home/warden/<name> to give each project a unique path in the agent’s config file (which keys cost data by workspace path).

func DetectStaleMounts(original, current []api.Mount) []string

DetectStaleMounts re-resolves the original (pre-resolution) mount specs and compares the result with the container’s current resolved mounts. Returns a list of container paths where the fresh resolution differs from what the container currently has.

Only mount paths produced by the fresh resolution are checked. Extra mounts in the container that aren’t derived from the original specs (e.g. socket mounts, cache volumes) are ignored — they aren’t tracked in original_mounts and shouldn’t trigger stale mount detection.

func DisconnectKeyToByte(key string) byte

DisconnectKeyToByte converts a “ctrl+<char>” string to its control character byte. Returns 0 if the format is invalid. Supports:

  • ctrl+a through ctrl+z (0x01–0x1a)
  • ctrl+[ (0x1b, ESC) — not recommended
  • ctrl+\ (0x1c, default)
  • ctrl+] (0x1d)
  • ctrl+^ (0x1e)
  • ctrl+_ (0x1f)

func IsStaleMountsError(err error) bool

IsStaleMountsError reports whether err is a StaleMountsError.

func IsValidWorktreeID(id string) bool

IsValidWorktreeID validates worktree IDs (alphanumeric start, then alphanumeric/hyphens/underscores/dots).

func ProjectCostFromContainerStatuses(statuses map[string]*agent.Status, workspacePrefix string) float64

ProjectCostFromContainerStatuses sums cost only for entries whose path starts with the given workspace prefix. This filters out host project entries that appear in the bind-mounted .claude.json but don’t belong to this container.

func ProjectID(hostPath string) (string, error)

ProjectID computes a deterministic project identifier from a host directory path. The ID is the first 12 hex characters of the SHA-256 hash of the cleaned, absolute path. Trailing slashes are stripped before hashing.

The caller is responsible for resolving symlinks (via filepath.EvalSymlinks) before calling this function — symlink resolution requires filesystem access which is inappropriate at this level.

func ProjectIDFromURL(cloneURL string) (string, error)

ProjectIDFromURL computes a deterministic project identifier from a git clone URL. The URL is normalized (lowercase host, stripped trailing .git and slash) before hashing so that equivalent URLs produce the same ID.

func ResolveProjectID(hostPath, cloneURL string) (string, error)

ResolveProjectID computes the deterministic project ID from either a clone URL (remote project) or an absolute host path (local project).

func ValidProjectID(id string) bool

ValidProjectID reports whether id is a valid project identifier (exactly 12 lowercase hex characters).

func WorkspaceVolumeName(containerName string) string

WorkspaceVolumeName returns the Docker volume name for a remote project’s persistent workspace.

AgentCostResult holds cost and billing type from a single config read.

type AgentCostResult struct {
TotalCost float64
IsEstimated bool
// Sessions holds per-session cost breakdown, keyed by session ID.
// Used for session-keyed DB persistence.
Sessions []SessionCost
}

AgentStatus represents whether the agent CLI is actively running inside a container.

type AgentStatus string

const (
// AgentStatusIdle means no agent process is running.
AgentStatusIdle AgentStatus = "idle"
// AgentStatusWorking means an agent process is currently active.
AgentStatusWorking AgentStatus = "working"
// AgentStatusUnknown means the status could not be determined.
AgentStatusUnknown AgentStatus = "unknown"
)

Client defines the interface for interacting with Docker containers. All methods accept a context for cancellation and timeout control.

type Client interface {
// ListProjects returns projects for the given container names, enriched with
// live Docker state. Names not found in Docker are returned with HasContainer: false.
ListProjects(ctx context.Context, names []string) ([]Project, error)
// StopProject gracefully stops the container with the given ID.
StopProject(ctx context.Context, id string) error
// RestartProject restarts the container with the given ID. originalMounts
// are the pre-symlink-resolution mount specs from the DB, used to detect
// stale bind mounts before restarting. Network isolation is re-applied
// separately via ReapplyNetworkIsolation after the restart returns.
RestartProject(ctx context.Context, id string, originalMounts []api.Mount) error
// ReapplyNetworkIsolation waits for installs and re-applies iptables
// rules after a container restart. No-op for full network mode.
ReapplyNetworkIsolation(ctx context.Context, id, networkMode string, allowedDomains []string)
// CheckContainerName reports whether a container name is available.
CheckContainerName(ctx context.Context, name string) api.CheckNameResult
// CreateContainer creates and starts a new project container.
CreateContainer(ctx context.Context, req api.CreateContainerRequest) (string, error)
// DeleteContainer stops and removes a container.
DeleteContainer(ctx context.Context, id string) error
// CleanupEventDir removes the bind-mounted event directory for a container.
CleanupEventDir(containerName string)
// InspectContainer returns the editable configuration of a container.
InspectContainer(ctx context.Context, id string) (*api.ContainerConfig, error)
// ContainerIP returns the bridge network IP address of a running container.
ContainerIP(ctx context.Context, containerID string) (string, error)
// RenameContainer changes the name of an existing container without recreation.
RenameContainer(ctx context.Context, id string, newName string) error
// ReloadAllowedDomains re-runs the network isolation script inside a
// running container to update the allowed domain list without recreation.
// Uses privileged docker exec since the container lacks NET_ADMIN.
ReloadAllowedDomains(ctx context.Context, containerID string, domains []string) error
// WaitForInstalls polls for the install-complete marker written by the
// container entrypoint after agent CLI and runtime installs finish.
WaitForInstalls(ctx context.Context, containerID string) error
// ApplyNetworkIsolation runs the network isolation script via privileged
// docker exec. Used after container start/restart to set up iptables
// without granting NET_ADMIN to the container.
ApplyNetworkIsolation(ctx context.Context, containerID, mode string, domains []string) error
// RecreateContainer replaces a stopped container with a new one using updated config.
// Returns the new container ID.
RecreateContainer(ctx context.Context, id string, req api.CreateContainerRequest) (string, error)
// ListWorktrees returns all worktrees for the given container with their terminal state.
// When skipEnrich is true, the expensive batch docker exec for terminal state is skipped
// (the caller is expected to overlay state from the event bus store instead).
ListWorktrees(ctx context.Context, containerID string, skipEnrich bool) ([]Worktree, error)
// CreateWorktree creates a new git worktree inside the container and connects a terminal.
// When skipPermissions is true, Claude Code runs with --dangerously-skip-permissions.
// Returns the worktree ID on success.
CreateWorktree(ctx context.Context, containerID, name string, skipPermissions bool) (string, error)
// ConnectTerminal starts a terminal for a worktree inside the container.
// When skipPermissions is true, Claude Code runs with --dangerously-skip-permissions.
// Returns the worktree ID on success.
ConnectTerminal(ctx context.Context, containerID, worktreeID string, skipPermissions bool) (string, error)
// DisconnectTerminal pushes a disconnect event and cleans up tracking state.
// The tmux session (and Claude/bash) continues running in the background.
DisconnectTerminal(ctx context.Context, containerID, worktreeID string) error
// KillWorktreeProcess kills the tmux session for a worktree, destroying
// the process entirely. The git worktree directory on disk is preserved.
KillWorktreeProcess(ctx context.Context, containerID, worktreeID string) error
// SendWorktreeInput sends text to a worktree's tmux pane. Uses literal mode
// to prevent key-name interpretation. If pressEnter is true, sends Enter after the text.
SendWorktreeInput(ctx context.Context, containerID, worktreeID, text string, pressEnter bool) error
// ResetWorktree clears all history for a worktree without removing it.
// Kills the process, clears JSONL session files, and removes terminal
// tracking state.
ResetWorktree(ctx context.Context, containerID, worktreeID string) error
// RemoveWorktree fully removes a worktree: kills any running processes,
// runs `git worktree remove`, and cleans up tracking state. Cannot remove
// the "main" worktree.
RemoveWorktree(ctx context.Context, containerID, worktreeID string) error
// CleanupOrphanedWorktrees removes worktree directories from .claude/worktrees/
// that are not tracked by git. Returns the list of removed worktree IDs.
CleanupOrphanedWorktrees(ctx context.Context, containerID string) ([]string, error)
// AllowBridgePortInContainer adds an iptables rule inside the container
// allowing outbound TCP to the gateway on the given port. Required for
// restricted/none network modes. No-op if iptables isn't set up.
AllowBridgePortInContainer(ctx context.Context, containerID string, port int) error
// ExecSocatBridge starts a socat process inside the container that
// creates a Unix socket and forwards connections to the host TCP bridge.
ExecSocatBridge(ctx context.Context, containerID, containerPath string, port int) error
// ExecPortForward creates a docker exec with socat STDIO forwarding to
// a container port. Returns a bidirectional stream. Used by the port
// bridge on Docker Desktop where bridge IPs are unreachable.
ExecPortForward(ctx context.Context, containerID string, port int) (ExecStream, error)
// KillSocatBridges kills all socat bridge processes inside the container.
KillSocatBridges(ctx context.Context, containerID string) error
// SetupBridgeFirewall creates the iptables chain for bridge port rules.
// Idempotent — flushes stale rules from a previous server run. No-op on
// Docker Desktop.
SetupBridgeFirewall(ctx context.Context) error
// AddBridgeFirewallRule adds an iptables ACCEPT rule for a bridge port.
// No-op on Docker Desktop.
AddBridgeFirewallRule(ctx context.Context, port int) error
// AddBridgeFirewallRules adds iptables ACCEPT rules for multiple bridge
// ports in a single operation. No-op on Docker Desktop.
AddBridgeFirewallRules(ctx context.Context, ports []int) error
// RemoveBridgeFirewallRule removes the iptables ACCEPT rule for a bridge
// port. No-op on Docker Desktop.
RemoveBridgeFirewallRule(ctx context.Context, port int) error
// TeardownBridgeFirewall removes the iptables chain and all rules.
// Called on graceful shutdown. No-op on Docker Desktop.
TeardownBridgeFirewall(ctx context.Context) error
// ValidateInfrastructure checks whether a container has the required Warden
// terminal infrastructure installed (tmux, gosu, create-terminal.sh, etc).
// Returns true if all binaries are present, along with the list of missing items.
ValidateInfrastructure(ctx context.Context, containerID string) (bool, []string, error)
// GetWorktreeDiff returns uncommitted changes (tracked + untracked) for a
// worktree inside the container as a unified diff with per-file statistics.
GetWorktreeDiff(ctx context.Context, containerID, worktreeID string) (*api.DiffResponse, error)
// ReadAgentStatus reads the agent config file from a running container
// and returns per-project status data keyed by working directory path.
// Used as a fallback cost source when the event bus has no data.
ReadAgentStatus(ctx context.Context, containerID string) (map[string]*agent.Status, error)
// IsEstimatedCost returns true when a container's cost is estimated
// (subscription user) rather than actual API spend (API key user).
IsEstimatedCost(ctx context.Context, containerID string) bool
// ReadAgentCostAndBillingType reads the agent config once and returns
// both cost (filtered by workspace prefix) and billing type.
ReadAgentCostAndBillingType(ctx context.Context, containerID, workspacePrefix string) (*AgentCostResult, error)
// ContainerStartupHealth inspects a container's state to determine if it
// is crash-looping. When the container is restarting, reads the last lines
// of container logs to capture the error. Used by the liveness checker to
// enrich stale-heartbeat audit events with diagnostic details.
ContainerStartupHealth(ctx context.Context, containerName string) (*ContainerHealth, error)
// CopyFileToContainer writes a single file into a running container.
// Uses exec+stdin (sh -c 'cat > file') instead of the Docker tar archive API.
// Used by the clipboard upload feature to stage images for the xclip shim.
CopyFileToContainer(ctx context.Context, containerID, destDir, filename string, content io.Reader, size int64) error
// RemoveVolume removes a Docker named volume. Used to clean up workspace
// volumes for remote projects when their container is deleted. Errors are
// logged but not fatal — the volume may not exist (temporary projects).
RemoveVolume(ctx context.Context, name string) error
}

ContainerHealth describes a container’s startup health state. Used by the liveness checker to diagnose crash-looping containers.

type ContainerHealth struct {
// Restarting is true when the container is in a Docker restart loop.
Restarting bool
// RestartCount is the number of times Docker has restarted the container.
RestartCount int
// ExitCode is the last exit code from the container's entrypoint.
ExitCode int
// OOMKilled is true if the container was killed due to memory limits.
OOMKilled bool
// LogTail contains the last lines of container logs (only populated when unhealthy).
LogTail string
}

EngineClient wraps the Docker Engine SDK client for container operations.

type EngineClient struct {
// contains filtered or unexported fields
}

func NewClient(socketPath string, registry *agent.Registry) (*EngineClient, error)

NewClient creates an EngineClient using the given socket path. The registry maps agent type names to StatusProvider implementations. When socketPath is empty, falls back to client.FromEnv (default Docker behavior).

The socketPath can be an absolute Unix path (/var/run/docker.sock), a Windows named pipe (//./pipe/docker_engine), or a URI with scheme (unix://, npipe://, tcp://).

func (ec *EngineClient) APIClient() client.APIClient

APIClient returns the underlying Docker API client. Used by the terminal proxy to create exec sessions with TTY mode.

func (*EngineClient) AddBridgeFirewallRule

Section titled “func (*EngineClient) AddBridgeFirewallRule”
func (ec *EngineClient) AddBridgeFirewallRule(ctx context.Context, port int) error

AddBridgeFirewallRule adds an iptables ACCEPT rule for the given TCP port in the WARDEN-BRIDGE chain. This allows containers on the docker0 bridge to reach the socket bridge TCP listener on the host.

func (*EngineClient) AddBridgeFirewallRules

Section titled “func (*EngineClient) AddBridgeFirewallRules”
func (ec *EngineClient) AddBridgeFirewallRules(ctx context.Context, ports []int) error

AddBridgeFirewallRules adds iptables ACCEPT rules for multiple TCP ports in a single container execution. Batching avoids the overhead of one Docker container per port.

func (*EngineClient) AllowBridgePortInContainer

Section titled “func (*EngineClient) AllowBridgePortInContainer”
func (ec *EngineClient) AllowBridgePortInContainer(ctx context.Context, containerID string, port int) error

AllowBridgePortInContainer adds an iptables rule inside the container that allows outbound TCP connections to the Docker gateway on the given port. Required for containers with restricted/none network modes where outbound traffic is blocked by default — without this rule, socat cannot reach the host TCP bridge via host.docker.internal.

The rule is inserted at position 2 in the OUTPUT chain (after the loopback ACCEPT rule) so it takes precedence over the REJECT rule. No-op if the container has no iptables rules (full network mode).

func (*EngineClient) ApplyNetworkIsolation

Section titled “func (*EngineClient) ApplyNetworkIsolation”
func (ec *EngineClient) ApplyNetworkIsolation(ctx context.Context, containerID, mode string, domains []string) error

ApplyNetworkIsolation runs the network isolation script via privileged docker exec. Used after container start/restart to set up iptables without granting NET_ADMIN to the container’s capability set. This makes network isolation tamper-proof — even root inside the container cannot modify iptables rules.

func (ec *EngineClient) CheckContainerName(ctx context.Context, name string) api.CheckNameResult

CheckContainerName reports whether a container name is available. If a container exists, returns whether it is Warden-managed and its Docker state.

func (*EngineClient) CleanupEphemeralContainers

Section titled “func (*EngineClient) CleanupEphemeralContainers”
func (ec *EngineClient) CleanupEphemeralContainers(ctx context.Context)

CleanupEphemeralContainers removes any leftover ephemeral containers (precache, firewall, network isolation) from a previous run that crashed before defer-based cleanup could execute. Should be called once on startup.

func (ec *EngineClient) CleanupEventDir(containerName string)

CleanupEventDir removes the event directory for a container. Called after a container is deleted to prevent orphaned directories.

func (*EngineClient) CleanupOrphanedWorktrees

Section titled “func (*EngineClient) CleanupOrphanedWorktrees”
func (ec *EngineClient) CleanupOrphanedWorktrees(ctx context.Context, containerID string) ([]string, error)

CleanupOrphanedWorktrees tidies up worktree-related state in the container:

  1. Prunes git worktree metadata for directories that no longer exist on disk.
  2. Removes .claude/worktrees/ directories not tracked by git (leftovers from kill-worktree.sh running before Claude could call `git worktree remove`).
  3. Removes .warden/terminals/<id>/ directories whose tmux sessions are dead OR whose worktree directories no longer exist on disk (orphaned by Claude running `git worktree remove` inside the container). Kills orphaned tmux sessions before removing their tracking directories.

Returns the deduplicated list of removed worktree IDs from steps 2 and 3.

func (ec *EngineClient) ConnectTerminal(ctx context.Context, containerID, worktreeID string, skipPermissions bool) (string, error)

ConnectTerminal starts a terminal for a worktree inside the container. If a tmux session is still alive (background state), the WebSocket proxy will attach to it. Otherwise runs create-terminal.sh which starts a tmux session and launches the agent. When skipPermissions is true, Claude Code runs with —dangerously-skip-permissions.

func (ec *EngineClient) ContainerIP(ctx context.Context, containerID string) (string, error)

ContainerIP returns the bridge network IP address of a running container. Returns an error if the container has no bridge network or the IP is empty (e.g. container is stopped).

func (*EngineClient) ContainerStartupHealth

Section titled “func (*EngineClient) ContainerStartupHealth”
func (ec *EngineClient) ContainerStartupHealth(ctx context.Context, containerName string) (*ContainerHealth, error)

ContainerStartupHealth inspects a container to determine if it is crash-looping and, if so, captures the last log lines for diagnostics.

func (ec *EngineClient) CopyFileToContainer(ctx context.Context, containerID, destDir, filename string, content io.Reader, _ int64) error

CopyFileToContainer writes a single file into a running container via exec. Creates intermediate directories if needed. Runs as the container user so file ownership is correct for the agent process.

func (ec *EngineClient) CreateContainer(ctx context.Context, req api.CreateContainerRequest) (string, error)

CreateContainer creates and starts a new project container with the given configuration. Returns the container ID (truncated to 12 chars).

func (ec *EngineClient) CreateWorktree(ctx context.Context, containerID, name string, skipPermissions bool) (string, error)

CreateWorktree creates a new worktree terminal inside the container. Claude Code’s —worktree flag handles git worktree creation and isolation, so the worktree won’t exist in git yet — we skip the orphan check. When skipPermissions is true, Claude Code runs with —dangerously-skip-permissions.

func (ec *EngineClient) DeleteContainer(ctx context.Context, id string) error

DeleteContainer stops and removes a container, clearing its git repo cache entry.

func (ec *EngineClient) DisconnectTerminal(ctx context.Context, containerID, worktreeID string) error

DisconnectTerminal pushes a disconnect event and cleans up tracking state. The tmux session (and Claude/bash running inside it) continues in the background.

func (ec *EngineClient) ExecPortForward(ctx context.Context, containerID string, port int) (ExecStream, error)

ExecPortForward creates a docker exec running socat in STDIO mode to forward data to a TCP port inside the container. Returns a hijacked connection that provides a bidirectional stream: writes go to socat’s stdin, reads come from socat’s stdout/stderr (multiplexed).

Used by the port bridge on Docker Desktop where the container’s bridge IP is unreachable from the host. Each call creates a new exec instance for one TCP connection.

func (ec *EngineClient) ExecSocatBridge(ctx context.Context, containerID, containerPath string, port int) error

ExecSocatBridge starts a socat process inside the container that creates a Unix socket at containerPath and forwards connections to the host TCP bridge via host.docker.internal:port. Runs as the warden user in detached mode so it survives the exec returning.

func (ec *EngineClient) GetWorktreeDiff(ctx context.Context, containerID, worktreeID string) (*api.DiffResponse, error)

GetWorktreeDiff returns the uncommitted changes (tracked + untracked) for a worktree inside the container. Uses a temporary index copy so the real index is never modified.

func (ec *EngineClient) InspectContainer(ctx context.Context, id string) (*api.ContainerConfig, error)

InspectContainer returns the editable configuration of an existing container by parsing its inspect data (binds, env vars, labels).

func (ec *EngineClient) IsEstimatedCost(ctx context.Context, containerID string) bool

IsEstimatedCost checks whether a container is using estimated cost (subscription user) vs actual API cost. Reads oauthAccount.billingType from .claude.json — “stripe_subscription” means estimated cost. Falls back to true (estimated) if the billing type can’t be determined.

func (ec *EngineClient) KillSocatBridges(ctx context.Context, containerID string) error

KillSocatBridges kills all socat bridge processes inside the container and removes stale iptables rules for old bridge ports. Called before re-exec’ing bridges with new ports (e.g. after server restart) to avoid stale connections and leftover firewall rules.

func (ec *EngineClient) KillWorktreeProcess(ctx context.Context, containerID, worktreeID string) error

KillWorktreeProcess kills the tmux session for a worktree, destroying the process entirely. The git worktree directory on disk is preserved. Use DisconnectTerminal to only disconnect the viewer and keep the session alive.

func (ec *EngineClient) ListProjects(ctx context.Context, names []string) ([]Project, error)

ListProjects fetches Docker state for the given container names. Names not found in Docker are returned with HasContainer: false. The returned order matches the input name order.

func (ec *EngineClient) ListWorktrees(ctx context.Context, containerID string, skipEnrich bool) ([]Worktree, error)

ListWorktrees returns all worktrees for the given container with their terminal state. For git repos, discovers worktrees via `git worktree list —porcelain`. For non-git repos, returns a single implicit worktree at /project. When skipEnrich is true, the expensive batch docker exec for terminal state is skipped.

func (ec *EngineClient) Ping(ctx context.Context) error

Ping checks whether the Docker daemon is reachable.

func (ec *EngineClient) PreWarmCLICache(ctx context.Context) error

PreWarmCLICache downloads pinned agent CLIs into the warden-cache volume using throwaway containers. Both agent types run in parallel. Subsequent container creates get a cache hit and skip the download.

func (*EngineClient) ReadAgentCostAndBillingType

Section titled “func (*EngineClient) ReadAgentCostAndBillingType”
func (ec *EngineClient) ReadAgentCostAndBillingType(ctx context.Context, containerID, workspacePrefix string) (*AgentCostResult, error)

ReadAgentCostAndBillingType reads the agent config file once and extracts both cost (filtered by workspace prefix) and billing type. Returns per-session cost breakdown for session-keyed DB persistence.

func (ec *EngineClient) ReadAgentStatus(ctx context.Context, containerID string) (map[string]*agent.Status, error)

ReadAgentStatus reads the agent config file from a running container and extracts per-project status data. Returns a map keyed by the working directory path inside the container.

This is the Go-side equivalent of the jq extraction in warden-event.sh, but more reliable: it uses proper JSON parsing, doesn’t depend on WARDEN_EVENT_DIR being set, and runs from the host via docker exec.

func (*EngineClient) ReapplyNetworkIsolation

Section titled “func (*EngineClient) ReapplyNetworkIsolation”
func (ec *EngineClient) ReapplyNetworkIsolation(ctx context.Context, id, networkMode string, allowedDomains []string)

ReapplyNetworkIsolation waits for agent/runtime installs to finish, then re-applies iptables rules. Iptables rules are lost when the container stops, so they must be re-established after every restart. The install wait ensures the entrypoint has unrestricted network for downloads before rules are applied.

func (ec *EngineClient) RecreateContainer(ctx context.Context, id string, req api.CreateContainerRequest) (string, error)

RecreateContainer replaces a stopped container with a new one using updated config. The old container is renamed to a temporary name before creating the replacement, so it can be restored if the create fails (atomic swap).

func (ec *EngineClient) ReloadAllowedDomains(ctx context.Context, containerID string, domains []string) error

ReloadAllowedDomains re-runs the network isolation script inside a running container to update the allowed domain list without recreation. Delegates to ApplyNetworkIsolation with restricted mode hardcoded (the only mode that uses domain hot-reload).

func (*EngineClient) RemoveBridgeFirewallRule

Section titled “func (*EngineClient) RemoveBridgeFirewallRule”
func (ec *EngineClient) RemoveBridgeFirewallRule(ctx context.Context, port int) error

RemoveBridgeFirewallRule removes the iptables ACCEPT rule for the given TCP port from the WARDEN-BRIDGE chain. Called when a socket bridge is stopped (container delete, recreate, or shutdown).

func (ec *EngineClient) RemoveVolume(ctx context.Context, name string) error

RemoveVolume removes a Docker named volume. Used to clean up workspace volumes for remote projects when their container is deleted.

func (ec *EngineClient) RemoveWorktree(ctx context.Context, containerID, worktreeID string) error

RemoveWorktree fully removes a worktree: kills any running tmux session, runs `git worktree remove —force`, and cleans up the .warden/terminals/ tracking directory. Cannot remove the “main” worktree.

Tolerates missing git worktrees (e.g. when Claude already ran `git worktree remove` inside the container). In that case, git metadata is pruned and the terminal tracking directory is still cleaned up.

func (ec *EngineClient) RenameContainer(ctx context.Context, id string, newName string) error

RenameContainer changes the name of an existing container without recreation.

func (ec *EngineClient) ResetWorktree(ctx context.Context, containerID, worktreeID string) error

ResetWorktree clears all history for a worktree without removing it from disk. It kills any running tmux session, removes the terminal tracking directory (exit_code, inner-cmd.sh), and deletes agent JSONL session files so that the FileTailer won’t replay old events on restart.

func (ec *EngineClient) RestartProject(ctx context.Context, id string, originalMounts []api.Mount) error

RestartProject restarts a container and re-applies network isolation. Before restarting, it validates that all bind mount source paths still exist on the host. If any are stale, the restart is blocked with a StaleMountsError so the caller can warn the user.

After the restart completes, network isolation is re-applied via privileged docker exec if the project uses restricted or none mode. Iptables rules don’t persist across container restarts, so they must be re-established every time.

originalMounts are the pre-symlink-resolution mount specs from the DB. When nil, mount validation is skipped (container predates the migration).

func (ec *EngineClient) SendWorktreeInput(ctx context.Context, containerID, worktreeID, text string, pressEnter bool) error

SendWorktreeInput sends text to a worktree’s tmux pane. Uses `tmux send-keys -l` (literal mode) to prevent tmux key-name interpretation. If pressEnter is true, sends a separate Enter keystroke after the text.

func (ec *EngineClient) SetEventBaseDir(dir string)

SetEventBaseDir configures the host-side base directory for event files. Each container gets a subdirectory at <baseDir>/<containerName>/events/ that is bind-mounted into the container at /var/warden/events/.

func (ec *EngineClient) SetFirewallChain(chain string)

SetFirewallChain overrides the iptables chain name used for socket bridge rules. Use this to isolate dev and production firewall rules when both run on the same host (e.g. “WARDEN-BRIDGE-DEV” for development builds).

func (ec *EngineClient) SetIsDesktop(desktop bool)

SetIsDesktop marks whether the Docker runtime is Docker Desktop. When true, host-side iptables operations are skipped (the VM’s NAT handles container-to-host forwarding).

func (ec *EngineClient) SetSeccompProfile(profileJSON string)

SetSeccompProfile configures the inline seccomp profile JSON for new containers. Docker’s API applies it via SecurityOpt.

func (ec *EngineClient) SetupBridgeFirewall(ctx context.Context) error

SetupBridgeFirewall creates the WARDEN-BRIDGE iptables chain on the host and adds a jump rule from INPUT. Idempotent — safe to call on every startup. Flushes stale rules from a previous server run.

Only runs on native Docker (not Docker Desktop). On Desktop, the VM’s NAT handles container-to-host forwarding without iptables rules.

func (ec *EngineClient) StopProject(ctx context.Context, id string) error

StopProject gracefully stops a container with a 30-second timeout.

func (*EngineClient) TeardownBridgeFirewall

Section titled “func (*EngineClient) TeardownBridgeFirewall”
func (ec *EngineClient) TeardownBridgeFirewall(ctx context.Context) error

TeardownBridgeFirewall removes the WARDEN-BRIDGE chain and its jump rule from INPUT. Called on graceful server shutdown. Errors are logged but not returned since this runs during cleanup.

func (*EngineClient) ValidateInfrastructure

Section titled “func (*EngineClient) ValidateInfrastructure”
func (ec *EngineClient) ValidateInfrastructure(ctx context.Context, containerID string) (bool, []string, error)

ValidateInfrastructure checks whether a container has the required Warden terminal infrastructure installed. Uses POSIX `test -x` to check each binary so it works in minimal containers without `which`.

func (ec *EngineClient) WaitForInstalls(ctx context.Context, containerID string) error

WaitForInstalls polls the container for the install-complete marker written by entrypoint.sh. This ensures network isolation is not applied while downloads are in progress. Returns nil once the marker exists, or an error on timeout/context cancellation.

func (ec *EngineClient) WatchContainerEvents(ctx context.Context, onStart func(containerID, containerName string))

WatchContainerEvents subscribes to Docker container start events and calls onStart for each Warden-managed container that starts. This catches auto-restarts by the Docker daemon (restart policy: unless-stopped) so the Go server can re-apply network isolation.

The watcher reconnects automatically on errors with exponential backoff. It runs until the context is cancelled.

ExecStream is a bidirectional stream from a docker exec process. Wraps the Docker SDK’s HijackedResponse to avoid leaking SDK types through the Client interface.

type ExecStream struct {
// Conn is the raw connection for writing to exec stdin.
Conn net.Conn
// Reader provides the multiplexed stdout/stderr stream from the exec.
Reader io.Reader
}

func (s ExecStream) Close()

Close releases the underlying connection.

InspectedMounts holds the parsed mount information from a Docker container inspect response. Consolidates the two-phase pattern (parse HostConfig.Binds, then scan info.Mounts for structured entries) into a single pass.

type InspectedMounts struct {
// ProjectPath is the host path mounted at the workspace directory,
// or empty for remote projects (which use Docker volumes).
ProjectPath string
// Mounts are the non-workspace, non-warden-internal bind mounts.
Mounts []api.Mount
}

NotificationType is re-exported from event/ for backward compatibility.

type NotificationType = event.NotificationType

Project represents a project tracked by Warden, optionally backed by a Docker container. ProjectID is the stable identity (deterministic hash of host path or clone URL). ID is the Docker container ID (empty when HasContainer is false).

type Project struct {
// ProjectID is the deterministic project identifier (sha256 of host path or clone URL, 12 hex chars).
ProjectID string `json:"projectId"`
// ID is the Docker container ID (empty when no container exists).
ID string `json:"id"`
// Name is the user-chosen display label / Docker container name.
Name string `json:"name"`
// HostPath is the absolute host directory mounted into the container (local projects only).
HostPath string `json:"hostPath,omitempty"`
// CloneURL is the git repository URL to clone (remote projects only).
CloneURL string `json:"cloneURL,omitempty"`
// Temporary is true when a remote project's workspace is ephemeral.
Temporary bool `json:"temporary,omitempty"`
// HasContainer is true when a Docker container is associated with this project.
HasContainer bool `json:"hasContainer"`
Type string `json:"type"`
Image string `json:"image"`
OS string `json:"os"`
CreatedAt int64 `json:"createdAt"`
SSHPort string `json:"sshPort"`
State string `json:"state"`
Status string `json:"status"`
AgentStatus AgentStatus `json:"agentStatus"`
// NeedsInput is true when any worktree requires user attention.
NeedsInput bool `json:"needsInput,omitempty"`
// NotificationType indicates why Claude needs attention (e.g. permission_prompt, idle_prompt).
NotificationType NotificationType `json:"notificationType,omitempty"`
// AttentionWorktreeIDs lists the IDs of worktrees currently needing user attention.
AttentionWorktreeIDs []string `json:"attentionWorktreeIDs,omitempty"`
// ActiveWorktreeCount is the number of worktrees with connected terminals.
ActiveWorktreeCount int `json:"activeWorktreeCount"`
// TotalCost is the aggregate cost across all worktrees in USD (from agent status provider).
TotalCost float64 `json:"totalCost"`
// IsEstimatedCost is true when the cost is an estimate (e.g. subscription users).
// When false, the cost reflects actual API spend.
IsEstimatedCost bool `json:"isEstimatedCost,omitempty"`
// CostBudget is the per-project cost limit in USD (0 = use global default).
CostBudget float64 `json:"costBudget"`
// IsGitRepo indicates whether the container's /project is a git repository.
IsGitRepo bool `json:"isGitRepo"`
// AgentType identifies the CLI agent running in this project (e.g. "claude-code", "codex").
AgentType constants.AgentType `json:"agentType"`
// SkipPermissions indicates whether terminals should skip permission prompts.
SkipPermissions bool `json:"skipPermissions"`
// MountedDir is the host directory mounted into the container.
MountedDir string `json:"mountedDir,omitempty"`
// WorkspaceDir is the container-side workspace directory (mount destination).
WorkspaceDir string `json:"workspaceDir,omitempty"`
// NetworkMode controls the container's network isolation level.
NetworkMode api.NetworkMode `json:"networkMode"`
// AllowedDomains lists domains accessible when NetworkMode is "restricted".
AllowedDomains []string `json:"allowedDomains,omitempty"`
// AgentVersion is the pinned CLI version installed in this container.
AgentVersion string `json:"agentVersion,omitempty"`
// ForwardedPorts lists container ports exposed via the reverse proxy.
ForwardedPorts []int `json:"forwardedPorts,omitempty"`
}

SessionCost holds cost data for a single agent session.

type SessionCost struct {
SessionID string
Cost float64
}

StaleMountsError is returned by RestartProject when the container’s bind mounts no longer match what a fresh symlink resolution would produce. This typically happens when a dotfile manager changes symlink targets after the container was created.

Starting a container with stale mounts causes Claude Code to see unreadable or outdated config files. If Claude Code cannot read its settings, it performs a fresh write that overwrites the host’s settings through the read-write parent directory mount — destroying user hooks, permissions, and preferences.

The container must be recreated to pick up the current symlink targets.

type StaleMountsError struct {
// StalePaths lists the container-side mount paths whose host-side
// sources have diverged from a fresh resolution.
StalePaths []string
}

func (e *StaleMountsError) Error() string

Error implements the error interface.

Worktree represents a git worktree (or implicit project root) with its terminal state.

type Worktree struct {
// ID is the worktree identifier — directory name for git worktrees, "main" for project root.
ID string `json:"id"`
// ProjectID is the container ID this worktree belongs to.
ProjectID string `json:"projectId"`
// Path is the filesystem path inside the container.
Path string `json:"path"`
// Branch is the git branch checked out in this worktree.
Branch string `json:"branch,omitempty"`
// State is the terminal connection state (connected, shell, background, stopped).
State WorktreeState `json:"state"`
// ExitCode is the agent's exit code when in shell state.
// Nil means the agent is still running (or no exit code captured).
ExitCode *int `json:"exitCode,omitempty"`
// NeedsInput is true when Claude is blocked waiting for user attention.
NeedsInput bool `json:"needsInput,omitempty"`
// NotificationType indicates why Claude needs attention.
NotificationType NotificationType `json:"notificationType,omitempty"`
}

WorktreeState represents the terminal connection state of a worktree.

type WorktreeState string

const (
// WorktreeStateConnected means a terminal is running with Claude active.
WorktreeStateConnected WorktreeState = "connected"
// WorktreeStateShell means the agent exited but the bash shell is still alive.
WorktreeStateShell WorktreeState = "shell"
// WorktreeStateBackground means the tmux session is alive but no viewer is
// connected (e.g. browser closed). Claude Code may still be working.
WorktreeStateBackground WorktreeState = "background"
// WorktreeStateStopped means no terminal process is running.
WorktreeStateStopped WorktreeState = "stopped"
)

Generated by gomarkdoc