Public: anyone can read this; the crew can change it.

A run queued again after a restart is not charged again for the minutes the interrupted run already paid #11639

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

torque/forge:torque/11582-a-run-queued-again-after-a-restart-is-no → main 1 commit fork 1 thread · 1 unresolved

  • Merged. torque/11582-a-run-queued-again-after-a-restart-is-no was merged into main as 68e4994.

Review

Sign in to review this change.
1 file changed +32 −0
test/crew-stopped-twice.test.ts +32
@@ -9,6 +9,9 @@ import { createApp, type App } from "../src/app.ts";
99 import { claimRun, createRun, getRun, listQueued } from "../src/core/runs.ts";
1010 import { createUser } from "../src/core/users.ts";
1111 import { requeueInterrupted } from "../src/crew/dispatch.ts";
12 import { daySpent } from "../src/core/account_ceiling.ts";
13 import { noteRunProgress } from "../src/core/runs.ts";

noteRunProgress comes from ../src/core/runs.ts, which line 9 already imports. Add it to that import instead of a second one.

14 import { recordUsage, usageTotals } from "../src/core/usage.ts";
1215 import { STALE_AFTER_MS } from "../src/runs/lease.ts";
1316 import type { User } from "../src/core/users.ts";
1417
@@ -82,4 +85,33 @@ describe("a run a restart cuts off", () => {
8285 expect(after.summary).toBe("stopped twice by its forge restarting; not tried again");
8386 expect(forIssue(issue.id)).toEqual([]);
8487 });
88
89 test("an interruption is charged once: the sum of each run's own spend", async () => {
90 const { issue, run } = await setup();
91 const at = () => new Date().toISOString();
92 const line = (id: string, costUsd: number, outcome: "running" | "ok") =>
93 recordUsage(app.db, {
94 runId: id, repoId: run.repoId, issueId: issue.id, agentId: bot.id,
95 subscriptionId: null, kind: "build", tokensIn: 10, tokensOut: 5,
96 costUsd, wallMs: 1000, outcome, at: at(),
97 });
98 const owed = () =>
99 usageTotals(app.db, "issue", { since: "1970-01-01T00:00:00.000Z", repoId: run.repoId })
100 .reduce((sum, t) => sum + t.costUsd, 0);
101 // The first run paid $1.50 before the restart cut it off.
102 claimRun(app.db, run.id);
103 noteRunProgress(app.db, run.id, { costUsd: 1.5 });
104 line(run.id, 1.5, "running");
105 const before = daySpent(app.db, alice.id, new Date());
106 lapse(run.id);
107 requeueInterrupted(app);
108 // Being queued again costs nothing.
109 expect(owed()).toBeCloseTo(1.5, 10);
110 // The run that carries on reports only what it spent itself: $0.75.
111 const next = forIssue(issue.id)[0]!;
112 claimRun(app.db, next.id);
113 line(next.id, 0.75, "ok");
114 expect(owed()).toBeCloseTo(2.25, 10);
115 expect(daySpent(app.db, alice.id, new Date()) - before).toBeCloseTo(0.75, 10);
116 });
85117 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs