Skip to content
99 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.

Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged1/**
2 * g1t's own work on a workspace's self-hosted runners. When a workspace (or
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.3 * one of its repositories) says so under Settings, Runners, a merge check or
4 * a state of the merge queue is not started in a sandbox here: the
5 * sandbox's Durable Object hands the same environment to the actions
6 * service as a task, a runner of the workspace's with the right labels
7 * takes it on its next poll, and the actions service tells the Durable
8 * Object how it ended (`task_ended`), which then does exactly what it does
Guardrails no longer restrict the network: every sandbox on g1t's machines reaches the internet, as a GitHub-hosted runner does, and the settings for it are gone at every level, the workspace's defaults and each project's overrides: Only allowed hosts (restrict_network), the package registries, allowed domains and workflow-only domains. Guardrails are now the time caps on checks, the merge queue and merge checks, and nothing else.9 * when a container stops. The run says where it went.
Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged10 */
11
12import type { RepoPath, ServiceBinding } from "@g1t/contracts";
13
14/** The actions service's JSON protocol. */
15async function call<T>(actions: ServiceBinding, method: string, args: object): Promise<T> {
16 const response = await actions.fetch(`https://actions/rpc/${method}`, {
17 method: "POST",
18 headers: { "content-type": "application/json" },
19 body: JSON.stringify(args),
20 });
21 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
22 return (await response.json()) as T;
23}
24
25/**
26 * The labels g1t's own work in `repo` runs on, when it runs on the
27 * workspace's runners; null for g1t's sandboxes. Never throws: when the
28 * actions service cannot say, the work runs in a sandbox as before.
29 */
30export async function selfHostedRoute(actions: ServiceBinding, repo: RepoPath): Promise<string[] | null> {
31 try {
32 const labels = await call<string[] | null>(actions, "runner_route", { workspace: repo.namespace.toLowerCase(), repo });
33 return Array.isArray(labels) && labels.length > 0 ? labels : null;
34 } catch (error) {
35 console.log("self-hosted route unknown; using a sandbox", repo.namespace, String(error));
36 return null;
37 }
38}
39
40type Outcome<T> = { ok: true; value: T } | { ok: false; error: { message: string } };
41
42/** Hands a sandbox's work to the workspace's runners. Returns the task's id. */
43export async function enqueueTask(
44 actions: ServiceBinding,
45 task: {
46 sandbox: string;
47 repo: RepoPath;
48 kind: string;
49 title: string;
50 labels: string[];
51 env: Record<string, string>;
52 timeoutMinutes: number;
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.53 /** The run the work is, when the sandbox opened one: the Runners page links to it. */
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.54 runId?: string | null;
Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged55 },
56): Promise<string> {
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.57 const { runId, ...rest } = task;
58 const queued = await call<Outcome<string>>(actions, "enqueue_task", { ...rest, run_id: runId ?? null, workspace: task.repo.namespace.toLowerCase() });
Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged59 if (!queued.ok) throw new Error(queued.error.message);
60 return queued.value;
61}
62
63/** Withdraws a sandbox's task: a person stopped it, or its time ran out. Never throws. */
64export async function cancelTask(actions: ServiceBinding, sandbox: string, reason: string | null): Promise<void> {
65 await call(actions, "cancel_task", { sandbox, reason }).catch((error: unknown) =>
66 console.log("self-hosted task not cancelled", sandbox, String(error)),
67 );
68}
69
70/**
71 * The environment a self-hosted runner gets: what the sandbox would have
72 * been started with, less what only makes sense in g1t's sandbox (the
73 * egress certificate and proxy), with the mining watch off, since the
74 * machine and its electricity are the workspace's.
75 */
76export function taskEnv(vars: Record<string, string>): Record<string, string> {
77 const env: Record<string, string> = {};
78 for (const [name, value] of Object.entries(vars)) {
79 if (/^(HTTPS?_PROXY|NO_PROXY|NODE_EXTRA_CA_CERTS|SSL_CERT_FILE|G1T_EGRESS_CA)$/i.test(name)) continue;
80 env[name] = value;
81 }
82 env.G1T_ABUSE = "off";
83 env.G1T_SELF_HOSTED = "1";
84 return env;
85}
86
87/** What the run's session says when it goes to the workspace's runners. */
88export function handedOverStep(labels: string[]): string {
Guardrails no longer restrict the network: every sandbox on g1t's machines reaches the internet, as a GitHub-hosted runner does, and the settings for it are gone at every level, the workspace's defaults and each project's overrides: Only allowed hosts (restrict_network), the package registries, allowed domains and workflow-only domains. Guardrails are now the time caps on checks, the merge queue and merge checks, and nothing else.89 return `Handed to a self-hosted runner with labels ${labels.join(", ")}.`;
Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged90}
91
92/** Where the work is: the tracked repository, else the meter's `owner/name`. */
93export function taskRepo(track: { repo: RepoPath } | undefined, meter: { repo: string } | undefined, owner: { repo: string | null } | undefined): RepoPath | null {
94 if (track) return track.repo;
95 const full = meter?.repo ?? owner?.repo ?? null;
96 if (!full) return null;
97 const [namespace, name] = full.split("/");
98 return namespace && name ? { namespace, name } : null;
99}

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