use std::collections::HashMap;
use std::fmt::Write as _;
use std::io::Cursor;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

use async_trait::async_trait;
use bollard::Docker;
use bollard::container::{
    Config, CreateContainerOptions, DownloadFromContainerOptions, InspectContainerOptions,
    LogOutput, RemoveContainerOptions, StartContainerOptions, StopContainerOptions,
    UploadToContainerOptions,
};
use bollard::errors::Error as DockerError;
use bollard::exec::{CreateExecOptions, StartExecOptions, StartExecResults};
use bollard::image::CreateImageOptions;
use bollard::models::{ContainerInspectResponse, HostConfig};
use fabro_github::GitHubCredentials;
use fabro_github::token_source::InstallationTokenSource;
use fabro_types::{CommandOutputStream, CommandTermination, RunId, SandboxProviderKind};
use fabro_util::time::elapsed_ms;
use futures::StreamExt;
use tokio::io::{AsyncWriteExt, duplex};
use tokio::sync::{Mutex as TokioMutex, Notify, OnceCell};
use tokio::{fs, time};
use tokio_util::sync::CancellationToken;

use crate::clone_source::{self, CloneDecision, EmptyWorkspaceReason};
use crate::git_retry::{self, CredentialContext};
use crate::managed_labels::{self, MANAGED_LABEL, RUN_ID_LABEL};
use crate::push_credentials::{self, PushCredentialState};
use crate::redact::redact_auth_url;
use crate::sandbox::{
    self, BASH_ENV_VAR, BASH_PROBE_SCRIPT, BASH_PROBE_TIMEOUT_MS, REMOTE_BASH,
    REMOTE_WALK_TIMEOUT_MS, RefreshOutcome, StdioProcessControl, optional_timeout, resolve_path,
    validate_bash_probe, write_process_stdin,
};
use crate::{
    CommandOutputCallback, DEFAULT_EXEC_OUTPUT_TAIL_BYTES, DirEntry, ExecResult,
    ExecStreamingRequest, ExecStreamingResult, GrepOptions, Sandbox, SandboxEvent,
    SandboxEventCallback, SandboxFile, StderrCollector, StdioProcess, StdioProcessHandle,
    StdioProcessTermination, WalkOptions, format_lines_numbered, shell_quote,
};

const DOCKER_BASH_REQUIREMENT: &str = "Docker sandboxes require /bin/bash for every command, with no `sh` fallback; use an \
     image with bash and git, such as buildpack-deps:noble.";

pub(crate) const WORKING_DIRECTORY: &str = "/workspace";
pub(crate) const REPOS_ROOT: &str = "/repos";
const GIT_CLONE_DEPTH: usize = 10;
const GIT_CLONE_TIMEOUT: Duration = Duration::from_mins(5);
#[cfg(test)]
const EXEC_STOP_POLL_SLEEP_SECONDS: &str = "0.005";
#[cfg(not(test))]
const EXEC_STOP_POLL_SLEEP_SECONDS: &str = "0.1";
#[cfg(test)]
const EXEC_TERM_GRACE_SECONDS: &str = "0.02";
#[cfg(not(test))]
const EXEC_TERM_GRACE_SECONDS: &str = "0.2";

struct DockerCloneFailure {
    error:        crate::Error,
    retry_reason: Option<git_retry::GitRetryReason>,
}

fn env_entry_name(entry: &str) -> &str {
    entry.split_once('=').map_or(entry, |(name, _)| name)
}

/// Remove caller/image startup-file injection and explicitly override any
/// inherited image value for Docker-created processes.
fn clean_bash_env_entries(entries: impl IntoIterator<Item = String>) -> Vec<String> {
    let mut clean: Vec<String> = entries
        .into_iter()
        .filter(|entry| env_entry_name(entry) != BASH_ENV_VAR)
        .collect();
    clean.push(format!("{BASH_ENV_VAR}="));
    clean
}

fn docker_bash_exec_env(env_vars: Option<&HashMap<String, String>>) -> Vec<String> {
    let entries = env_vars.map_or_else(Vec::new, |vars| {
        vars.iter()
            .map(|(key, value)| format!("{key}={value}"))
            .collect()
    });
    clean_bash_env_entries(entries)
}

static EXEC_CONTROL_COUNTER: AtomicU64 = AtomicU64::new(1);

pub fn docker_access_command(container_id: &str, working_directory: &str) -> String {
    let shell = format!("cd {} && exec sh -l", shell_quote(working_directory));
    format!(
        "docker exec -it {} sh -lc {}",
        shell_quote(container_id),
        shell_quote(&shell)
    )
}

#[derive(Clone, Debug, PartialEq)]
pub struct DockerSandboxOptions {
    /// Docker image to use.
    pub image:        String,
    /// Docker network mode. Default: `Some("bridge")`.
    pub network_mode: Option<String>,
    /// Memory limit in bytes. `None` = unlimited.
    pub memory_limit: Option<i64>,
    /// CPU quota (microseconds per 100ms period). `None` = unlimited.
    pub cpu_quota:    Option<i64>,
    /// Whether to pull the image if not found locally. Default: `true`.
    pub auto_pull:    bool,
    /// Additional `KEY=VALUE` environment variables for the container.
    pub env_vars:     Vec<String>,
    /// Create an empty workspace instead of cloning even when an origin exists.
    pub skip_clone:   bool,
}

impl Default for DockerSandboxOptions {
    fn default() -> Self {
        Self {
            image:        "buildpack-deps:noble".to_string(),
            network_mode: Some("bridge".to_string()),
            memory_limit: None,
            cpu_quota:    None,
            auto_pull:    true,
            env_vars:     Vec::new(),
            skip_clone:   false,
        }
    }
}

pub struct DockerSandbox {
    docker:            Docker,
    config:            DockerSandboxOptions,
    push_credentials:  PushCredentialState,
    run_id:            Option<RunId>,
    clone_origin_url:  Option<String>,
    clone_branch:      Option<String>,
    container_id:      OnceCell<String>,
    repo_cloned:       OnceCell<bool>,
    working_directory: OnceCell<String>,
    origin_url:        OnceCell<String>,
    cached_platform:   std::sync::OnceLock<String>,
    cached_os_version: std::sync::OnceLock<String>,
    rg_available:      OnceCell<bool>,
    event_callback:    Option<SandboxEventCallback>,
}

enum EnsureImageOutcome {
    Skipped,
    AlreadyLocal,
    Pulled,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
#[strum(serialize_all = "lowercase")]
enum ContainerStartAction {
    Start,
    Unpause,
}

impl DockerSandbox {
    pub fn new(
        config: DockerSandboxOptions,
        github_app: Option<&GitHubCredentials>,
        run_id: Option<RunId>,
        clone_origin_url: Option<String>,
        clone_branch: Option<String>,
    ) -> crate::Result<Self> {
        let docker = Docker::connect_with_local_defaults().map_err(crate::Error::docker_connect)?;
        Self::with_docker_client(
            docker,
            config,
            github_app,
            run_id,
            clone_origin_url,
            clone_branch,
        )
    }

    fn with_docker_client(
        docker: Docker,
        config: DockerSandboxOptions,
        github_app: Option<&GitHubCredentials>,
        run_id: Option<RunId>,
        clone_origin_url: Option<String>,
        clone_branch: Option<String>,
    ) -> crate::Result<Self> {
        let push_credentials = PushCredentialState::new(push_credentials::build_token_source(
            github_app,
            clone_origin_url.as_deref(),
        )?);
        Ok(Self {
            docker,
            config,
            push_credentials,
            run_id,
            clone_origin_url,
            clone_branch,
            container_id: OnceCell::new(),
            repo_cloned: OnceCell::new(),
            working_directory: OnceCell::new(),
            origin_url: OnceCell::new(),
            cached_platform: std::sync::OnceLock::new(),
            cached_os_version: std::sync::OnceLock::new(),
            rg_available: OnceCell::const_new(),
            event_callback: None,
        })
    }

    pub async fn reconnect(
        container_id: &str,
        repo_cloned: bool,
        working_directory: String,
        clone_origin_url: Option<String>,
        clone_branch: Option<String>,
        run_id: Option<RunId>,
    ) -> crate::Result<Self> {
        let sandbox = Self::new(
            DockerSandboxOptions::default(),
            None,
            run_id,
            clone_origin_url.clone(),
            clone_branch,
        )?;
        sandbox.validate_managed_container(container_id).await?;
        sandbox
            .container_id
            .set(container_id.to_string())
            .map_err(|_| "Container already initialized".to_string())?;
        sandbox
            .repo_cloned
            .set(repo_cloned)
            .map_err(|_| "Clone state already initialized".to_string())?;
        sandbox
            .working_directory
            .set(working_directory)
            .map_err(|_| "Working directory already initialized".to_string())?;
        if repo_cloned {
            if let Some(origin) = clone_origin_url {
                let _ = sandbox.origin_url.set(origin);
            }
        }
        Ok(sandbox)
    }

    pub fn set_event_callback(&mut self, cb: SandboxEventCallback) {
        self.event_callback = Some(cb);
    }

    fn emit(&self, event: SandboxEvent) {
        event.trace();
        if let Some(ref cb) = self.event_callback {
            cb(event);
        }
    }

    fn container_id(&self) -> crate::Result<&str> {
        self.container_id.get().map(String::as_str).ok_or_else(|| {
            crate::Error::message("Container not initialized — call initialize() first")
        })
    }

    pub(crate) fn container_identifier(&self) -> crate::Result<&str> {
        self.container_id()
    }

    pub(crate) fn docker_client(&self) -> Docker {
        self.docker.clone()
    }

    fn resolve_container_path(&self, path: &str) -> String {
        resolve_path(path, self.working_directory())
    }

    async fn download_file_bytes(&self, remote_path: &str) -> crate::Result<Vec<u8>> {
        #[expect(
            clippy::disallowed_types,
            reason = "tar entries are synchronous in-memory readers; bytes are collected before any await"
        )]
        use std::io::Read as _;

        let container_id = self.container_id()?;
        let container_path = self.resolve_container_path(remote_path);
        let opts = DownloadFromContainerOptions {
            path: container_path.clone(),
        };
        let mut stream = self
            .docker
            .download_from_container(container_id, Some(opts));
        let mut archive_bytes = Vec::new();
        while let Some(chunk) = stream.next().await {
            let chunk = chunk.map_err(|e| {
                crate::Error::context(
                    format!("Failed to download {container_path} from container"),
                    e,
                )
            })?;
            archive_bytes.extend_from_slice(&chunk);
        }

