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

Commit

The deployments tests now show that a build a workflow job ran is never charged by the second, whether it finished or failed, while a build g1t's sandbox ran still is, and that a workflow's build is refused at the workspace's spend limit as any build is; what a finished workflow build puts up is the same app as any other, metered for requests, CPU and domains the same way.

Claude-Session:
https://claude.ai/code/session_01PnwTRSNaC7vEHTAuBzjhrn
Claudeagent and Claudeagentcommitted Parenta8ca6caBrowse files
1 file+35−10/1 viewed
+35−1
7272 pull: { status: "open", headCommit: SHA, branch: "feature" as string | null, fork: null as null | { namespace: string; name: string } },
7373 collaborator: "write",
7474 plan: true,
75+ limit: "ok",
7576 /** What other services were asked, as `SERVICE method`. */
7677 calls: [] as string[],
7778 /** What `resolve_settings` was asked for. */
113114 }),
114115 BILLING: service("BILLING", (method) => {
115116 if (method === "has_feature") return state.plan ? ok(true) : { ok: false, error: { code: "payment_required", message: "Deployments need the Team plan." } };
116− if (method === "check_limit") return ok({ state: "ok" });
117+ if (method === "check_limit") return ok({ state: state.limit, message: state.limit === "stopped" ? "The workspace reached its spend limit." : null });
117118 if (method === "prices") return { prices: [] };
118119 return ok(null);
119120 }),
226227 assert.equal((await post(`/jobs/${build.deploy_id}/started`, { token: build.token })).ok, false);
227228 });
228229
230+test("a sandbox's build is still charged by the second; a workflow job's never is", async () => {
231+ // A classic build: what a push opens, built by g1t's sandbox.
232+ const { createHash } = await import("node:crypto");
233+ const token = "f".repeat(64);
234+ w.state.sqlite
235+ .prepare(
236+ `INSERT INTO deployments (id, project_id, workspace, slug, repo_id, repo, kind, branch, number, commit_sha, script, status,
237+ token_hash, trusted, created_by, created_at)
238+ VALUES ('dpl_sandbox', ?, 'acme', 'web', ?, 'acme/web', 'preview', 'other', NULL, ?, 'web-git-other-acme', 'building', ?, 1, 'g1t', '2026-10-12T00:00:00Z')`,
239+ )
240+ .run(PROJECT, REPO, SHA, createHash("sha256").update(token).digest("hex"));
241+ const finish = { worker: { compatibilityDate: "2026-09-26", vars: {} }, completionJwt: "done", buildSeconds: 30 };
242+ assert.equal((await post("/jobs/dpl_sandbox/finish", { token, ...finish })).ok, true);
243+ assert.equal(w.state.calls.filter((call) => call === "BILLING charge_feature").length, 1);
244+ assert.deepEqual(rows("SELECT build_seconds FROM meters"), [{ build_seconds: 30 }]);
245+ // A workflow job's build of the same project, finished and then failed: not charged.
246+ const { value: done } = await open();
247+ assert.equal((await post(`/jobs/${done.deploy_id}/finish`, { token: done.token, ...finish })).ok, true);
248+ const { value: failed } = await open();
249+ await post(`/jobs/${failed.deploy_id}/fail`, { token: failed.token, message: "npm run build failed", buildSeconds: 99 });
250+ assert.equal(w.state.calls.filter((call) => call === "BILLING charge_feature").length, 1);
251+ assert.deepEqual(rows("SELECT build_seconds FROM meters"), [{ build_seconds: 30 }]);
252+ // What runs after the build is the same app as any: in apps, which the sweep meters.
253+ assert.deepEqual(rows("SELECT script, kind FROM apps ORDER BY script"), [
254+ { script: "web-acme", kind: "production" },
255+ { script: "web-git-other-acme", kind: "preview" },
256+ ]);
257+});
258+
229259 test("another branch builds its preview; a pull request's run builds the pull request's", async () => {
230260 w.state.run = { ...w.state.run, ref: "refs/heads/feature/x" };
231261 const branch = (await open()).value;
265295 assert.deepEqual([unpaid.ok, unpaid.error.code], [false, "payment_required"]);
266296 assert.deepEqual(rows("SELECT status FROM deployments"), [{ status: "skipped" }]);
267297 w.state.plan = true;
298+ w.state.limit = "stopped";
299+ const stopped = await open();
300+ assert.deepEqual([stopped.ok, stopped.error.code, stopped.error.message], [false, "payment_required", "The workspace reached its spend limit."]);
301+ w.state.limit = "ok";
268302 w.state.sqlite.prepare("UPDATE settings SET previews = 0").run();
269303 w.state.run = { ...w.state.run, ref: "refs/heads/feature" };
270304 assert.equal(await refused({}), "conflict");