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+\\"

ErrNameTaken is returned when a container with the requested name already exists.

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

TmuxSessionName returns the tmux session name for a worktree.

var TmuxSessionName = constants.TmuxSessionName

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 current mount differs from what a fresh resolution would produce.

This detects ANY divergence — deleted targets, changed symlink targets, new symlinks, or removed symlinks — not just missing files.

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 and re-applies
// network isolation if needed. originalMounts are the pre-symlink-resolution
// mount specs from the DB, used to detect stale bind mounts before restarting.
// networkMode and allowedDomains are read from the DB so that network
// isolation can be re-applied after the container restarts.
RestartProject(ctx context.Context, id string, originalMounts []api.Mount, networkMode string, allowedDomains []string) error
// 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)
// 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) 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) 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) 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) 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 (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 (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, networkMode string, allowedDomains []string) 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) SetSeccompProfile(profileJSON string)

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

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

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

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.

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"`
// 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