Documentation
¶
Overview ¶
Package sandbox holds the podman-backed Sandbox implementation (internal/agent.Sandbox) — a session- or worker-scoped rootless container that RunBashTool dispatches commands into via `podman exec` instead of running them directly on the host. See roadmap/sandbox-podman.md for the design.
This package mirrors internal/lsp's role: a sibling package holding an external-process lifecycle, imported only by the two session entry points (internal/tui, internal/oneshot) and internal/agent's dispatch worker wiring — never by internal/agent itself, which only needs the Sandbox interface.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func PathVisibleFromMountRoot ¶ added in v0.5.0
PathVisibleFromMountRoot mirrors sandboxMountPaths without creating anything.
func PruneOrphaned ¶ added in v0.5.0
PruneOrphaned removes yc-* containers left behind by a session or dispatch worker that never reached Close() (a crash skips normal teardown), or that got stuck mid-teardown (Close's own `podman rm -f` interrupted before it finished). NewPodmanSandbox's own leftover sweep (see podman.go) only helps when a LATER session reuses the exact same deterministic name via --resume; a container from a session that is never resumed leaks forever without this. Runs once at session startup, best-effort: only the listing call's own error is returned, individual removal failures are swallowed.
Safety: container State is the primary filter, but "running" is no longer an unconditional skip. A container backing a live session or dispatch worker is always "running" (its entrypoint is `sleep infinity`) even when idle between commands, so State alone can't distinguish "genuinely still in use" from "abandoned, nothing ever told it to stop" — SIGHUP (closed terminal, dropped SSH), SIGKILL (force-kill, the process itself becoming an OOM-kill target), or a crash that skips Close() all leave a container "running" forever with no session left to use it. ownerAlive resolves this by checking whether the process that created the container (recorded as podman labels, see podman.go's sandboxOwner) is still alive: a "running" container is only pruned once its owner is confirmed gone. A container with no owner label (created before this labeling existed, or by something other than yottacode) is left alone, same as before this check existed. pruneGracePeriod guards every prune target — running-with-dead-owner included — against racing a container that is mid-transition through a concurrent Close() call right now, or one whose owner label hasn't propagated to `podman ps` output yet immediately after creation.
func PruneOrphanedSecrets ¶ added in v0.5.0
PruneOrphanedSecrets is createSessionSecrets' counterpart to PruneOrphaned: a session that crashes (or is killed) between creating a secret and either mounting it into a container or reaching Close() leaves it behind forever without this. Unlike a container, a secret has no "running" state to muddy the safety check — ownerAlive (see PruneOrphaned's doc comment) is the whole filter, gated by the same pruneGracePeriod. Runs once at session startup, best-effort: only the listing call's own error is returned, individual removal failures are swallowed.
Types ¶
type PodmanSandbox ¶
type PodmanSandbox struct {
// contains filtered or unexported fields
}
PodmanSandbox runs commands inside a single long-lived rootless podman container (`podman run -d ... sleep infinity`), dispatching each RunBashTool call via `podman exec`. One container per session or per dispatch write-worker — never per command (see roadmap/sandbox-podman.md for why a fresh container per command is rejected).
func NewPodmanSandbox ¶
func NewPodmanSandbox(ctx context.Context, cfg config.SandboxConfig, id, mountRoot string) (*PodmanSandbox, error)
NewPodmanSandbox starts a session-scoped container per cfg, bind-mounting mountRoot at the SAME absolute path inside the container (so paths the model uses are valid on both sides). id becomes part of the container name (e.g. a session ID or "<session>-<taskID>" for a dispatch worker).
Synchronous: blocks until the container is confirmed started. On any failure (podman missing, image pull failure, rootless setup issues) it returns a wrapped, actionable error and leaves no container running — callers must treat this as fatal to session/worker startup and must NEVER fall back to HostSandbox on error, which would silently defeat the isolation the caller asked for.
func (*PodmanSandbox) Close ¶
func (s *PodmanSandbox) Close() error
Close removes the container. See removeContainer — Close just adds the caller-facing error wrap; callers at the two documented call sites (session teardown, dispatch worker cleanup) treat it as non-fatal and swallow it, consistent with this codebase's existing best-effort cleanup convention (dispatch_cleanup.go).
func (*PodmanSandbox) Command ¶
Command returns a `podman exec` into this sandbox's container. Mirrors HostSandbox.Command's contract exactly: command is a single /bin/sh -c argument, so there is no new shell-injection surface beyond what already exists for the host path today.
The command is tagged with a unique marker and cmd.Cancel is set to best-effort terminate the marked process and its descendant tree inside the container when ctx is canceled or times out. Without this, the default Cancel behavior (kill the local `podman exec` client) does NOT kill the process it started inside the long-lived container — verified empirically: a canceled `sleep 30` kept running, reparented to the container's PID 1, after the local client was killed. A plain process-group kill doesn't reach it either — this container setup gives no per-exec process group (children inherit the container's own group rather than a fresh one), also verified by direct test.
This is NOT a complete guarantee: a command that daemonizes itself via the classic double-fork idiom (e.g. `(cmd &) &`, `nohup cmd & disown`) can land a descendant in a process group cancellation's cleanup never reaches — verified directly against a real container. See docs/sandbox.md's Known limitations for why (closing this needs per-exec cgroup-scoped kill, which needs cgroup delegation this container setup doesn't reliably have).
func (*PodmanSandbox) Label ¶
func (s *PodmanSandbox) Label() string
Unambiguously "-sandbox", not just "[podman]" — a bare "[podman]" tag on a run_bash card reads like a statement about the command itself (e.g. "this ran podman"), not "this ran inside a podman sandbox container".
type Status ¶
type Status struct {
Installed bool
ImagePresent bool // only meaningful when Installed
DocumentsImagePresent bool // only meaningful when Installed
}
Status reports whether podman is usable and whether the configured base image is already present locally. Surfaced by the TUI /sandbox picker so a missing podman or unpulled image shows up as a warning there, instead of only failing later when NewPodmanSandbox runs at session start.