        let mut archive = tar::Archive::new(Cursor::new(archive_bytes));
        let entries = archive.entries().map_err(|e| {
            crate::Error::context(
                format!("Failed to read Docker archive for {container_path}"),
                e,
            )
        })?;
        for entry in entries {
            let mut entry = entry.map_err(|e| {
                crate::Error::context(
                    format!("Failed to read Docker archive entry for {container_path}"),
                    e,
                )
            })?;
            if !entry.header().entry_type().is_file() {
                continue;
            }
            let mut bytes = Vec::new();
            entry.read_to_end(&mut bytes).map_err(|e| {
                crate::Error::context(
                    format!("Failed to read Docker archive file for {container_path}"),
                    e,
                )
            })?;
            return Ok(bytes);
        }

        Err(crate::Error::message(format!(
            "Docker archive for {container_path} did not contain a file"
        )))
    }

    fn repo_cloned(&self) -> bool {
        self.repo_cloned.get().copied().unwrap_or(false)
    }

    fn set_working_directory(&self, working_directory: impl Into<String>) -> crate::Result<()> {
        self.working_directory
            .set(working_directory.into())
            .map_err(|_| crate::Error::message("Docker working directory already initialized"))
    }

    async fn docker_exec(
        &self,
        cmd: Vec<String>,
        working_dir: Option<&str>,
        env: Option<Vec<String>>,
    ) -> crate::Result<(String, String, i32)> {
        let container_id = self.container_id()?;

        let exec_opts = CreateExecOptions {
            cmd: Some(cmd),
            attach_stdout: Some(true),
            attach_stderr: Some(true),
            working_dir: working_dir.map(ToString::to_string),
            env: env.map(|e| e.into_iter().collect()),
            ..Default::default()
        };

        let (exec_id, start_result) = create_and_start_exec(
            &self.docker,
            container_id,
            exec_opts,
            None,
            "Failed to create exec",
            "Failed to start exec",
        )
        .await?;

        let mut stdout = String::new();
        let mut stderr = String::new();

        if let StartExecResults::Attached { mut output, .. } = start_result {
            while let Some(chunk) = output.next().await {
                match chunk {
                    Ok(LogOutput::StdOut { message }) => {
                        stdout.push_str(&String::from_utf8_lossy(&message));
                    }
                    Ok(LogOutput::StdErr { message }) => {
                        stderr.push_str(&String::from_utf8_lossy(&message));
                    }
                    Ok(_) => {}
                    Err(e) => {
                        return Err(crate::Error::context("Error reading exec output", e));
                    }
                }
            }
        }

        let inspect = self
            .docker
            .inspect_exec(&exec_id)
            .await
            .map_err(|e| crate::Error::context("Failed to inspect exec", e))?;

        let exit_code = inspect
            .exit_code
            .and_then(|code| i32::try_from(code).ok())
            .unwrap_or(-1);
        Ok((stdout, stderr, exit_code))
    }

    async fn docker_exec_streaming(
        docker: Docker,
        container_id: String,
        cmd: Vec<String>,
        working_dir: Option<String>,
        env: Option<Vec<String>>,
        stdin: Option<Vec<u8>>,
        output_callback: Option<CommandOutputCallback>,
    ) -> crate::Result<(Vec<u8>, Vec<u8>, i32)> {
        let exec_opts = CreateExecOptions {
            cmd: Some(cmd),
            attach_stdin: Some(stdin.is_some()),
            attach_stdout: Some(true),
            attach_stderr: Some(true),
            tty: Some(false),
            working_dir,
            env: env.map(|e| e.into_iter().collect()),
            ..Default::default()
        };

        let (exec_id, start_result) = create_and_start_exec(
            &docker,
            &container_id,
            exec_opts,
            None,
            "Failed to create exec",
            "Failed to start exec",
        )
        .await?;

        let mut stdout = Vec::new();
        let mut stderr = Vec::new();

        let StartExecResults::Attached { mut output, input } = start_result else {
            return Err(crate::Error::message(
                "Docker started streaming command without attached standard I/O",
            ));
        };
        let write_stdin = async move {
            if let Some(stdin) = stdin {
                write_process_stdin(input, &stdin).await?;
            }
            crate::Result::Ok(())
        };
        let read_output = async {
            while let Some(chunk) = output.next().await {
                match chunk {
                    Ok(LogOutput::StdOut { message }) => {
                        stdout.extend_from_slice(&message);
                        if let Some(output_callback) = output_callback.as_ref() {
                            output_callback(CommandOutputStream::Stdout, message.to_vec()).await?;
                        }
                    }
                    Ok(LogOutput::StdErr { message }) => {
                        stderr.extend_from_slice(&message);
                        if let Some(output_callback) = output_callback.as_ref() {
                            output_callback(CommandOutputStream::Stderr, message.to_vec()).await?;
                        }
                    }
                    Ok(_) => {}
                    Err(e) => {
                        return Err(crate::Error::context("Error reading exec output", e));
                    }
                }
            }
            crate::Result::Ok(())
        };
        tokio::try_join!(write_stdin, read_output)?;

        let inspect = docker
            .inspect_exec(&exec_id)
            .await
            .map_err(|e| crate::Error::context("Failed to inspect exec", e))?;

        let exit_code = inspect
            .exit_code
            .and_then(|code| i32::try_from(code).ok())
            .unwrap_or(-1);
        Ok((stdout, stderr, exit_code))
    }

    async fn docker_exec_shell(
        &self,
        command: &str,
        timeout_ms: u64,
        working_dir: Option<&str>,
        env_vars: Option<&HashMap<String, String>>,
        cancel_token: Option<CancellationToken>,
    ) -> crate::Result<ExecResult> {
        let start = Instant::now();
        let effective_dir = working_dir
            .unwrap_or_else(|| self.working_directory())
            .to_string();
        let env = Some(docker_bash_exec_env(env_vars));
        let cmd = vec![
            REMOTE_BASH.to_string(),
            "-c".to_string(),
            command.to_string(),
        ];

        let timeout_duration = std::time::Duration::from_millis(timeout_ms);
        let token = cancel_token.unwrap_or_default();

        tokio::select! {
            result = self.docker_exec(cmd, Some(&effective_dir), env) => {
                let (stdout, stderr, exit_code) = result?;
                let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX);
                Ok(ExecResult {
                    stdout,
                    stderr,
                    exit_code: Some(exit_code),
                    termination: CommandTermination::Exited,
                    duration_ms,
                })
            }
            () = time::sleep(timeout_duration) => {
                let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX);
                Ok(ExecResult {
                    stdout: String::new(),
                    stderr: "Command timed out".to_string(),
                    exit_code: None,
                    termination: CommandTermination::TimedOut,
                    duration_ms,
                })
            }
            () = token.cancelled() => {
                let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX);
                Ok(ExecResult {
                    stdout: String::new(),
                    stderr: "Command cancelled".to_string(),
                    exit_code: None,
                    termination: CommandTermination::Cancelled,
                    duration_ms,
                })
            }
        }
    }

    async fn docker_exec_shell_streaming(
        &self,
        request: ExecStreamingRequest<'_>,
    ) -> crate::Result<ExecStreamingResult> {
        let ExecStreamingRequest {
            command,
            timeout_ms,
            working_dir,
            env_vars,
            cancel_token,
            stdin,
            output_callback,
        } = request;
        let start = Instant::now();
        let effective_dir = working_dir
            .unwrap_or_else(|| self.working_directory())
            .to_string();
        let env = Some(docker_bash_exec_env(env_vars));
        let (stop_file, pid_file) = docker_exec_control_paths();
        let controlled_command = docker_controlled_shell_command(command, &stop_file, &pid_file);
        let cmd = vec![
            REMOTE_BASH.to_string(),
            "-c".to_string(),
            controlled_command,
        ];

        let timeout_future = optional_timeout(timeout_ms);
        tokio::pin!(timeout_future);
        let token = cancel_token.unwrap_or_default();

        let container_id = self.container_id()?.to_string();
        let mut output_task = tokio::spawn(Self::docker_exec_streaming(
            self.docker.clone(),
            container_id,
            cmd,
            Some(effective_dir.clone()),
            env,
            stdin,
            output_callback,
        ));

        let mut termination = CommandTermination::Exited;
        let output = tokio::select! {
            joined = &mut output_task => {
                joined
                    .map_err(|e| crate::Error::context("Docker exec stream task failed", e))??
            }
            () = &mut timeout_future => {
                termination = CommandTermination::TimedOut;
                self.request_docker_exec_stop(&stop_file).await?;
                output_task
                    .await
                    .map_err(|e| crate::Error::context("Docker exec stream task failed", e))??
            }
            () = token.cancelled() => {
                termination = CommandTermination::Cancelled;
                self.request_docker_exec_stop(&stop_file).await?;
                output_task
                    .await
                    .map_err(|e| crate::Error::context("Docker exec stream task failed", e))??
            }
        };

        let (stdout, stderr, exit_code) = output;
        let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX);
        Ok(ExecStreamingResult {
            result:            ExecResult {
                stdout: String::from_utf8_lossy(&stdout).into_owned(),
                stderr: String::from_utf8_lossy(&stderr).into_owned(),
                exit_code: (termination == CommandTermination::Exited).then_some(exit_code),
                termination,
                duration_ms,
            },
            streams_separated: true,
            live_streaming:    true,
        })
    }

    async fn request_docker_exec_stop(&self, stop_file: &str) -> crate::Result<()> {
        request_docker_exec_stop_with(&self.docker, self.container_id()?, stop_file).await
    }

    async fn ensure_image(&self) -> crate::Result<EnsureImageOutcome> {
        if !self.config.auto_pull {
            return Ok(EnsureImageOutcome::Skipped);
        }

        match self.docker.inspect_image(&self.config.image).await {
            Ok(_) => return Ok(EnsureImageOutcome::AlreadyLocal),
            Err(e) if docker_not_found(&e) => {}
            Err(e) => {
                return Err(crate::Error::docker_image_inspect(
                    self.config.image.clone(),
                    e,
                ));
            }
        }

        let (repo, tag) = if let Some((r, t)) = self.config.image.rsplit_once(':') {
            (r.to_string(), t.to_string())
        } else {
            (self.config.image.clone(), "latest".to_string())
        };

        let opts = CreateImageOptions {
            from_image: repo,
            tag,
            ..Default::default()
        };

        self.emit(SandboxEvent::SnapshotPulling {
            name: self.config.image.clone(),
        });
        let mut stream = self.docker.create_image(Some(opts), None, None);
        while let Some(result) = stream.next().await {
            result.map_err(|e| crate::Error::docker_image_pull(self.config.image.clone(), e))?;
        }

        Ok(EnsureImageOutcome::Pulled)
    }

    async fn create_workspace(&self) -> crate::Result<()> {
        let result = self
            .docker_exec_shell(
                &format!("mkdir -p {}", shell_quote(WORKING_DIRECTORY)),
                10_000,
                Some("/"),
                None,
                None,
            )
            .await?;
        if !result.is_success() {
            return Err(crate::Error::message(format!(
                "Failed to create Docker workspace (exit {}): {}",
                result.display_exit_code(),
                result.stderr
            )));
        }
        self.set_working_directory(WORKING_DIRECTORY)?;
        Ok(())
    }

    /// Verify the container evaluates commands as non-login Bash.
    ///
    /// Shared by fresh initialization and by `start` after a reconnect, so a
    /// resumed container cannot pass startup and then fail on its first
    /// command.
    async fn probe_bash(&self, working_dir: Option<&str>) -> crate::Result<()> {
        let result = self
            .docker_exec_shell(
                BASH_PROBE_SCRIPT,
                BASH_PROBE_TIMEOUT_MS,
                Some(working_dir.unwrap_or("/")),
                None,
                None,
            )
            .await
            .map_err(|err| crate::Error::context(DOCKER_BASH_REQUIREMENT, err))?;
        validate_bash_probe(result, DOCKER_BASH_REQUIREMENT)
    }

    async fn verify_git_available(&self) -> crate::Result<()> {
        let result = self
            .docker_exec_shell("git --version", 10_000, Some("/"), None, None)
            .await?;
        if !result.is_success() {
            return Err(crate::Error::message(format!(
                "Docker image '{}' must include git for repository clone and git lifecycle operations. Use an image with bash and git, such as buildpack-deps:noble.",
                self.config.image
            )));
        }
        Ok(())
    }

    /// Preserve a failed `git clone` result while masking the auth URL.
    fn clone_failure_error(
        &self,
        result: ExecResult,
        auth_url: Option<&fabro_redact::DisplaySafeUrl>,
    ) -> crate::Error {
        let error = result
            .into_exec_error_with_redactor("git clone", |output| redact_auth_url(output, auth_url));
        let message = if self.push_credentials.source().is_none() {
            "Git clone failed. If this is a private repository, configure a GitHub App with \
             `fabro install` and install it for your organization."
        } else {
            "Failed to clone repository into Docker sandbox"
        };
        crate::Error::context(message, error)
    }

    fn report_clone_failure(&self, origin_url: &str, err: crate::Error) -> crate::Error {
        self.emit(SandboxEvent::GitCloneFailed {
            url:    origin_url.to_string(),
            error:  err.to_string(),
            causes: err.causes(),
        });
        err
    }

    async fn clone_github_repo(
        &self,
        origin_url: String,
        branch: Option<String>,
    ) -> crate::Result<()> {
        self.verify_git_available().await?;
        let layout = clone_source::github_repo_layout(&origin_url, WORKING_DIRECTORY, REPOS_ROOT)?;
        // The clone mints its own token (never a warm-cache reuse) and seeds
        // the shared source, so the first refresh compares against the clone
        // token instead of believing nothing was ever embedded.
        let resolved_token = match self.push_credentials.source() {
            Some(source) => Some(source.mint_for_clone().await.map_err(|err| {
                crate::Error::context_anyhow("Failed to get GitHub App credentials for clone", err)
            })?),
            None => None,
        };
        // The clone call site maps its mint knowledge onto the credential
        // context: a token minted for this clone is FreshApp; a static
        // credential cannot become valid by waiting.
        let clone_credential_context =
            CredentialContext::from_snapshot(resolved_token.as_ref().map(|token| &token.snapshot));

        let auth_url = match &resolved_token {
            Some(token) => Some(
                fabro_github::embed_token_in_url(&origin_url, token.token.expose()).map_err(
                    |err| {
                        crate::Error::context_anyhow(
                            "Failed to build authenticated GitHub clone URL",
                            err,
                        )
                    },
                )?,
            ),
            None => None,
        };
        let clone_url = auth_url
            .as_ref()
            .map_or(origin_url.as_str(), |url| url.as_raw_url().as_str());

        self.emit(SandboxEvent::GitCloneStarted {
            url:    origin_url.clone(),
            branch: branch.clone(),
        });
        let clone_start = Instant::now();

        let prepare_command = format!(
            "mkdir -p {} {}",
            shell_quote(WORKING_DIRECTORY),
            shell_quote(&layout.repos_owner_path),
        );
        match self
            .docker_exec_shell(&prepare_command, 10_000, Some("/"), None, None)
            .await
        {
            Ok(result) if result.is_success() => {}
            Ok(result) => {
                let err = result.into_exec_error("prepare Docker repository checkout");
                return Err(self.report_clone_failure(&origin_url, err));
            }
            Err(err) => {
                return Err(self.report_clone_failure(&origin_url, err));
            }
        }

        let command = git_clone_command(clone_url, branch.as_deref(), &layout.primary_repo_path);
        let clone_deadline = time::Instant::now() + GIT_CLONE_TIMEOUT;
        let clone_plan = git_retry::RetryPlan::clone_default(Some(clone_deadline));
        let clone_result = git_retry::retry_clone(
            SandboxProviderKind::Docker,
            "clone",
            &clone_plan,
            |_attempt| {
                let command = command.as_str();
                let auth_url = auth_url.as_ref();
                async move {
                    let remaining = clone_deadline.saturating_duration_since(time::Instant::now());
                    let timeout_ms = u64::try_from(remaining.as_millis()).unwrap_or(u64::MAX);
                    if timeout_ms == 0 {
                        return Err(DockerCloneFailure {
                            error:        crate::Error::message(
                                "Docker git clone deadline expired before retry",
                            ),
                            retry_reason: None,
                        });
                    }
                    let result = self
                        .docker_exec_shell_streaming(ExecStreamingRequest {
                            timeout_ms: Some(timeout_ms),
                            working_dir: Some("/"),
                            ..ExecStreamingRequest::new(command)
                        })
                        .await
                        .map_err(|error| DockerCloneFailure {
                            error:        crate::Error::context(
                                "Docker git clone transport failed",
                                error,
                            ),
                            retry_reason: None,
                        })?
                        .result;
                    if result.is_success() {
                        return Ok(());
                    }
                    let retry_reason =
                        classify_docker_clone_result(&result, clone_credential_context);
                    Err(DockerCloneFailure {
                        error: self.clone_failure_error(result, auth_url),
                        retry_reason,
                    })
                }
            },
            |failure: &DockerCloneFailure| failure.retry_reason,
        )
        .await;

        if let Err(failure) = clone_result {
            let err = failure.error;
            return Err(self.report_clone_failure(&origin_url, err));
        }

        let symlink_command = clone_source::repo_symlink_command(&layout);
        match self
            .docker_exec_shell(&symlink_command, 10_000, Some("/"), None, None)
            .await
        {
            Ok(result) if result.is_success() => {}
            Ok(result) => {
                let err = result.into_exec_error("create Docker workspace repo symlink");
                return Err(self.report_clone_failure(&origin_url, err));
            }
            Err(err) => {
                return Err(self.report_clone_failure(&origin_url, err));
            }
        }

        let _ = self.repo_cloned.set(true);
        let _ = self.origin_url.set(origin_url.clone());
        self.set_working_directory(layout.execution_directory.clone())?;
        if let Some(token) = resolved_token {
            // The clone URL embedded this token in `origin`; record it so
            // refreshes compare against the clone generation.
            self.push_credentials.record_embedded(token).await;
        }

        if let Some(auth_url) = auth_url.as_ref() {
            let command = format!(
                "git -c maintenance.auto=0 remote set-url origin {}",
                shell_quote(auth_url.as_raw_url().as_str())
            );
            let result = self
                .docker_exec_shell(
                    &command,
                    10_000,
                    Some(&layout.execution_directory),
                    None,
                    None,
                )
                .await?;
            if !result.is_success() {
                let err = result
                    .into_exec_error_with_redactor("git remote set-url origin (post-clone)", |s| {
                        redact_auth_url(s, Some(auth_url))
                    });
                tracing::warn!(
                    error = %err,
                    "Failed to set Docker sandbox push credentials on origin — \
                     subsequent git push from this sandbox will fail"
                );
            }
        }

        let clone_duration = u64::try_from(clone_start.elapsed().as_millis()).unwrap_or(u64::MAX);
        self.emit(SandboxEvent::GitCloneCompleted {
            url:         origin_url,
            duration_ms: clone_duration,
        });
        Ok(())
    }

    async fn validate_managed_container(&self, container_id: &str) -> crate::Result<()> {
        let labels = self.inspect_labels(container_id).await?;
        verify_managed_labels(container_id, &labels, self.run_id.as_ref())
    }

    async fn inspect_container(
        &self,
        container_id: &str,
    ) -> crate::Result<ContainerInspectResponse> {
        self.docker
            .inspect_container(container_id, None::<InspectContainerOptions>)
            .await
            .map_err(|source| {
                let message = if docker_not_found(&source) {
                    format!("Docker container '{container_id}' is gone")
                } else {
                    format!("Failed to inspect Docker container '{container_id}'")
                };
                crate::Error::context(message, source)
            })
    }

    async fn inspect_labels(&self, container_id: &str) -> crate::Result<HashMap<String, String>> {
        let inspect = self.inspect_container(container_id).await?;
        Ok(container_labels(&inspect))
    }

    async fn ensure_name_available(&self) -> crate::Result<Option<String>> {
        let Some(run_id) = self.run_id.as_ref() else {
            return Ok(None);
        };
        let name = container_name(run_id);
        match self
            .docker
            .inspect_container(&name, None::<InspectContainerOptions>)
            .await
        {
            Ok(_) => Err(crate::Error::message(format!(
                "Docker container name '{name}' already exists for run {run_id}. Remove the stale container manually before retrying."
            ))),
            Err(e) if docker_not_found(&e) => Ok(Some(name)),
            Err(e) => Err(crate::Error::message(format!(
                "Failed to check Docker container name '{name}' before creation: {e}"
            ))),
        }
    }

    async fn upload_bytes_to_container(&self, path: &str, bytes: &[u8]) -> crate::Result<()> {
        let container_path = self.resolve_container_path(path);
        let container_id = self.container_id()?;
        let parent_dir = std::path::Path::new(&container_path)
            .parent()
            .map_or_else(|| "/".to_string(), |p| p.to_string_lossy().to_string());
        let file_name = std::path::Path::new(&container_path)
            .file_name()
            .ok_or_else(|| crate::Error::message(format!("Invalid path: {container_path}")))?
            .to_string_lossy()
            .to_string();

        let result = self
            .docker_exec_shell(
                &format!("mkdir -p {}", shell_quote(&parent_dir)),
                10_000,
                Some("/"),
                None,
                None,
            )
            .await?;
        if !result.is_success() {
            return Err(crate::Error::message(format!(
                "Failed to create parent dirs for {container_path}: {}",
                result.stderr
            )));
        }

        let tar_bytes = build_single_file_tar(&file_name, bytes)?;
        let upload_opts = UploadToContainerOptions {
            path:                     parent_dir,
            no_overwrite_dir_non_dir: "false".to_string(),
        };

        self.docker
            .upload_to_container(container_id, Some(upload_opts), tar_bytes.into())
            .await
            .map_err(|e| crate::Error::context("Failed to upload file to container", e))
    }

    fn begin_start(&self) -> Instant {
        self.emit(SandboxEvent::StartStarted {
            provider: "docker".into(),
        });
        Instant::now()
    }

    async fn set_container_running(
        &self,
        container_id: &str,
        labels: &HashMap<String, String>,
        action: ContainerStartAction,
    ) -> crate::Result<()> {
        let result = match action {
            ContainerStartAction::Start => {
                self.docker
                    .start_container(container_id, None::<StartContainerOptions<String>>)
                    .await
            }
            ContainerStartAction::Unpause => self.docker.unpause_container(container_id).await,
        };
        if let Err(source) = result {
            if !docker_not_modified(&source) {
                return Err(crate::Error::context(
                    format!(
                        "Failed to {action} Docker container '{container_id}' with labels {labels:?}"
                    ),
                    source,
                ));
            }
        }
        Ok(())
    }

    async fn complete_start(
        &self,
        started: Instant,
        container_id: &str,
        labels: &HashMap<String, String>,
        action: ContainerStartAction,
    ) -> crate::Result<()> {
        if let Err(error) = self
            .set_container_running(container_id, labels, action)
            .await
        {
            return self.start_error(error);
        }
        if let Err(error) = self.probe_bash(None).await {
            return self.start_error(crate::Error::context(
                format!("Docker container '{container_id}' health check"),
                error,
            ));
        }

        self.emit(SandboxEvent::StartCompleted {
            provider:    "docker".into(),
            duration_ms: elapsed_ms(started),
        });
        Ok(())
    }

    fn start_error(&self, error: crate::Error) -> crate::Result<()> {
        self.emit(SandboxEvent::StartFailed {
            provider: "docker".into(),
            error:    error.to_string(),
            causes:   error.causes(),
        });
        Err(error)
    }

    fn stop_error(&self, error: crate::Error) -> crate::Result<()> {
        self.emit(SandboxEvent::StopFailed {
            provider: "docker".into(),
            error:    error.to_string(),
            causes:   error.causes(),
        });
        Err(error)
    }

    fn delete_error(&self, error: crate::Error) -> crate::Result<()> {
        self.emit(SandboxEvent::DeleteFailed {
            provider: "docker".into(),
            error:    error.to_string(),
            causes:   error.causes(),
        });
        Err(error)
    }

    fn fail_init(&self, init_start: Instant, err: crate::Error) -> crate::Error {
        let duration_ms = u64::try_from(init_start.elapsed().as_millis()).unwrap_or(u64::MAX);
        self.emit(SandboxEvent::InitializeFailed {
            provider: "docker".into(),
            error: err.to_string(),
            causes: err.causes(),
            duration_ms,
        });
        err
    }
}

