pub struct Host { /* private fields */ }Expand description
The injectable side-effect seam (ADR-0008). Construct once per session, share behind
an Arc; holds the jail root + policy and the exec/truncation limits.
Implementations§
Source§impl Host
impl Host
Sourcepub async fn read_file(
&self,
cwd: &Path,
path: &Path,
) -> Result<FileRead, FsError>
pub async fn read_file( &self, cwd: &Path, path: &Path, ) -> Result<FileRead, FsError>
Read a file (jail-resolved), returning its lossy-UTF-8 contents + stat.
§Errors
FsError::Path if the path escapes the jail; FsError::Io if the read fails.
Sourcepub async fn write_file(
&self,
cwd: &Path,
path: &Path,
contents: &str,
) -> Result<FileStat, FsError>
pub async fn write_file( &self, cwd: &Path, path: &Path, contents: &str, ) -> Result<FileStat, FsError>
Create-or-overwrite a file (jail-resolved), returning its post-write stat.
Does not auto-create parent directories (a mistyped nested path silently creating dirs is a footgun); a missing parent surfaces as an IO error.
§Errors
FsError::Path if the path escapes the jail; FsError::Io if the write fails.
Sourcepub async fn create_dir(
&self,
cwd: &Path,
path: &Path,
parents: bool,
exist_ok: bool,
) -> Result<(), FsError>
pub async fn create_dir( &self, cwd: &Path, path: &Path, parents: bool, exist_ok: bool, ) -> Result<(), FsError>
Create a directory (jail-resolved) — an explicit, opt-in mkdir, separate
from Host::write_file, which deliberately never auto-creates dirs. A
pack calls this when it wants a harness’s real “create the parent dir on
write” behavior (e.g. Claude Code’s Edit/Write), without changing the
footgun-avoidance default for everyone else.
parents— create missing intermediate dirs (mkdir -p); whenfalse, a missing parent is an error.exist_ok— succeed if the target dir already exists; whenfalse, an existing target is an error (mkdirwithout-p’s tolerance).
§Errors
FsError::Path if the path escapes the jail; FsError::Io on a missing
parent (when !parents), an existing target (when !exist_ok), or any other
IO failure.
Sourcepub async fn remove_file(&self, cwd: &Path, path: &Path) -> Result<(), FsError>
pub async fn remove_file(&self, cwd: &Path, path: &Path) -> Result<(), FsError>
Remove a single file (jail-resolved). Never recursive — a directory target
surfaces as an IO error (codex’s apply_patch refuses to delete a directory).
The explicit delete seam for a pack that ports a real “delete file” behavior
(codex apply_patch’s *** Delete File: and the source side of *** Move to:).
§Errors
FsError::Path if the path escapes the jail; FsError::Io if the removal
fails (missing file, a directory target, or any other IO failure).
Sourcepub async fn stat(&self, cwd: &Path, path: &Path) -> Result<FileStat, FsError>
pub async fn stat(&self, cwd: &Path, path: &Path) -> Result<FileStat, FsError>
Stat a file (jail-resolved).
§Errors
FsError::Path if the path escapes the jail; FsError::Io if the stat fails.
Source§impl Host
impl Host
Sourcepub async fn resolve_in_jail(
&self,
cwd: &Path,
candidate: &Path,
) -> Result<PathBuf, PathError>
pub async fn resolve_in_jail( &self, cwd: &Path, candidate: &Path, ) -> Result<PathBuf, PathError>
Resolve candidate (absolute, ~-prefixed, or relative to cwd) to a concrete
absolute path.
A leading ~ or ~/ is expanded against $HOME before anything else, so a
model-supplied ~/.config/x names the real home path instead of a literal ~
directory under cwd. Only ~ and ~/… expand; ~user is left alone, and with
no $HOME the path is unchanged. Expansion is not a permission:
the expanded path still faces the jail below, and under PathPolicy::Jailed a
home path outside the workspace is rejected as an escape — the point is that the
rejection now names the path the caller actually meant.
Under PathPolicy::Jailed the result is guaranteed to live under the workspace
root — .., absolute, and symlink escapes are rejected — while paths whose
leaf does not yet exist (create-new-file) are still allowed. Under
PathPolicy::Unrestricted the path is only made absolute; nothing is rejected.
§Errors
PathError::Escape if a jailed path escapes the root; PathError::Io if an
ancestor cannot be canonicalized.
Source§impl Host
impl Host
Sourcepub async fn exec(
&self,
req: ExecRequest,
cancel: &CancellationToken,
) -> Result<ExecOutput, ExecError>
pub async fn exec( &self, req: ExecRequest, cancel: &CancellationToken, ) -> Result<ExecOutput, ExecError>
Run req through the shell, capturing output under the host’s limits and honoring
cooperative cancel.
A command that fails, times out, or is cancelled is a successful capture with the corresponding fields set — the pack decides how to present it.
§Errors
ExecError::Spawn only if the shell process itself cannot be started.
Sourcepub async fn run_capture(
&self,
program: &Path,
args: &[String],
cwd: &Path,
timeout: Option<Duration>,
cancel: &CancellationToken,
) -> Result<ExecOutput, ExecError>
pub async fn run_capture( &self, program: &Path, args: &[String], cwd: &Path, timeout: Option<Duration>, cancel: &CancellationToken, ) -> Result<ExecOutput, ExecError>
Run a resolved program with explicit argv (no shell — safe for untrusted args
like a regex or glob), capturing output under the same limits/kill/cancel machinery
as Host::exec. Used by rg-backed tools (Task 11).
§Errors
ExecError::Spawn if the program cannot be started (e.g. rg not on PATH).
Source§impl Host
impl Host
Sourcepub async fn walk(
&self,
cwd: &Path,
path: &Path,
opts: WalkOptions,
) -> Result<Vec<WalkEntry>, FsError>
pub async fn walk( &self, cwd: &Path, path: &Path, opts: WalkOptions, ) -> Result<Vec<WalkEntry>, FsError>
Walk path (jail-resolved against cwd) with grok-style standard
filters. Hidden files and .git are always skipped
(standard_filters(true)); the three gitignore sources are toggled
together by WalkOptions::respect_gitignore.
§Errors
FsError::Path when the jail rejects path; FsError::Io when
the root does not exist or is unreadable.
Sourcepub async fn is_path_ignored(
&self,
cwd: &Path,
path: &Path,
) -> Result<bool, FsError>
pub async fn is_path_ignored( &self, cwd: &Path, path: &Path, ) -> Result<bool, FsError>
Whether path (jail-resolved against cwd) is gitignored relative to
the workspace’s git root. false when the workspace is not a git repo
(grok’s allow-all rule). Non-existent paths fall back to their parent
for resolution (grok’s new-file-creation case).
The matcher is built once per host from the git root’s .gitignore and
.git/info/exclude. Known limitation vs grok (documented): nested
.gitignore files in subdirectories are not consulted here — the
Host::walk path handles them fully via the walker.
§Errors
FsError::Path when the jail rejects path.
Source§impl Host
impl Host
Sourcepub fn add_root(&self, dir: &Path) -> Result<PathBuf, PathError>
pub fn add_root(&self, dir: &Path) -> Result<PathBuf, PathError>
Widen the jail with another root at runtime (/add-dir).
Returns the canonicalized root on success. Both forms are recorded, for
the same reason Host::new records both: a root behind a symlink must
still satisfy the lexical pre-check. Adding an already-present root is a
no-op rather than an error, so repeating the command is harmless.
§Errors
PathError::InvalidRoot when dir does not exist or cannot be
canonicalized — the jail is left untouched.
Sourcepub fn new(config: HostConfig) -> Result<Self, PathError>
pub fn new(config: HostConfig) -> Result<Self, PathError>
Build a host, canonicalizing workspace_root (the jail root must be a real,
symlink-resolved absolute path).
§Errors
PathError::InvalidRoot if workspace_root does not exist or cannot be
canonicalized.
Sourcepub fn workspace_root(&self) -> &Path
pub fn workspace_root(&self) -> &Path
The canonicalized jail root.
Sourcepub fn path_policy(&self) -> PathPolicy
pub fn path_policy(&self) -> PathPolicy
The active path policy.
Sourcepub fn limits(&self) -> &ExecLimits
pub fn limits(&self) -> &ExecLimits
The shell execution limits.