Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
756 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.1//! Running what g1t hands the runner: a workflow job, or g1t's own work (a
2//! merge check or a state of the merge queue), with the same harness g1t's
3//! sandboxes run (this program, in another mode).
A package's latest version is its highest, not the last one published4//!
5//! - **In Docker** (the default): one container per job, removed when it
6//! ends, from the job's `container:` image or the runner's `--image`. A
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r97//! Linux build of the harness is mounted into it read-only; that and the
8//! job's own work volume, removed after, are its only mounts. It runs
9//! with every capability dropped but the few a build needs, no new
10//! privileges, a process, memory and CPU limit, and a network of its
11//! own. With nested-container isolation (sandbox.rs), all of this is on
12//! the runner's own Docker Engine, inside the runner's container, and a
13//! job that uses Docker gets an engine of its own (engine.rs).
14//! - **Directly** (`--no-docker`, deprecated): the harness runs as a child
15//! process, in a fresh folder under the runner's work folder that is
16//! removed after. Unsandboxed: the job's log says so, and a public
17//! repository's work is refused.
A package's latest version is its highest, not the last one published18//!
19//! The runner's own credential never reaches either: a job only ever has
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r920//! its own short-lived token, as it would in g1t's sandbox. Credentials are
21//! passed by name (`-e NAME`), never on the command line.
A package's latest version is its highest, not the last one published22
23use std::collections::BTreeMap;
24use std::path::{Path, PathBuf};
25use std::process::{Child, Command, Stdio};
26use std::time::{Duration, Instant};
27
28use anyhow::{Context, Result, bail};
29
30use super::api::Assignment;
31use super::config::Config;
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r932use super::sandbox::{self, Isolation};
A package's latest version is its highest, not the last one published33use super::{log, update};
34
35/// The image workflow jobs run in when they name none: Debian with Node,
36/// git and Python, as g1t's sandbox has.
37pub const DEFAULT_IMAGE: &str = "node:24-bookworm";
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r938/// The most processes in a job's container, unless the runner says.
39pub const DEFAULT_PIDS_LIMIT: u32 = 4096;
40
41/// The capabilities a job's container keeps; Docker's others, and
42/// everything else, are dropped. A workflow job runs as its image's user,
43/// root for most CI images, and these are what installing packages and
44/// building need:
45///
46/// - `CHOWN`, `FOWNER`, `FSETID`, `DAC_OVERRIDE`: a package manager (apt,
47/// npm as root) writing files it then hands to another owner, keeping
48/// setgid bits, and root writing files it does not own.
49/// - `SETUID`, `SETGID`: apt and others drop to their own user (`_apt`) to
50/// download; with no new privileges, nothing can gain a user back.
51/// - `KILL`: a step stopping processes another user started.
52/// - `NET_BIND_SERVICE`: a test server on port 80 or 443.
53///
54/// Not kept, among others: `NET_RAW` (raw sockets, `ping`), `MKNOD`,
55/// `SYS_CHROOT`, `SETFCAP`, `AUDIT_WRITE`, and every `SYS_*` and `NET_ADMIN`.
56pub const JOB_CAPS: &[&str] = &["CHOWN", "DAC_OVERRIDE", "FOWNER", "FSETID", "SETUID", "SETGID", "KILL", "NET_BIND_SERVICE"];
A package's latest version is its highest, not the last one published57
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.58/// The work kind g1t's own tasks come as: a merge check or a state of the
59/// merge queue, with the environment g1t's sandbox would have had. Older
60/// g1t sent agents' runs as this kind too; those are refused.
61pub const TASK_KIND: &str = "agent";
62
63/// The modes of g1t's own tasks this runner runs (main.rs).
64pub const TASK_MODES: &[&str] = &["queue", "mergecheck", "checks"];
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r965
A package's latest version is its highest, not the last one published66/// Work in progress.
67pub struct Running {
68 pub id: String,
69 pub kind: String,
70 pub name: String,
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.71 pub repo: String,
A package's latest version is its highest, not the last one published72 child: Child,
73 container: Option<String>,
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r974 /// The job's network and work volume, removed after.
75 network: Option<String>,
76 volume: Option<String>,
77 /// The job's own Docker Engine, when it may have one.
78 engine: Option<JobEngine>,
A package's latest version is its highest, not the last one published79 folder: Option<PathBuf>,
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.80 /// The job's own VM, for a job native to this OS (native.rs); `child`
81 /// is then `wsb exec`, whose output carries the job's exit code.
82 native: Option<WsbJob>,
83 /// How it ended, once read: a native job's code is read once from
84 /// `wsb exec`'s output.
85 exit: Option<i32>,
A package's latest version is its highest, not the last one published86 /// Past its time limit and a grace for g1t to say so: stopped here.
87 deadline: Instant,
88}
89
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.90use super::native::{self, WsbJob};
91
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r992#[cfg(unix)]
93use super::engine::JobEngine;
94
95/// No job has an engine of its own off Unix.
96#[cfg(not(unix))]
97pub struct JobEngine;
98
99#[cfg(not(unix))]
100impl JobEngine {
101 fn remove(&self) {}
102 fn job_args(&self) -> Vec<String> {
103 Vec::new()
104 }
105}
106
107/// `docker`, at the runner's own engine when it has one.
108fn docker() -> Command {
109 #[cfg(unix)]
110 return super::engine::docker();
111 #[cfg(not(unix))]
112 Command::new("docker")
113}
114
115fn quietly(command: &mut Command) {
116 let _ = command.stdout(Stdio::null()).stderr(Stdio::null()).status();
117}
118
A package's latest version is its highest, not the last one published119impl Running {
120 pub fn overdue(&self) -> bool {
121 Instant::now() > self.deadline
122 }
123
124 /// Its exit code once it has ended.
125 pub fn ended(&mut self) -> Option<i32> {
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.126 if self.exit.is_some() {
127 return self.exit;
128 }
129 self.exit = match self.child.try_wait() {
130 Ok(Some(_)) if self.native.is_some() => {
131 use std::io::Read;
132 let mut text = String::new();
133 if let Some(out) = self.child.stdout.as_mut() {
134 let _ = out.read_to_string(&mut text);
135 }
136 Some(native::parse_exit_code(&text).unwrap_or(1))
137 }
A package's latest version is its highest, not the last one published138 Ok(Some(status)) => Some(status.code().unwrap_or(1)),
139 Ok(None) => None,
140 Err(_) => Some(1),
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.141 };
142 self.exit
A package's latest version is its highest, not the last one published143 }
144
145 /// Stops it: the container, then the process and everything it started.
146 pub fn stop(&mut self) {
147 if let Some(container) = &self.container {
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9148 quietly(docker().args(["kill", container]));
149 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9150 #[cfg(unix)]
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9151 if let Some(engine) = &self.engine {
152 quietly(docker().args(["kill", &engine.container]));
A package's latest version is its highest, not the last one published153 }
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.154 if let Some(job) = &self.native {
155 job.stop();
156 }
A package's latest version is its highest, not the last one published157 let pid = self.child.id().to_string();
158 if cfg!(windows) {
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9159 quietly(Command::new("taskkill").args(["/T", "/F", "/PID", &pid]));
A package's latest version is its highest, not the last one published160 } else {
161 // Its own process group (see `directly`): the steps' processes too.
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9162 quietly(Command::new("kill").args(["-TERM", &format!("-{pid}")]));
A package's latest version is its highest, not the last one published163 }
164 let _ = self.child.kill();
165 let _ = self.child.wait();
166 }
167
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9168 /// Removes what it left: its container, its engine and that engine's
169 /// data, its network, its work volume, its folder.
A package's latest version is its highest, not the last one published170 pub fn clean(&mut self) {
171 if let Some(container) = &self.container {
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9172 quietly(docker().args(["rm", "-f", "-v", container]));
173 }
174 if let Some(engine) = self.engine.take() {
175 engine.remove();
A package's latest version is its highest, not the last one published176 }
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9177 if let Some(network) = &self.network {
178 quietly(docker().args(["network", "rm", network]));
179 }
180 if let Some(volume) = &self.volume {
181 quietly(docker().args(["volume", "rm", "-f", volume]));
182 }
A package's latest version is its highest, not the last one published183 if let Some(folder) = &self.folder {
184 let _ = std::fs::remove_dir_all(folder);
185 }
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.186 if let Some(job) = self.native.take() {
187 job.clean();
188 }
A package's latest version is its highest, not the last one published189 }
190}
191
192/// A name Docker takes, from a job's id.
193fn container_name(id: &str) -> String {
194 let clean: String = id.chars().map(|c| if c.is_ascii_alphanumeric() || c == '_' || c == '-' { c } else { '-' }).collect();
195 format!("g1t-{clean}")
196}
197
198/// The environment the harness gets for this work.
199fn harness_env(config: &Config, work: &Assignment) -> Result<BTreeMap<String, String>> {
200 let mut env = BTreeMap::new();
201 match work.kind.as_str() {
202 "workflow" => {
203 let Some(token) = &work.token else { bail!("the job came without its token") };
204 env.insert("MODE".into(), "actions".into());
205 env.insert("G1T_API".into(), config.api.clone());
206 env.insert("ACTIONS_JOB".into(), work.id.clone());
207 env.insert("ACTIONS_TOKEN".into(), token.clone());
208 }
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.209 TASK_KIND => {
A package's latest version is its highest, not the last one published210 for (name, value) in work.env.clone().unwrap_or_default() {
211 let value = match value {
212 serde_json::Value::String(text) => text,
213 other => other.to_string(),
214 };
215 env.insert(name, value);
216 }
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.217 match env.get("MODE").map(String::as_str) {
218 Some(mode) if TASK_MODES.contains(&mode) => {}
219 Some(mode) => bail!("this runner does not run g1t work of mode {mode}"),
220 None => bail!("this runner does not run agents' work"),
A package's latest version is its highest, not the last one published221 }
222 // Where its API calls go: this installation's.
223 env.insert("G1T_API".into(), config.api.clone());
224 }
225 other => bail!("this runner does not know work of kind {other}; update it"),
226 }
227 // The machine is the workspace's own: no mining watch.
228 env.insert("G1T_ABUSE".into(), "off".into());
229 Ok(env)
230}
231
232/// Starts work. The harness reports a workflow job's steps and log itself;
233/// the runner tells g1t how the process ended.
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9234pub fn start(config: &Config, isolation: Isolation, folder: &Path, work: &Assignment) -> Result<Running> {
A package's latest version is its highest, not the last one published235 let env = harness_env(config, work)?;
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.236 match isolation {
237 Isolation::Host => {
238 refuse_public_on_host(work)?;
239 directly(config, work, env)
240 }
241 Isolation::Vm => in_native_vm(config, folder, work, env),
242 _ => in_docker(config, isolation, folder, work, env),
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9243 }
244}
245
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.246/// A memory size as `--memory` takes it (`8g`, `4096m`), in MiB.
247pub fn mib(text: &str) -> Option<u64> {
248 let text = text.trim().to_ascii_lowercase();
249 let (number, unit) = text.split_at(text.find(|c: char| !c.is_ascii_digit()).unwrap_or(text.len()));
250 let number: u64 = number.parse().ok()?;
251 match unit.trim_end_matches('b').trim_end_matches('i') {
252 "" | "m" => Some(number),
253 "g" => Some(number * 1024),
254 "k" => Some(number / 1024),
255 _ => None,
256 }
257}
258
259/// A job native to this OS, in a throwaway VM of its own (native.rs):
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.260/// Windows Sandbox on Windows. g1t's own work needs its Linux sandbox
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.261/// layout and runs on a Linux runner.
262fn in_native_vm(config: &Config, folder: &Path, work: &Assignment, env: BTreeMap<String, String>) -> Result<Running> {
263 if work.kind != "workflow" {
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.264 bail!("g1t's own work runs on Linux runners: this one runs jobs native to {}", std::env::consts::OS);
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.265 }
266 if !cfg!(windows) {
267 bail!("this runner has no sandbox for jobs native to {}", std::env::consts::OS);
268 }
269 let memory = config.memory.as_deref().and_then(mib).unwrap_or(4096);
270 let drop = folder.join("native").join(container_name(&work.id));
271 let job = WsbJob::start(drop, &env, memory)?;
272 let child = match job.exec().spawn() {
273 Ok(child) => child,
274 Err(error) => {
275 job.clean();
276 return Err(error).context("could not run wsb exec");
277 }
278 };
279 log(&format!(
280 "Running {} {} in a Windows Sandbox of its own ({memory} MiB): no folder of this machine's but its read-only harness, discarded after",
281 work.kind, work.name
282 ));
283 Ok(Running {
284 id: work.id.clone(),
285 repo: work.repo.clone(),
286 kind: work.kind.clone(),
287 name: work.name.clone(),
288 child,
289 container: None,
290 network: None,
291 volume: None,
292 engine: None,
293 folder: None,
294 native: Some(job),
295 exit: None,
296 deadline: deadline(work),
297 })
298}
299
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9300/// Host mode does not take a public repository's work: anyone can propose
301/// code to one, and here it would run on the machine itself.
302fn refuse_public_on_host(work: &Assignment) -> Result<()> {
303 match work.visibility.as_deref() {
304 Some("public") => bail!("{}", sandbox::host_mode_refuses(&work.repo)),
305 Some(_) => Ok(()),
306 None => {
307 log(&format!("g1t did not say whether {} is public (an older g1t), so it runs; host mode refuses public repositories' work once it does.", work.repo));
308 Ok(())
309 }
310 }
311}
312
313/// Each job container's limits.
314#[derive(Debug, Clone, PartialEq)]
315pub struct Limits {
316 pub memory: String,
317 pub cpus: String,
318 pub pids: u32,
319}
320
321/// Three quarters of `MemTotal` in `/proc/meminfo`, in MiB, which under
322/// Sysbox is the runner container's own memory.
323fn default_memory(meminfo: &str) -> Option<String> {
324 let kb: u64 = meminfo.lines().find_map(|l| l.strip_prefix("MemTotal:"))?.trim().trim_end_matches("kB").trim().parse().ok()?;
325 let mib = kb / 1024 * 3 / 4;
326 (mib >= 256).then(|| format!("{mib}m"))
327}
328
329pub fn limits(config: &Config) -> Limits {
330 let memory = config
331 .memory
332 .clone()
333 .or_else(|| default_memory(&std::fs::read_to_string("/proc/meminfo").unwrap_or_default()))
334 .unwrap_or_else(|| "4g".into());
335 let cpus = config.cpus.clone().unwrap_or_else(|| std::thread::available_parallelism().map(|n| n.get()).unwrap_or(2).to_string());
336 Limits { memory, cpus, pids: config.pids_limit.unwrap_or(DEFAULT_PIDS_LIMIT) }
337}
338
339/// Everything a job's `docker run` is made from.
340pub struct Plan<'a> {
341 pub name: &'a str,
342 pub runner: &'a str,
343 pub image: &'a str,
344 pub harness: &'a Path,
345 pub network: &'a str,
346 /// The job's work volume, for a workflow job's `/home/runner`.
347 pub volume: Option<&'a str>,
348 pub limits: &'a Limits,
349 /// The names of the harness's variables, whose values come from the
350 /// runner's process, never its command line.
351 pub env_names: Vec<&'a str>,
352 /// What the job's own engine adds (`JobEngine::job_args`).
353 pub engine_args: Vec<String>,
354}
355
356/// The arguments of `docker` that start a job's container.
357pub fn run_args(plan: &Plan) -> Vec<String> {
358 let mut args: Vec<String> = Vec::new();
359 fn add(args: &mut Vec<String>, items: &[&str]) {
360 args.extend(items.iter().map(|s| s.to_string()));
361 }
362 add(&mut args, &["run", "--rm", "--name", plan.name, "--label", &format!("sh.g1t.runner={}", plan.runner)]);
363 add(&mut args, &["--pull", "missing", "--init"]);
364 // The walls.
365 add(&mut args, &["--cap-drop", "ALL"]);
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.366 add(&mut args, &["--security-opt", "no-new-privileges"]);
367 for cap in JOB_CAPS {
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9368 add(&mut args, &["--cap-add", cap]);
369 }
370 let pids = plan.limits.pids.to_string();
371 add(&mut args, &["--pids-limit", &pids, "--memory", &plan.limits.memory, "--memory-swap", &plan.limits.memory, "--cpus", &plan.limits.cpus]);
372 add(&mut args, &["--network", plan.network, "--ipc", "private"]);
373 // The job is in its `container:` image already: the harness inside
374 // does not start it again (actions/containers.rs).
375 add(&mut args, &["-e", "G1T_JOB_CONTAINER=1"]);
376 for name in &plan.env_names {
377 add(&mut args, &["-e", name]);
378 }
379 // Its only mounts: the harness, read-only, and its own volume.
380 add(&mut args, &["-v", &format!("{}:/opt/g1t/g1t-runner:ro", plan.harness.display())]);
381 if let Some(volume) = plan.volume {
382 add(&mut args, &["-v", &format!("{volume}:/home/runner")]);
383 }
384 args.extend(plan.engine_args.iter().cloned());
385 add(&mut args, &["--entrypoint", "/opt/g1t/g1t-runner", plan.image]);
386 args
A package's latest version is its highest, not the last one published387}
388
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9389fn in_docker(config: &Config, isolation: Isolation, folder: &Path, work: &Assignment, env: BTreeMap<String, String>) -> Result<Running> {
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.390 // g1t's own work needs only git, which the default image has.
391 let image = work.image.clone().or_else(|| config.image.clone()).unwrap_or_else(|| DEFAULT_IMAGE.to_owned());
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9392 #[cfg(unix)]
393 if super::engine::in_use() {
394 super::engine::ensure_running()?;
395 }
A package's latest version is its highest, not the last one published396 let harness = update::linux_harness(config, folder)?;
397 let name = container_name(&work.id);
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9398 // What an earlier run of this job may have left.
399 quietly(docker().args(["rm", "-f", "-v", &name]));
400 quietly(docker().args(["network", "rm", &name]));
401 let output = docker()
402 .args(["network", "create", "--label", &format!("sh.g1t.runner={}", config.runner), &name])
403 .output()
404 .context("could not run docker: is Docker installed and running?")?;
405 if !output.status.success() {
406 bail!("could not make the job's network: {}", String::from_utf8_lossy(&output.stderr).trim());
A package's latest version is its highest, not the last one published407 }
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9408 let volume = (work.kind == "workflow").then(|| format!("{name}-work"));
409 if let Some(volume) = &volume {
410 quietly(docker().args(["volume", "rm", "-f", volume]));
411 quietly(docker().args(["volume", "create", "--label", &format!("sh.g1t.runner={}", config.runner), volume]));
412 }
413 // A job's own Docker, only inside the runner's own sandbox: on a
414 // shared engine it would be the machine's.
415 let engine: Option<JobEngine> = job_engine(config, isolation, &name);
416 let limits = limits(config);
417 let plan = Plan {
418 name: &name,
419 runner: &config.runner,
420 image: &image,
421 harness: &harness,
422 network: &name,
423 volume: volume.as_deref(),
424 limits: &limits,
425 env_names: env.keys().map(String::as_str).collect(),
426 engine_args: engine.as_ref().map(JobEngine::job_args).unwrap_or_default(),
427 };
428 let mut command = docker();
429 command.args(run_args(&plan));
A package's latest version is its highest, not the last one published430 command.envs(&env);
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9431 log(&format!(
432 "Running {} {} in {image}: its own container and network, capabilities dropped, {} memory, {} CPUs, {} processes{}",
433 work.kind,
434 work.name,
435 limits.memory,
436 limits.cpus,
437 limits.pids,
438 if engine.is_some() { ", Docker of its own when it uses it" } else { "" }
439 ));
440 let child = match command.spawn() {
441 Ok(child) => child,
442 Err(error) => {
443 if let Some(engine) = &engine {
444 engine.remove();
445 }
446 quietly(docker().args(["network", "rm", &name]));
447 return Err(error).context("could not run docker: is Docker installed and running?");
448 }
449 };
450 Ok(Running {
451 id: work.id.clone(),
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.452 repo: work.repo.clone(),
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9453 kind: work.kind.clone(),
454 name: work.name.clone(),
455 child,
456 container: Some(name.clone()),
457 network: Some(name),
458 volume,
459 engine,
460 folder: None,
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.461 native: None,
462 exit: None,
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9463 deadline: deadline(work),
464 })
465}
466
467#[cfg(unix)]
468fn job_engine(config: &Config, isolation: Isolation, name: &str) -> Option<JobEngine> {
469 if isolation != Isolation::NestedContainer || !config.job_docker {
470 return None;
471 }
472 let image = config.job_docker_image.as_deref().unwrap_or(super::engine::DEFAULT_JOB_ENGINE_IMAGE);
473 match JobEngine::prepare(name, &config.runner, image) {
474 Ok(engine) => Some(engine),
475 Err(error) => {
476 log(&format!("This job has no Docker of its own: {error:#}"));
477 None
478 }
479 }
A package's latest version is its highest, not the last one published480}
481
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9482#[cfg(not(unix))]
483fn job_engine(_config: &Config, _isolation: Isolation, _name: &str) -> Option<JobEngine> {
484 None
485}
486
A package's latest version is its highest, not the last one published487fn directly(config: &Config, work: &Assignment, env: BTreeMap<String, String>) -> Result<Running> {
488 let me = std::env::current_exe().context("could not find this program to run the job")?;
489 let mut command = Command::new(me);
490 let mut folder = None;
491 if work.kind == "workflow" {
492 // GitHub's layout, in a folder of its own.
493 let root = config.work_dir.join(container_name(&work.id));
494 let _ = std::fs::remove_dir_all(&root);
495 std::fs::create_dir_all(&root).with_context(|| format!("could not make {}", root.display()))?;
496 command.env("G1T_RUNNER_ROOT", &root);
497 folder = Some(root);
498 } else if !cfg!(target_os = "linux") || std::fs::create_dir_all("/work").is_err() {
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.499 // g1t's own work expects its sandbox layout (/work), which only a
A package's latest version is its highest, not the last one published500 // Linux machine (or a container) set aside for it has.
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.501 bail!("g1t's own work runs in Docker, or directly on Linux where /work can be written; this runner can do neither");
A package's latest version is its highest, not the last one published502 }
503 // Its own variables, less anything of the runner's.
504 for (name, _) in std::env::vars_os() {
505 if name.to_string_lossy().starts_with("G1T_RUNNER_") && name != "G1T_RUNNER_ROOT" {
506 command.env_remove(&name);
507 }
508 }
509 command.envs(&env);
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9510 // The job's log says it ran unsandboxed (actions/mod.rs).
511 command.env(JOB_NOTICE, sandbox::HOST_MODE_IN_JOB);
A package's latest version is its highest, not the last one published512 // A group of its own, so stopping it stops what its steps started.
513 #[cfg(unix)]
514 std::os::unix::process::CommandExt::process_group(&mut command, 0);
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9515 log(&format!("Running {} {} on this machine, unsandboxed (host mode, deprecated)", work.kind, work.name));
A package's latest version is its highest, not the last one published516 let child = command.spawn().context("could not start the job's process")?;
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9517 Ok(Running {
518 id: work.id.clone(),
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.519 repo: work.repo.clone(),
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9520 kind: work.kind.clone(),
521 name: work.name.clone(),
522 child,
523 container: None,
524 network: None,
525 volume: None,
526 engine: None,
527 folder,
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.528 native: None,
529 exit: None,
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9530 deadline: deadline(work),
531 })
A package's latest version is its highest, not the last one published532}
533
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9534/// A warning the harness puts at the top of the job's log.
535pub const JOB_NOTICE: &str = "G1T_JOB_NOTICE";
536
A package's latest version is its highest, not the last one published537fn deadline(work: &Assignment) -> Instant {
538 Instant::now() + Duration::from_secs(u64::from(work.timeout_minutes.max(1)) * 60 + 10 * 60)
539}
540
541/// Whether Docker answers.
542pub fn docker_ready() -> bool {
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9543 docker()
A package's latest version is its highest, not the last one published544 .args(["version", "--format", "{{.Server.Version}}"])
545 .stdout(Stdio::null())
546 .stderr(Stdio::null())
547 .status()
548 .is_ok_and(|status| status.success())
549}
550
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9551/// Removes what jobs of this runner's left behind, if it stopped while one ran.
552pub fn sweep(config: &Config) {
553 let label = format!("label=sh.g1t.runner={}", config.runner);
554 for (list, remove) in [
555 (vec!["ps", "-aq", "--filter", &label], vec!["rm", "-f", "-v"]),
556 (vec!["network", "ls", "-q", "--filter", &label], vec!["network", "rm"]),
557 (vec!["volume", "ls", "-q", "--filter", &label], vec!["volume", "rm", "-f"]),
558 ] {
559 let Ok(output) = docker().args(&list).output() else { return };
560 let ids: Vec<String> = String::from_utf8_lossy(&output.stdout).split_whitespace().map(str::to_owned).collect();
561 if !ids.is_empty() {
562 quietly(docker().args(&remove).args(&ids));
563 }
564 }
565}
566
A package's latest version is its highest, not the last one published567#[cfg(test)]
568mod tests {
569 use super::*;
570 use serde_json::json;
571
572 fn config() -> Config {
573 Config {
574 url: "https://g1t.sh".into(),
575 api: "https://api.g1t.sh".into(),
576 runner: "rnr_1".into(),
577 name: "a".into(),
578 credential: "g1tr_secret".into(),
579 workspace: "acme".into(),
580 repo: None,
581 group: None,
582 labels: vec![],
583 ephemeral: false,
584 work_dir: PathBuf::from("/tmp/w"),
585 docker: true,
586 image: None,
587 harness: None,
588 auto_update: false,
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9589 memory: None,
590 cpus: None,
591 pids_limit: None,
592 job_docker: true,
593 job_docker_image: None,
Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it.594 native: false,
A package's latest version is its highest, not the last one published595 }
596 }
597
598 fn assignment(kind: &str) -> Assignment {
599 serde_json::from_value(json!({
600 "kind": kind, "id": "job_1", "name": "build", "repo": "acme/web", "timeout_minutes": 60,
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.601 "token": "jobtoken", "env": { "MODE": "queue", "G1T_TOKEN": "g1t_run", "G1T_API": "https://elsewhere" }
A package's latest version is its highest, not the last one published602 }))
603 .unwrap()
604 }
605
606 #[test]
607 fn a_workflow_job_gets_its_own_token_and_never_the_runners() {
608 let env = harness_env(&config(), &assignment("workflow")).unwrap();
609 assert_eq!(env["MODE"], "actions");
610 assert_eq!(env["ACTIONS_JOB"], "job_1");
611 assert_eq!(env["ACTIONS_TOKEN"], "jobtoken");
612 assert_eq!(env["G1T_API"], "https://api.g1t.sh");
613 assert!(env.values().all(|value| !value.contains("g1tr_")));
614 }
615
616 #[test]
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.617 fn g1ts_own_work_gets_the_sandboxs_environment_and_this_api() {
618 let env = harness_env(&config(), &assignment(TASK_KIND)).unwrap();
619 assert_eq!(env["MODE"], "queue");
A package's latest version is its highest, not the last one published620 assert_eq!(env["G1T_TOKEN"], "g1t_run");
621 assert_eq!(env["G1T_API"], "https://api.g1t.sh");
622 assert_eq!(env["G1T_ABUSE"], "off");
623 }
624
625 #[test]
626 fn unknown_work_is_refused() {
627 assert!(harness_env(&config(), &assignment("deploy")).is_err());
628 }
629
630 #[test]
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.631 fn an_agents_run_from_an_older_g1t_is_refused_before_it_starts() {
632 for env in [json!({ "MODE": "revise", "G1T_TOKEN": "g1t_run" }), json!({ "PROMPT": "Fix it", "G1T_TOKEN": "g1t_run" })] {
633 let mut work = assignment(TASK_KIND);
634 work.env = serde_json::from_value(env).unwrap();
635 assert!(harness_env(&config(), &work).is_err());
636 assert!(start(&config(), Isolation::Host, Path::new("/tmp"), &work).is_err());
637 }
638 }
639
640 #[test]
A package's latest version is its highest, not the last one published641 fn container_names_are_dockers() {
642 assert_eq!(container_name("job_01ab"), "g1t-job_01ab");
643 assert_eq!(container_name("a/b c"), "g1t-a-b-c");
644 }
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9645
646 fn args_for(kind: &str, engine_args: Vec<String>) -> (Vec<String>, BTreeMap<String, String>) {
647 let config = config();
648 let env = harness_env(&config, &assignment(kind)).unwrap();
649 let limits = Limits { memory: "6144m".into(), cpus: "4".into(), pids: 4096 };
650 let harness = PathBuf::from("/usr/local/bin/g1t-runner");
651 let plan = Plan {
652 name: "g1t-job_1",
653 runner: "rnr_1",
654 image: DEFAULT_IMAGE,
655 harness: &harness,
656 network: "g1t-job_1",
657 volume: (kind == "workflow").then_some("g1t-job_1-work"),
658 limits: &limits,
659 env_names: env.keys().map(String::as_str).collect(),
660 engine_args,
661 };
662 (run_args(&plan), env.clone())
663 }
664
665 fn value_of<'a>(args: &'a [String], flag: &str) -> Vec<&'a str> {
666 args.windows(2).filter(|w| w[0] == flag).map(|w| w[1].as_str()).collect()
667 }
668
669 #[test]
670 fn a_jobs_container_is_walled_in() {
671 let (args, _) = args_for("workflow", Vec::new());
672 assert_eq!(args[0], "run");
673 assert!(args.contains(&"--rm".to_owned()));
674 assert_eq!(value_of(&args, "--cap-drop"), vec!["ALL"]);
675 assert_eq!(value_of(&args, "--cap-add"), JOB_CAPS.to_vec());
676 assert_eq!(value_of(&args, "--security-opt"), vec!["no-new-privileges"]);
677 assert_eq!(value_of(&args, "--pids-limit"), vec!["4096"]);
678 assert_eq!(value_of(&args, "--memory"), vec!["6144m"]);
679 assert_eq!(value_of(&args, "--memory-swap"), vec!["6144m"]);
680 assert_eq!(value_of(&args, "--cpus"), vec!["4"]);
681 assert_eq!(value_of(&args, "--network"), vec!["g1t-job_1"]);
682 for forbidden in ["--privileged", "--pid", "--userns", "--device", "host"] {
683 assert!(!args.iter().any(|a| a == forbidden), "{forbidden} in {args:?}");
684 }
685 assert!(!JOB_CAPS.iter().any(|c| c.starts_with("SYS_") || *c == "NET_ADMIN" || *c == "NET_RAW"));
686 }
687
688 #[test]
689 fn a_jobs_only_mounts_are_the_harness_and_its_own_volume() {
690 let (args, _) = args_for("workflow", Vec::new());
691 let mounts = value_of(&args, "-v");
692 assert_eq!(mounts, vec!["/usr/local/bin/g1t-runner:/opt/g1t/g1t-runner:ro", "g1t-job_1-work:/home/runner"]);
693 assert!(!args.iter().any(|a| a.contains("docker.sock")), "no engine socket: {args:?}");
694 assert!(!args.iter().any(|a| a == "--mount"));
695 }
696
697 #[test]
698 fn credentials_go_by_name_and_the_runners_never() {
699 let (args, env) = args_for("workflow", Vec::new());
700 let passed = value_of(&args, "-e");
701 assert!(passed.contains(&"ACTIONS_TOKEN"));
702 // Never a value on the command line, and never the runner's credential.
703 assert!(!args.iter().any(|a| a.contains("jobtoken")));
704 assert!(!args.iter().any(|a| a.contains("g1tr_")));
705 assert!(env.values().all(|v| !v.contains("g1tr_secret")));
706 assert!(!env.contains_key("G1T_RUNNER_TOKEN"));
707 }
708
709 #[test]
The runner no longer runs g1t's own agents. Cloud and self-hosted runners now do what a forge's runner does: Actions jobs, required checks, merge checks and merge-queue test runs, deployments, dependency bumps and backups. Everything that started Claude Code in a sandbox on an issue or pull request is gone, with its prompts, model access, mentions, delegation, plans, reviews, revisions and the workspace agents' computers.710 fn g1ts_own_work_has_every_wall_a_job_has() {
711 let (args, _) = args_for(TASK_KIND, Vec::new());
712 assert_eq!(value_of(&args, "--security-opt"), vec!["no-new-privileges"]);
713 assert_eq!(value_of(&args, "--cap-add"), JOB_CAPS.to_vec());
Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9714 assert_eq!(value_of(&args, "--cap-drop"), vec!["ALL"]);
715 assert_eq!(value_of(&args, "-v"), vec!["/usr/local/bin/g1t-runner:/opt/g1t/g1t-runner:ro"]);
716 }
717
718 #[test]
719 fn a_job_with_docker_gets_a_socket_of_its_own_not_the_runners() {
720 let engine_args = vec!["-v".into(), "/run/g1t-runner/jobs/g1t-job_1/proxy:/run/g1t-docker:ro".into(), "-e".into(), "DOCKER_HOST=unix:///run/g1t-docker/docker.sock".into()];
721 let (args, _) = args_for("workflow", engine_args);
722 let mounts = value_of(&args, "-v");
723 assert!(mounts.contains(&"/run/g1t-runner/jobs/g1t-job_1/proxy:/run/g1t-docker:ro"));
724 assert!(!mounts.iter().any(|m| m.starts_with("/var/run/docker.sock") || m.starts_with("/run/g1t-runner/docker.sock")));
725 // The entrypoint and image stay last.
726 assert_eq!(&args[args.len() - 3..], ["--entrypoint", "/opt/g1t/g1t-runner", DEFAULT_IMAGE]);
727 }
728
729 #[test]
730 fn limits_default_sensibly() {
731 assert_eq!(default_memory("MemTotal: 16384000 kB\nMemFree: 1 kB\n"), Some("12000m".into()));
732 assert_eq!(default_memory(""), None);
733 let mut config = config();
734 config.memory = Some("2g".into());
735 config.pids_limit = Some(100);
736 let limits = limits(&config);
737 assert_eq!(limits.memory, "2g");
738 assert_eq!(limits.pids, 100);
739 assert!(limits.cpus.parse::<f64>().unwrap() >= 1.0);
740 }
741
742 #[test]
743 fn host_mode_refuses_a_public_repositorys_work() {
744 let mut work = assignment("workflow");
745 work.visibility = Some("public".into());
746 let refused = refuse_public_on_host(&work).unwrap_err().to_string();
747 assert!(refused.contains("acme/web is public"), "{refused}");
748 work.visibility = Some("private".into());
749 assert!(refuse_public_on_host(&work).is_ok());
750 work.visibility = None;
751 assert!(refuse_public_on_host(&work).is_ok());
752 // And refuses it before anything runs.
753 work.visibility = Some("public".into());
754 assert!(start(&config(), Isolation::Host, Path::new("/tmp"), &work).is_err());
755 }
A package's latest version is its highest, not the last one published756}

This file's history is long; its oldest lines are credited to the oldest commit read.