fn container_name(run_id: &RunId) -> String {
    format!("fabro-run-{run_id}")
}

fn docker_exec_control_paths() -> (String, String) {
    let sequence = EXEC_CONTROL_COUNTER.fetch_add(1, Ordering::Relaxed);
    let nonce = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_nanos())
        .unwrap_or_default();
    let prefix = format!("/tmp/fabro-exec-{}-{nonce}-{sequence}", std::process::id());
    (format!("{prefix}.stop"), format!("{prefix}.pid"))
}

fn docker_controlled_shell_command(command: &str, stop_file: &str, pid_file: &str) -> String {
    format!(
        "\
stop_file={stop_file}; \
pid_file={pid_file}; \
user_command={command}; \
exec 3<&0; \
rm -f \"$pid_file\"; \
if [ -e \"$stop_file\" ]; then \
  rm -f \"$stop_file\" \"$pid_file\"; \
  exit 143; \
fi; \
( \
  while [ ! -e \"$stop_file\" ]; do sleep {stop_poll_sleep}; done; \
  while [ ! -s \"$pid_file\" ]; do sleep {stop_poll_sleep}; done; \
  child=$(cat \"$pid_file\"); \
  kill -TERM \"-$child\" 2>/dev/null || kill -TERM \"$child\" 2>/dev/null || true; \
  sleep {term_grace}; \
  kill -KILL \"-$child\" 2>/dev/null || kill -KILL \"$child\" 2>/dev/null || true; \
) & watcher=$!; \
if command -v setsid >/dev/null 2>&1; then \
  setsid {bash} -c \"$user_command\" <&3 & \
else \
  {bash} -c \"$user_command\" <&3 & \
fi; \
child=$!; \
exec 3<&-; \
echo \"$child\" > \"$pid_file\"; \
wait \"$child\"; \
status=$?; \
kill \"$watcher\" 2>/dev/null || true; \
wait \"$watcher\" 2>/dev/null || true; \
rm -f \"$stop_file\" \"$pid_file\"; \
exit \"$status\"\
",
        bash = REMOTE_BASH,
        stop_file = shell_quote(stop_file),
        pid_file = shell_quote(pid_file),
        command = shell_quote(command),
        stop_poll_sleep = EXEC_STOP_POLL_SLEEP_SECONDS,
        term_grace = EXEC_TERM_GRACE_SECONDS,
    )
}

fn docker_stdio_exec_options(
    command: String,
    working_dir: String,
    env: Option<Vec<String>>,
) -> (CreateExecOptions<String>, StartExecOptions) {
    let env = clean_bash_env_entries(env.unwrap_or_default());
    (
        CreateExecOptions {
            attach_stdin: Some(true),
            attach_stdout: Some(true),
            attach_stderr: Some(true),
            tty: Some(false),
            cmd: Some(vec![REMOTE_BASH.to_string(), "-c".to_string(), command]),
            working_dir: Some(working_dir),
            env: Some(env),
            ..Default::default()
        },
        StartExecOptions {
            detach:          false,
            tty:             false,
            output_capacity: None,
        },
    )
}

async fn create_and_start_exec(
    docker: &Docker,
    container_id: &str,
    exec_options: CreateExecOptions<String>,
    start_options: Option<StartExecOptions>,
    create_context: &'static str,
    start_context: &'static str,
) -> crate::Result<(String, StartExecResults)> {
    let exec_instance = docker
        .create_exec(container_id, exec_options)
        .await
        .map_err(|err| crate::Error::context(create_context, err))?;
    let exec_id = exec_instance.id;
    let start_result = docker
        .start_exec(&exec_id, start_options)
        .await
        .map_err(|err| crate::Error::context(start_context, err))?;

    Ok((exec_id, start_result))
}

fn docker_stop_request_exec_options(stop_file: &str) -> CreateExecOptions<String> {
    CreateExecOptions {
        cmd: Some(vec![
            REMOTE_BASH.to_string(),
            "-c".to_string(),
            format!("touch {}", shell_quote(stop_file)),
        ]),
        attach_stdout: Some(true),
        attach_stderr: Some(true),
        working_dir: Some("/".to_string()),
        env: Some(clean_bash_env_entries(Vec::new())),
        ..Default::default()
    }
}

async fn request_docker_exec_stop_with(
    docker: &Docker,
    container_id: &str,
    stop_file: &str,
) -> crate::Result<()> {
    let (exec_id, start_result) = create_and_start_exec(
        docker,
        container_id,
        docker_stop_request_exec_options(stop_file),
        None,
        "Failed to create Docker exec stop request",
        "Failed to start Docker exec stop request",
    )
    .await?;

    let mut stdout = String::new();
    let mut stderr = String::new();
    if let StartExecResults::Attached { mut output, .. } = start_result {
        while let Some(chunk) = output.next().await {
            match chunk {
                Ok(LogOutput::StdOut { message }) => {
                    stdout.push_str(&String::from_utf8_lossy(&message));
                }
                Ok(LogOutput::StdErr { message }) => {
                    stderr.push_str(&String::from_utf8_lossy(&message));
                }
                Ok(_) => {}
                Err(e) => {
                    return Err(crate::Error::context(
                        "Error reading stop request output",
                        e,
                    ));
                }
            }
        }
    }

    let inspect = docker
        .inspect_exec(&exec_id)
        .await
        .map_err(|e| crate::Error::context("Failed to inspect Docker exec stop request", e))?;
    let exit_code = inspect
        .exit_code
        .and_then(|code| i32::try_from(code).ok())
        .unwrap_or(-1);
    if exit_code != 0 {
        return Err(crate::Error::message(format!(
            "Failed to request Docker exec stop (exit {exit_code}): {stderr}{stdout}"
        )));
    }
    Ok(())
}

struct DockerStdioProcessControl {
    docker:       Docker,
    container_id: String,
    exec_id:      String,
    stop_file:    String,
    state:        Arc<DockerStdioProcessState>,
}

#[derive(Default)]
struct DockerStdioProcessState {
    stop_requested:     AtomicBool,
    termination:        TokioMutex<Option<StdioProcessTermination>>,
    termination_notify: Notify,
}

impl DockerStdioProcessState {
    async fn cached_termination(&self) -> Option<StdioProcessTermination> {
        *self.termination.lock().await
    }

    async fn request_stop_once(&self) -> bool {
        self.cached_termination().await.is_none()
            && !self.stop_requested.swap(true, Ordering::AcqRel)
    }

    async fn cache_termination(&self, termination: StdioProcessTermination) {
        let mut cached = self.termination.lock().await;
        if cached.is_none() {
            *cached = Some(termination);
            self.termination_notify.notify_waiters();
        }
    }

    async fn wait_for_cached_termination(&self) -> StdioProcessTermination {
        loop {
            if let Some(termination) = self.cached_termination().await {
                return termination;
            }
            self.termination_notify.notified().await;
        }
    }
}

#[async_trait]
impl StdioProcessControl for DockerStdioProcessControl {
    async fn terminate(&self) -> crate::Result<()> {
        if !self.state.request_stop_once().await {
            return Ok(());
        }
        request_docker_exec_stop_with(&self.docker, &self.container_id, &self.stop_file).await?;
        Ok(())
    }

    async fn wait(&self) -> crate::Result<StdioProcessTermination> {
        if let Some(termination) = self.state.cached_termination().await {
            return Ok(termination);
        }

        let mut poll_interval = time::interval(std::time::Duration::from_secs(1));
        loop {
            if let Some(termination) = self.state.cached_termination().await {
                return Ok(termination);
            }
            let inspect = self
                .docker
                .inspect_exec(&self.exec_id)
                .await
                .map_err(|e| crate::Error::context("Failed to inspect Docker stdio exec", e))?;
            if inspect.running != Some(true) {
                let exit_code = inspect.exit_code.and_then(|code| i32::try_from(code).ok());
                let termination = StdioProcessTermination::exited(exit_code);
                self.state.cache_termination(termination).await;
                return Ok(termination);
            }
            tokio::select! {
                termination = self.state.wait_for_cached_termination() => return Ok(termination),
                _ = poll_interval.tick() => {}
            }
        }
    }
}

async fn cache_docker_stdio_completion(
    docker: Docker,
    exec_id: String,
    state: Arc<DockerStdioProcessState>,
) {
    match docker.inspect_exec(&exec_id).await {
        Ok(inspect) if inspect.running != Some(true) => {
            let exit_code = inspect.exit_code.and_then(|code| i32::try_from(code).ok());
            state
                .cache_termination(StdioProcessTermination::exited(exit_code))
                .await;
        }
        Ok(_) => {}
        Err(err) => {
            tracing::warn!(error = %err, "Failed to inspect completed Docker stdio exec");
        }
    }
}

fn git_clone_command(clone_url: &str, branch: Option<&str>, checkout_path: &str) -> String {
    let mut command = "git -c maintenance.auto=0 -c gc.auto=0 clone".to_string();
    if let Some(branch) = branch {
        command.push_str(" --branch ");
        command.push_str(&shell_quote(branch));
        command.push_str(" --single-branch");
    }
    command.push_str(" --depth ");
    command.push_str(&GIT_CLONE_DEPTH.to_string());
    command.push_str(" --no-tags");
    command.push_str(" -- ");
    command.push_str(&shell_quote(clone_url));
    command.push(' ');
    command.push_str(&shell_quote(checkout_path));
    command
}

fn classify_docker_clone_result(
    result: &ExecResult,
    cred: CredentialContext,
) -> Option<git_retry::GitRetryReason> {
    git_retry::classify_output(&result.stderr, &result.stdout, cred).retry_reason()
}

fn host_config(config: &DockerSandboxOptions) -> HostConfig {
    HostConfig {
        binds: None,
        network_mode: config.network_mode.clone(),
        memory: config.memory_limit,
        cpu_quota: config.cpu_quota,
        ..Default::default()
    }
}

fn container_config(config: &DockerSandboxOptions, run_id: Option<&RunId>) -> Config<String> {
    Config {
        image: Some(config.image.clone()),
        cmd: Some(vec![
            REMOTE_BASH.to_string(),
            "-c".to_string(),
            format!(
                "mkdir -p {} && sleep infinity",
                shell_quote(WORKING_DIRECTORY)
            ),
        ]),
        working_dir: Some(WORKING_DIRECTORY.to_string()),
        env: Some(clean_bash_env_entries(config.env_vars.clone())),
        labels: Some(managed_labels::for_run(run_id)),
        host_config: Some(host_config(config)),
        ..Default::default()
    }
}

fn verify_managed_labels(
    container_id: &str,
    labels: &HashMap<String, String>,
    run_id: Option<&RunId>,
) -> crate::Result<()> {
    if labels.get(MANAGED_LABEL).map(String::as_str) != Some("true") {
        return Err(crate::Error::message(format!(
            "Refusing to operate on Docker container '{container_id}' because it is missing label {MANAGED_LABEL}=true"
        )));
    }
    if let Some(run_id) = run_id {
        let actual = labels.get(RUN_ID_LABEL).map(String::as_str);
        let expected = run_id.to_string();
        if actual != Some(expected.as_str()) {
            return Err(crate::Error::message(format!(
                "Refusing to operate on Docker container '{container_id}' because label {RUN_ID_LABEL}={actual:?} does not match run {run_id}"
            )));
        }
    }
    Ok(())
}

fn container_labels(inspect: &ContainerInspectResponse) -> HashMap<String, String> {
    inspect
        .config
        .as_ref()
        .and_then(|config| config.labels.clone())
        .unwrap_or_default()
}

fn activation_action(inspect: &ContainerInspectResponse) -> Option<ContainerStartAction> {
    let Some(state) = inspect.state.as_ref() else {
        return Some(ContainerStartAction::Start);
    };
    if state.running != Some(true) {
        return Some(ContainerStartAction::Start);
    }
    (state.paused == Some(true)).then_some(ContainerStartAction::Unpause)
}

fn docker_not_found(error: &DockerError) -> bool {
    matches!(error, DockerError::DockerResponseServerError {
        status_code: 404,
        ..
    })
}

fn docker_not_modified(error: &DockerError) -> bool {
    matches!(error, DockerError::DockerResponseServerError {
        status_code: 304,
        ..
    })
}

fn bash_remediation(image: &str) -> String {
    format!("Failed to start Docker container from image '{image}'. {DOCKER_BASH_REQUIREMENT}")
}

fn build_single_file_tar(file_name: &str, bytes: &[u8]) -> crate::Result<Vec<u8>> {
    let mut tar_builder = tar::Builder::new(Vec::new());
    let mut header = tar::Header::new_gnu();
    header
        .set_path(file_name)
        .map_err(|e| crate::Error::context("Failed to set tar path", e))?;
    header.set_size(
        u64::try_from(bytes.len())
            .map_err(|_| crate::Error::message("file is too large for tar header"))?,
    );
    header.set_mode(0o644);
    header.set_cksum();
    tar_builder
        .append(&header, bytes)
        .map_err(|e| crate::Error::context("Failed to build tar archive", e))?;
    tar_builder
        .into_inner()
        .map_err(|e| crate::Error::context("Failed to finalize tar archive", e))
}

#[async_trait]
impl Sandbox for DockerSandbox {
    async fn download_file_to_local(
        &self,
        remote_path: &str,
        local_path: &std::path::Path,
    ) -> crate::Result<()> {
        let bytes = self.download_file_bytes(remote_path).await?;
        if let Some(parent) = local_path.parent() {
            fs::create_dir_all(parent)
                .await
                .map_err(|e| crate::Error::context("Failed to create parent dirs", e))?;
        }
        fs::write(local_path, bytes).await.map_err(|e| {
            crate::Error::context(format!("Failed to write {}", local_path.display()), e)
        })
    }

    async fn upload_file_from_local(
        &self,
        local_path: &std::path::Path,
        remote_path: &str,
    ) -> crate::Result<()> {
        let bytes = fs::read(local_path).await.map_err(|e| {
            crate::Error::context(format!("Failed to read {}", local_path.display()), e)
        })?;
        self.upload_bytes_to_container(remote_path, &bytes).await
    }

    async fn initialize(&self) -> crate::Result<()> {
        self.emit(SandboxEvent::Initializing {
            provider: "docker".into(),
        });
        let init_start = Instant::now();

        let pull_start = Instant::now();
        match self.ensure_image().await {
            Ok(EnsureImageOutcome::Skipped) => {}
            Ok(EnsureImageOutcome::AlreadyLocal | EnsureImageOutcome::Pulled) => {
                let pull_duration =
                    u64::try_from(pull_start.elapsed().as_millis()).unwrap_or(u64::MAX);
                self.emit(SandboxEvent::SnapshotReady {
                    name:        self.config.image.clone(),
                    duration_ms: pull_duration,
                });
            }
            Err(e) => {
                self.emit(SandboxEvent::SnapshotFailed {
                    name:   self.config.image.clone(),
                    error:  e.to_string(),
                    causes: e.causes(),
                });
                return Err(self.fail_init(init_start, e));
            }
        }

        let container_name = self
            .ensure_name_available()
            .await
            .map_err(|e| self.fail_init(init_start, e))?;
        let create_options = container_name.map(|name| CreateContainerOptions {
            name,
            platform: None,
        });
        let container = self
            .docker
            .create_container(create_options, container_config(&self.config, self.run_id.as_ref()))
            .await
            .map_err(|e| {
                let message = if matches!(
                    e,
                    DockerError::DockerResponseServerError {
                        status_code: 409,
                        ..
                    }
                ) {
                    "Docker container for run already exists. Remove the stale fabro-run container manually before retrying.".to_string()
                } else {
                    "Failed to create Docker container".to_string()
                };
                self.fail_init(init_start, crate::Error::context(message, e))
            })?;

        let id = container.id.clone();
        self.container_id
            .set(id.clone())
            .map_err(|_| crate::Error::message("Container already initialized"))?;

        self.docker
            .start_container(&id, None::<StartContainerOptions<String>>)
            .await
            .map_err(|e| {
                let err = crate::Error::context(bash_remediation(&self.config.image), e);
                self.fail_init(init_start, err)
            })?;

        if let Err(e) = self.probe_bash(Some(WORKING_DIRECTORY)).await {
            return Err(self.fail_init(init_start, e));
        }

        let (uname_output, _, _) = self
            .docker_exec(vec!["uname".to_string(), "-r".to_string()], None, None)
            .await?;
        let _ = self.cached_platform.set("linux".to_string());
        let _ = self
            .cached_os_version
            .set(format!("linux {}", uname_output.trim()));

        let clone_decision = clone_source::decide_clone(
            self.config.skip_clone,
            self.clone_origin_url.as_deref(),
            self.clone_branch.as_deref(),
        )
        .map_err(|e| self.fail_init(init_start, e))?;

        match clone_decision {
            CloneDecision::EmptyWorkspace { reason } => {
                if matches!(reason, EmptyWorkspaceReason::MissingOrigin) {
                    tracing::warn!(
                        provider = "docker",
                        reason = reason.message(),
                        "Clone source missing for clone-based sandbox"
                    );
                }
                if let Err(e) = self.create_workspace().await {
                    return Err(self.fail_init(init_start, e));
                }
                let _ = self.repo_cloned.set(false);
            }
            CloneDecision::GitHub { origin_url, branch } => {
                if let Err(e) = self.clone_github_repo(origin_url, branch).await {
                    return Err(self.fail_init(init_start, e));
                }
            }
        }

        let init_duration = u64::try_from(init_start.elapsed().as_millis()).unwrap_or(u64::MAX);
        self.emit(SandboxEvent::Ready {
            provider:    "docker".into(),
            duration_ms: init_duration,
            name:        None,
            cpu:         None,
            memory:      None,
            url:         None,
        });

        Ok(())
    }

    async fn start(&self) -> crate::Result<()> {
        let started = self.begin_start();
        let container_id = self.container_id()?.to_string();
        let inspect = match self.inspect_container(&container_id).await {
            Ok(inspect) => inspect,
            Err(error) => return self.start_error(error),
        };
        let labels = container_labels(&inspect);
        if let Err(error) = verify_managed_labels(&container_id, &labels, self.run_id.as_ref()) {
            return self.start_error(error);
        }
        let action = activation_action(&inspect).unwrap_or(ContainerStartAction::Start);
        self.complete_start(started, &container_id, &labels, action)
            .await
    }

    async fn activate(&self) -> crate::Result<()> {
        let container_id = self.container_id()?.to_string();
        let inspect = self.inspect_container(&container_id).await?;
        let labels = container_labels(&inspect);
        verify_managed_labels(&container_id, &labels, self.run_id.as_ref())?;
        let Some(action) = activation_action(&inspect) else {
            return Ok(());
        };
        match action {
            ContainerStartAction::Unpause => {
                self.set_container_running(&container_id, &labels, action)
                    .await
            }
            ContainerStartAction::Start => {
                let started = self.begin_start();
                self.complete_start(started, &container_id, &labels, action)
                    .await
            }
        }
    }

    async fn stop(&self) -> crate::Result<()> {
        self.emit(SandboxEvent::StopStarted {
            provider: "docker".into(),
        });
        let start = Instant::now();

        let Some(container_id) = self.container_id.get().cloned() else {
            let duration_ms = elapsed_ms(start);
            self.emit(SandboxEvent::StopCompleted {
                provider: "docker".into(),
                duration_ms,
            });
            return Ok(());
        };

        let labels = match self.inspect_labels(&container_id).await {
            Ok(labels) => labels,
            Err(e) => return self.stop_error(e),
        };
        if let Err(e) = verify_managed_labels(&container_id, &labels, self.run_id.as_ref()) {
            return self.stop_error(e);
        }

        let stop_opts = StopContainerOptions { t: 1 };
        if let Err(e) = self
            .docker
            .stop_container(&container_id, Some(stop_opts))
            .await
        {
            if !docker_not_found(&e) && !docker_not_modified(&e) {
                return self.stop_error(crate::Error::context(
                    format!(
                        "Failed to stop Docker container '{container_id}' with labels {labels:?}"
                    ),
                    e,
                ));
            }
        }

        let duration_ms = elapsed_ms(start);
        self.emit(SandboxEvent::StopCompleted {
            provider: "docker".into(),
            duration_ms,
        });

        Ok(())
    }

    async fn delete(&self) -> crate::Result<()> {
        self.emit(SandboxEvent::DeleteStarted {
            provider: "docker".into(),
        });
        let start = Instant::now();

        let Some(container_id) = self.container_id.get().cloned() else {
            let duration_ms = elapsed_ms(start);
            self.emit(SandboxEvent::DeleteCompleted {
                provider: "docker".into(),
                duration_ms,
            });
            return Ok(());
        };

        let labels = match self.inspect_labels(&container_id).await {
            Ok(labels) => labels,
            Err(e) => return self.delete_error(e),
        };
        if let Err(e) = verify_managed_labels(&container_id, &labels, self.run_id.as_ref()) {
            return self.delete_error(e);
        }

        let remove_opts = RemoveContainerOptions {
            force: true,
            ..Default::default()
        };
        if let Err(e) = self
            .docker
            .remove_container(&container_id, Some(remove_opts))
            .await
        {
            if !docker_not_found(&e) {
                return self.delete_error(crate::Error::context(
                    format!(
                        "Failed to remove Docker container '{container_id}' with labels {labels:?}"
                    ),
                    e,
                ));
            }
        }

        let duration_ms = elapsed_ms(start);
        self.emit(SandboxEvent::DeleteCompleted {
            provider: "docker".into(),
            duration_ms,
        });

        Ok(())
    }

    async fn cleanup(&self) -> crate::Result<()> {
        self.delete().await
    }

    async fn exec_command(
        &self,
        command: &str,
        timeout_ms: u64,
        working_dir: Option<&str>,
        env_vars: Option<&HashMap<String, String>>,
        cancel_token: Option<CancellationToken>,
    ) -> crate::Result<ExecResult> {
        let dir = working_dir.map(|path| self.resolve_container_path(path));
        self.docker_exec_shell(command, timeout_ms, dir.as_deref(), env_vars, cancel_token)
            .await
    }

    async fn exec_command_streaming(
        &self,
        request: ExecStreamingRequest<'_>,
    ) -> crate::Result<ExecStreamingResult> {
        let dir = request
            .working_dir
            .map(|path| self.resolve_container_path(path));
        self.docker_exec_shell_streaming(ExecStreamingRequest {
            working_dir: dir.as_deref(),
            ..request
        })
        .await
    }

    async fn spawn_stdio_process(
        &self,
        command: &str,
        working_dir: Option<&str>,
        env_vars: Option<&HashMap<String, String>>,
        cancel_token: Option<CancellationToken>,
    ) -> crate::Result<StdioProcess> {
        let effective_dir = working_dir.map_or_else(
            || self.working_directory().to_string(),
            |path| self.resolve_container_path(path),
        );
        let env = env_vars.map(|vars| {
            vars.iter()
                .map(|(key, value)| format!("{key}={value}"))
                .collect()
        });
        let (stop_file, pid_file) = docker_exec_control_paths();
        let controlled_command = docker_controlled_shell_command(command, &stop_file, &pid_file);
        let (create_opts, start_opts) =
            docker_stdio_exec_options(controlled_command, effective_dir, env);

        let container_id = self.container_id()?.to_string();
        let (exec_id, start_result) = create_and_start_exec(
            &self.docker,
            &container_id,
            create_opts,
            Some(start_opts),
            "Failed to create Docker stdio exec",
            "Failed to start Docker stdio exec",
        )
        .await?;

        let StartExecResults::Attached { mut output, input } = start_result else {
            return Err(crate::Error::message(
                "Docker stdio exec started detached unexpectedly",
            ));
        };

        let stderr_collector = StderrCollector::new(DEFAULT_EXEC_OUTPUT_TAIL_BYTES);
        let stderr_for_output = stderr_collector.clone();
        let (mut stdout_writer, stdout_reader) = duplex(64 * 1024);
        let state = Arc::new(DockerStdioProcessState::default());
        let state_for_output = Arc::clone(&state);
        let docker_for_output = self.docker.clone();
        let exec_id_for_output = exec_id.clone();
        tokio::spawn(async move {
            while let Some(chunk) = output.next().await {
                match chunk {
                    Ok(LogOutput::StdOut { message }) => {
                        if let Err(err) = stdout_writer.write_all(&message).await {
                            tracing::warn!(error = %err, "Failed to forward Docker stdio stdout");
                            break;
                        }
                    }
                    Ok(LogOutput::StdErr { message }) => {
                        stderr_for_output.push(&message).await;
                    }
                    Ok(_) => {}
                    Err(err) => {
                        let message = format!("Docker stdio output stream error: {err}");
                        stderr_for_output.push(message.as_bytes()).await;
                        break;
                    }
                }
            }
            cache_docker_stdio_completion(docker_for_output, exec_id_for_output, state_for_output)
                .await;
        });

        let handle = StdioProcessHandle::new(DockerStdioProcessControl {
            docker: self.docker.clone(),
            container_id,
            exec_id,
            stop_file,
            state,
        });

        if let Some(token) = cancel_token {
            let handle_for_cancel = handle.clone();
            tokio::spawn(async move {
                token.cancelled().await;
                if let Err(err) = handle_for_cancel.terminate().await {
                    tracing::warn!(error = %err, "Failed to terminate cancelled Docker stdio exec");
                }
            });
        }

        Ok(StdioProcess {
            stdin: input,
            stdout: Box::pin(stdout_reader),
            stderr: stderr_collector,
            handle,
        })
    }

    async fn read_file_bytes(&self, path: &str) -> crate::Result<Vec<u8>> {
        self.download_file_bytes(path).await
    }

    async fn read_file(
        &self,
        path: &str,
        offset: Option<usize>,
        limit: Option<usize>,
    ) -> crate::Result<String> {
        let container_path = self.resolve_container_path(path);
        let (stdout, stderr, exit_code) = self
            .docker_exec(vec!["cat".to_string(), container_path.clone()], None, None)
            .await?;

        if exit_code != 0 {
            return Err(crate::Error::message(format!(
                "Failed to read {container_path}: {stderr}"
            )));
        }

        Ok(format_lines_numbered(&stdout, offset, limit))
    }

    async fn write_file(&self, path: &str, content: &str) -> crate::Result<()> {
        self.upload_bytes_to_container(path, content.as_bytes())
            .await
    }

    async fn delete_file(&self, path: &str) -> crate::Result<()> {
        let container_path = self.resolve_container_path(path);
        let (_, stderr, exit_code) = self
            .docker_exec(
                vec!["rm".to_string(), "-f".to_string(), container_path.clone()],
                None,
                None,
            )
            .await?;

        if exit_code != 0 {
            return Err(crate::Error::message(format!(
                "Failed to delete {container_path}: {stderr}"
            )));
        }
        Ok(())
    }

    async fn file_exists(&self, path: &str) -> crate::Result<bool> {
        let container_path = self.resolve_container_path(path);
        let (_, _, exit_code) = self
            .docker_exec(
                vec!["test".to_string(), "-e".to_string(), container_path],
                None,
                None,
            )
            .await?;

        Ok(exit_code == 0)
    }

    async fn list_directory(
        &self,
        path: &str,
        depth: Option<usize>,
    ) -> crate::Result<Vec<DirEntry>> {
        let container_path = self.resolve_container_path(path);
        let max_depth = depth.unwrap_or(1);
        let (stdout, stderr, exit_code) = self
            .docker_exec(
                vec![
                    "find".to_string(),
                    container_path.clone(),
                    "-mindepth".to_string(),
                    "1".to_string(),
                    "-maxdepth".to_string(),
                    max_depth.to_string(),
                    "-printf".to_string(),
                    "%y\t%s\t%P\n".to_string(),
                ],
                None,
                None,
            )
            .await?;

        if exit_code != 0 {
            return Err(crate::Error::message(format!(
                "Failed to list directory {container_path}: {stderr}"
            )));
        }

        let mut entries: Vec<DirEntry> = stdout
            .lines()
            .filter(|line| !line.is_empty())
            .filter_map(|line| {
                let parts: Vec<&str> = line.splitn(3, '\t').collect();
                if parts.len() < 3 {
                    return None;
                }
                let file_type = parts[0];
                let size: Option<u64> = parts[1].parse().ok();
                let name = parts[2].to_string();
                let is_dir = file_type == "d";
                Some(DirEntry {
                    name,
                    is_dir,
                    size: if is_dir { None } else { size },
                })
            })
            .collect();

        entries.sort_by(|a, b| a.name.cmp(&b.name));
        Ok(entries)
    }

    async fn grep(
        &self,
        pattern: &str,
        path: &str,
        options: &GrepOptions,
    ) -> crate::Result<Vec<String>> {
        let container_path = self.resolve_container_path(path);
        let use_rg = *self
            .rg_available
            .get_or_init(|| async {
                let result = self
                    .docker_exec(vec!["which".to_string(), "rg".to_string()], None, None)
                    .await;
                matches!(result, Ok((_, _, 0)))
            })
            .await;

        let command = if use_rg {
            let mut command = "rg -n".to_string();
            if options.case_insensitive {
                command.push_str(" -i");
            }
            if let Some(ref glob_filter) = options.glob_filter {
                command.push_str(" --glob ");
                command.push_str(&shell_quote(glob_filter));
            }
            if let Some(max) = options.max_results {
                let _ = write!(&mut command, " -m {max}");
            }
            command.push_str(" -- ");
            command.push_str(&shell_quote(pattern));
            command.push(' ');
            command.push_str(&shell_quote(&container_path));
            command
        } else {
            let mut command = "grep -rn".to_string();
            if options.case_insensitive {
                command.push_str(" -i");
            }
            if let Some(ref glob_filter) = options.glob_filter {
                command.push_str(" --include ");
                command.push_str(&shell_quote(glob_filter));
            }
            if let Some(max) = options.max_results {
                let _ = write!(&mut command, " -m {max}");
            }
            command.push_str(" -- ");
            command.push_str(&shell_quote(pattern));
            command.push(' ');
            command.push_str(&shell_quote(&container_path));
            command
        };

        let result = self
            .docker_exec_shell(&command, 30_000, None, None, None)
            .await?;
        if result.exit_code == Some(1) {
            return Ok(Vec::new());
        }
        if !result.is_success() {
            return Err(crate::Error::message(format!(
                "grep failed (exit {}): {}",
                result.display_exit_code(),
                result.stderr
            )));
        }

        Ok(result
            .stdout
            .lines()
            .map(String::from)
            .filter(|line| !line.is_empty())
            .collect())
    }

    async fn walk_files(
        &self,
        base: &str,
        relative_start: &str,
        options: &WalkOptions,
    ) -> crate::Result<Vec<SandboxFile>> {
        if options.excludes_relative_path(relative_start) {
            return Ok(Vec::new());
        }

        let base = self.resolve_container_path(base);
        let command = sandbox::build_remote_walk_command(&base, relative_start, options);
        let result = self
            .docker_exec_shell(&command, REMOTE_WALK_TIMEOUT_MS, None, None, None)
            .await?;
        if !result.is_success() {
            return Err(crate::Error::exec("recursive file traversal", result));
        }

        sandbox::parse_remote_walk_output(&base, relative_start, &result.stdout)
    }

    fn working_directory(&self) -> &str {
        self.working_directory
            .get()
            .map_or(WORKING_DIRECTORY, String::as_str)
    }

    async fn ssh_access_command(&self) -> crate::Result<Option<String>> {
        Ok(Some(docker_access_command(
            self.container_id()?,
            self.working_directory(),
        )))
    }

    fn platform(&self) -> &str {
        self.cached_platform.get().map_or("linux", String::as_str)
    }

    fn os_version(&self) -> String {
        self.cached_os_version
            .get()
            .cloned()
            .unwrap_or_else(|| "linux".to_string())
    }

    fn sandbox_info(&self) -> String {
        self.container_id.get().cloned().unwrap_or_default()
    }

    async fn setup_git(
        &self,
        intent: &crate::GitSetupIntent,
    ) -> crate::Result<Option<crate::GitRunInfo>> {
        if !self.repo_cloned() {
            return Ok(None);
        }
        crate::setup_git_via_exec(self, intent).await.map(Some)
    }

    fn resume_setup_commands(&self, run_branch: &str) -> Vec<String> {
        if !self.repo_cloned() {
            return Vec::new();
        }
        vec![format!(
            "git fetch origin {} && git checkout {}",
            shell_quote(run_branch),
            shell_quote(run_branch)
        )]
    }

    async fn git_push_ref(
        &self,
        refspec: &str,
        plan: &crate::RetryPlan,
    ) -> Result<crate::PushReport, crate::PushError> {
        if !self.repo_cloned() {
            return Ok(crate::PushReport::default());
        }
        let credentials = self
            .origin_url
            .get()
            .map(|origin_url| (&self.push_credentials, origin_url.as_str()));
        sandbox::git_push_via_exec(self, credentials, refspec, plan).await
    }

    fn origin_url(&self) -> Option<&str> {
        if !self.repo_cloned() {
            return None;
        }
        self.origin_url.get().map(String::as_str)
    }

    #[tracing::instrument(name = "git_op", skip_all, fields(op = "refresh-credentials"))]
    async fn refresh_push_credentials(&self) -> crate::Result<RefreshOutcome> {
        if !self.repo_cloned() {
            return Ok(RefreshOutcome::none());
        }
        let Some(origin_url) = self.origin_url.get() else {
            return Ok(RefreshOutcome::none());
        };
        self.push_credentials
            .refresh(origin_url, |auth_url| {
                push_credentials::set_auth_url_via_exec(self, auth_url)
            })
            .await
    }

    fn push_token_source(&self) -> Option<Arc<InstallationTokenSource>> {
        self.push_credentials.source().cloned()
    }
}

#[cfg(test)]
mod tests {
    #[expect(
        clippy::disallowed_types,
        reason = "unit test reads an in-memory tar entry synchronously"
    )]
    use std::io::Read as _;
    use std::process::Stdio;
    use std::time::Duration;

    use bollard::API_DEFAULT_VERSION;
    use httpmock::Method::{GET, POST};
    use httpmock::MockServer;
    use tokio::io::AsyncWriteExt as _;
    use tokio::process::Command;

    use super::*;
    use crate::sandbox::{BASH_PROBE_MARKER, bash_probe_passed};

    #[test]
    fn remote_walk_command_only_uses_find_for_traversal() {
        let command = sandbox::build_remote_walk_command("/workspace", ".ai", &WalkOptions {
            excluded_directory_names: vec!["target".to_string(), "node_modules".to_string()],
        });

        assert!(command.contains("[ ! -L /workspace/.ai ]"));
        assert!(command.contains("find -H /workspace/.ai"));
        assert!(command.contains("-name target"));
        assert!(command.contains("-name node_modules"));
        assert!(command.contains("-printf '%s\\0%P\\0'"));
        assert!(!command.contains("*.md"));
    }

    #[test]
    fn remote_walk_output_is_relative_to_the_declared_base() {
        let files =
            sandbox::parse_remote_walk_output("/workspace", ".ai/reports", "12\0result.md\0")
                .unwrap();

        assert_eq!(files, vec![SandboxFile {
            path:          "/workspace/.ai/reports/result.md".to_string(),
            relative_path: ".ai/reports/result.md".to_string(),
            size:          12,
        }]);
    }

    #[test]
    fn per_run_container_idle_command_uses_non_login_bash() {
        let config = container_config(&DockerSandboxOptions::default(), None);

        assert_eq!(
            config.cmd.as_ref().map(|cmd| &cmd[..2]),
            Some(&["/bin/bash".to_string(), "-c".to_string()][..])
        );
        assert_eq!(config.env, Some(vec!["BASH_ENV=".to_string()]));
    }

    #[test]
    fn stop_request_exec_uses_non_login_bash() {
        // Provider-control commands share the interpreter contract, so a
        // profile file can't change how a stop request behaves.
        let options = docker_stop_request_exec_options("/tmp/fabro exec.stop");

        assert_eq!(
            options.cmd,
            Some(vec![
                "/bin/bash".to_string(),
                "-c".to_string(),
                "touch '/tmp/fabro exec.stop'".to_string(),
            ])
        );
        assert_eq!(options.env, Some(vec!["BASH_ENV=".to_string()]));
    }

    #[test]
    fn bash_exec_env_blanks_caller_bash_env() {
        let env = docker_bash_exec_env(Some(&HashMap::from([
            (
                BASH_ENV_VAR.to_string(),
                "/tmp/untrusted-startup".to_string(),
            ),
            ("MODE".to_string(), "test".to_string()),
        ])));

        assert!(env.contains(&"MODE=test".to_string()));
        assert_eq!(
            env.iter()
                .filter(|entry| env_entry_name(entry) == BASH_ENV_VAR)
                .map(String::as_str)
                .collect::<Vec<_>>(),
            vec!["BASH_ENV="]
        );
    }

    #[test]
    fn controlled_shell_command_starts_its_child_with_non_login_bash() {
        let script = docker_controlled_shell_command("echo hi", "/tmp/stop", "/tmp/pid");

        assert!(
            script.contains("setsid /bin/bash -c \"$user_command\"")
                && script.contains("/bin/bash -c \"$user_command\""),
            "controlled child should run the user command under non-login bash: {script}"
        );
        assert!(
            !script.contains("-lc") && !script.contains("bash -l"),
            "controlled shell script should contain no login invocation: {script}"
        );
    }

    #[test]
    fn bash_probe_is_shared_by_fresh_initialization_and_resumed_start() {
        // Both lifecycle paths call `probe_bash`, so they cannot drift: a
        // resumed container is checked exactly as strictly as a fresh one.
        assert!(BASH_PROBE_SCRIPT.contains("BASH_VERSION"));
        assert!(BASH_PROBE_SCRIPT.contains("login_shell"));
        assert!(
            !bash_probe_passed(Some(0), "ready"),
            "a zero exit without the marker is not a successful probe"
        );
        assert!(bash_probe_passed(
            Some(0),
            &format!("{BASH_PROBE_MARKER}\n")
        ));
    }

    #[tokio::test]
    async fn activate_unpauses_paused_running_container() {
        let server = MockServer::start_async().await;
        let inspect = server
            .mock_async(|when, then| {
                when.method(GET)
                    .path_suffix("/containers/test-container/json");
                then.status(200)
                    .header("content-type", "application/json")
                    .json_body(serde_json::json!({
                        "Config": {
                            "Labels": managed_labels::for_run(None)
                        },
                        "State": {
                            "Running": true,
                            "Paused": true
                        }
                    }));
            })
            .await;
        let unpause = server
            .mock_async(|when, then| {
                when.method(POST)
                    .path_suffix("/containers/test-container/unpause");
                then.status(204);
            })
            .await;
        let start = server
            .mock_async(|when, then| {
                when.method(POST)
                    .path_suffix("/containers/test-container/start");
                then.status(204);
            })
            .await;
        let docker = Docker::connect_with_http(&server.base_url(), 5, API_DEFAULT_VERSION)
            .expect("mock Docker client should connect");
        let sandbox = test_docker_sandbox(docker, "test-container");

        sandbox
            .activate()
            .await
            .expect("a paused running container should be unpaused");

        inspect.assert_calls_async(1).await;
        unpause.assert_calls_async(1).await;
        start.assert_calls_async(0).await;
    }

    #[test]
    fn default_options_are_clone_based() {
        let options = DockerSandboxOptions::default();
        assert_eq!(options.image, "buildpack-deps:noble");
        assert_eq!(options.network_mode.as_deref(), Some("bridge"));
        assert!(!options.skip_clone);
    }

    #[test]
    fn clone_command_uses_depth_ten_without_tags_for_branch_clone() {
        let command = git_clone_command(
            "https://github.com/fabro-sh/fabro",
            Some("main"),
            "/repos/fabro-sh/fabro",
        );
        assert_eq!(
            command,
            "git -c maintenance.auto=0 -c gc.auto=0 clone --branch main --single-branch --depth 10 --no-tags -- https://github.com/fabro-sh/fabro /repos/fabro-sh/fabro"
        );
    }

    #[test]
    fn clone_result_uses_stderr_before_stdout() {
        let result = ExecResult {
            stdout:      "Could not resolve host: github.com".to_string(),
            stderr:      "fatal: destination path 'fabro' already exists".to_string(),
            exit_code:   Some(128),
            termination: CommandTermination::Exited,
            duration_ms: 1,
        };

        assert_eq!(
            classify_docker_clone_result(&result, CredentialContext::FreshApp),
            None
        );
    }

    #[test]
    fn container_config_has_no_bind_mounts_or_socket() {
        let options = DockerSandboxOptions {
            env_vars: vec![
                "FOO=bar".to_string(),
                "BASH_ENV=/tmp/untrusted-startup".to_string(),
            ],
            memory_limit: Some(4_000_000_000),
            cpu_quota: Some(200_000),
            ..DockerSandboxOptions::default()
        };
        let config = container_config(&options, None);
        let host_config = config.host_config.expect("host config");
        assert!(host_config.binds.is_none());
        assert_eq!(host_config.memory, Some(4_000_000_000));
        assert_eq!(host_config.cpu_quota, Some(200_000));
        assert_eq!(config.working_dir.as_deref(), Some(WORKING_DIRECTORY));
        assert_eq!(
            config.env,
            Some(vec!["FOO=bar".to_string(), "BASH_ENV=".to_string()])
        );
        assert!(
            config
                .env
                .unwrap()
                .iter()
                .all(|value| !value.starts_with("DOCKER_HOST="))
        );
    }

    #[test]
    fn real_run_container_gets_name_and_labels() {
        let run_id: RunId = "01HY0000000000000000000000".parse().unwrap();
        assert_eq!(
            container_name(&run_id),
            "fabro-run-01HY0000000000000000000000"
        );
        let labels = managed_labels::for_run(Some(&run_id));
        assert_eq!(labels.get(MANAGED_LABEL).map(String::as_str), Some("true"));
        assert_eq!(
            labels.get(RUN_ID_LABEL).map(String::as_str),
            Some("01HY0000000000000000000000")
        );
    }

    #[test]
    fn docker_access_command_uses_exec_in_workspace() {
        assert_eq!(
            docker_access_command(
                "fabro-run-01HY0000000000000000000000",
                "/workspace/rack-test"
            ),
            "docker exec -it fabro-run-01HY0000000000000000000000 sh -lc 'cd /workspace/rack-test && exec sh -l'"
        );
    }

    #[test]
    fn docker_access_command_quotes_container_identifier() {
        assert_eq!(
            docker_access_command("container with spaces", "/workspace/repo with spaces"),
            "docker exec -it 'container with spaces' sh -lc \"cd '/workspace/repo with spaces' && exec sh -l\""
        );
    }

    #[test]
    fn stdio_exec_options_attach_streams_without_tty() {
        let (create, start) = docker_stdio_exec_options(
            "python fake_agent.py".to_string(),
            WORKING_DIRECTORY.to_string(),
            Some(vec![
                "MODE=test".to_string(),
                "BASH_ENV=/tmp/untrusted-startup".to_string(),
            ]),
        );

        assert_eq!(create.attach_stdin, Some(true));
        assert_eq!(create.attach_stdout, Some(true));
        assert_eq!(create.attach_stderr, Some(true));
        assert_eq!(create.tty, Some(false));
        assert_eq!(create.working_dir.as_deref(), Some(WORKING_DIRECTORY));
        assert_eq!(
            create.env,
            Some(vec!["MODE=test".to_string(), "BASH_ENV=".to_string()])
        );
        assert_eq!(
            create.cmd,
            Some(vec![
                "/bin/bash".to_string(),
                "-c".to_string(),
                "python fake_agent.py".to_string()
            ])
        );
        assert!(!start.detach);
        assert!(!start.tty);
        assert_eq!(start.output_capacity, None);
    }

    #[tokio::test]
    async fn controlled_shell_command_honors_stop_requested_before_pid_file_exists() {
        let tempdir = tempfile::tempdir().expect("tempdir should be created");
        let stop_file = tempdir.path().join("stop");
        let pid_file = tempdir.path().join("pid");
        let block_fifo = tempdir.path().join("block");
        let stop_file = stop_file.to_string_lossy().into_owned();
        let pid_file = pid_file.to_string_lossy().into_owned();
        let block_fifo = block_fifo.to_string_lossy().into_owned();
        let marker = "fabro_controlled_shell_stop_sentinel";
        let command = docker_controlled_shell_command(
            &format!(
                "mkfifo {}; trap '' HUP TERM; read _ < {} # {marker}",
                shell_quote(&block_fifo),
                shell_quote(&block_fifo)
            ),
            &stop_file,
            &pid_file,
        );

        fs::write(&stop_file, b"")
            .await
            .expect("early stop file should be written");
        let mut child = Command::new("/bin/bash");
        child.arg("-c").arg(command).kill_on_drop(true);
        let output = if let Ok(output) = time::timeout(Duration::from_secs(5), child.output()).await
        {
            output.expect("controlled shell command should run")
        } else {
            kill_processes_with_marker(marker).await;
            panic!("controlled shell command should honor an early stop request");
        };

        assert!(
            !output.status.success(),
            "controlled shell command should be terminated by the stop request"
        );
        let matching_processes = processes_with_marker(marker).await;
        assert!(
            matching_processes.is_empty(),
            "controlled shell command should not leave child processes: {matching_processes:?}"
        );
    }

    #[tokio::test]
    async fn controlled_shell_command_preserves_stdin_for_user_command() {
        let tempdir = tempfile::tempdir().expect("tempdir should be created");
        let stop_file = tempdir.path().join("stop");
        let pid_file = tempdir.path().join("pid");
        let stop_file = stop_file.to_string_lossy().into_owned();
        let pid_file = pid_file.to_string_lossy().into_owned();
        let command = docker_controlled_shell_command("cat", &stop_file, &pid_file);

        let mut child = Command::new("/bin/bash")
            .arg("-c")
            .arg(command)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .kill_on_drop(true)
            .spawn()
            .expect("controlled shell command should spawn");
        let mut stdin = child
            .stdin
            .take()
            .expect("controlled shell command stdin should be piped");
        stdin
            .write_all(b"abc\n")
            .await
            .expect("stdin should be written");
        drop(stdin);

        let output = time::timeout(Duration::from_secs(5), child.wait_with_output())
            .await
            .expect("controlled shell command should not hang")
            .expect("controlled shell command should run");

        assert!(
            output.status.success(),
            "controlled shell command should exit successfully: {output:?}"
        );
        assert_eq!(output.stdout, b"abc\n");
    }

    #[tokio::test]
    async fn docker_stdio_process_state_does_not_cache_cancelled_on_stop_request() {
        let state = DockerStdioProcessState::default();

        assert!(state.request_stop_once().await);
        assert_eq!(state.cached_termination().await, None);
        assert!(!state.request_stop_once().await);

        let termination = StdioProcessTermination::exited(Some(143));
        state.cache_termination(termination).await;
        assert_eq!(state.cached_termination().await, Some(termination));
        assert!(!state.request_stop_once().await);
    }

    #[tokio::test]
    async fn controlled_shell_command_skips_user_command_when_stop_already_requested() {
        let tempdir = tempfile::tempdir().expect("tempdir should be created");
        let stop_file = tempdir.path().join("stop");
        let pid_file = tempdir.path().join("pid");
        let marker_file = tempdir.path().join("started");
        let stop_file = stop_file.to_string_lossy().into_owned();
        let pid_file = pid_file.to_string_lossy().into_owned();
        let marker_file = marker_file.to_string_lossy().into_owned();
        let command = docker_controlled_shell_command(
            &format!("touch {}", shell_quote(&marker_file)),
            &stop_file,
            &pid_file,
        );

        fs::write(&stop_file, b"")
            .await
            .expect("early stop file should be written");
        let output = Command::new("/bin/bash")
            .arg("-c")
            .arg(command)
            .output()
            .await
            .expect("controlled shell command should run");

        assert!(
            !output.status.success(),
            "controlled shell command should exit as stopped"
        );
        assert!(
            !fs::try_exists(&marker_file)
                .await
                .expect("marker file existence should be checked"),
            "controlled shell command should not start user command after an early stop"
        );
    }

    async fn processes_with_marker(marker: &str) -> Vec<String> {
        let output = Command::new("ps")
            .args(["-eo", "pid=,args="])
            .output()
            .await
            .expect("process probe should run");
        String::from_utf8_lossy(&output.stdout)
            .lines()
            .filter(|line| line.contains(marker))
            .map(str::to_string)
            .collect()
    }

    async fn kill_processes_with_marker(marker: &str) {
        for line in processes_with_marker(marker).await {
            if let Some(pid) = line.split_whitespace().next() {
                let _ = Command::new("kill").args(["-KILL", pid]).status().await;
            }
        }
    }

    #[test]
    fn label_validation_rejects_unmanaged_container() {
        let labels = HashMap::new();
        let error = verify_managed_labels("abc", &labels, None).unwrap_err();
        assert!(
            error
                .to_string()
                .contains("missing label sh.fabro.managed=true")
        );
    }

    #[test]
    fn single_file_tar_contains_named_file() {
        let bytes = build_single_file_tar("nested.txt", b"hello").unwrap();
        let mut archive = tar::Archive::new(Cursor::new(bytes));
        let mut entries = archive.entries().unwrap();
        let mut entry = entries.next().unwrap().unwrap();
        assert_eq!(entry.path().unwrap().to_string_lossy(), "nested.txt");
        let mut content = String::new();
        entry.read_to_string(&mut content).unwrap();
        assert_eq!(content, "hello");
    }

    fn test_docker_sandbox(docker: Docker, container_id: &str) -> DockerSandbox {
        let sandbox = DockerSandbox::with_docker_client(
            docker,
            DockerSandboxOptions::default(),
            None,
            None,
            None,
            None,
        )
        .expect("test sandbox should build");
        sandbox
            .container_id
            .set(container_id.to_string())
            .expect("test container should initialize once");
        sandbox
    }
}
