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

A run picked "none" never starts: it spends nothing and says "This repository has no model account" on its page and its issue #2128

Merged opened this

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

Merged

scout/forge:scout/2096-a-run-picked-none-never-starts-it-spends → main 1 commit fork

  • Merged. scout/2096-a-run-picked-none-never-starts-it-spends was merged into main as aa87f83.

Review

Sign in to review this change.
2 files changed +63 −2
src/crew/runner.ts +1 −1
@@ -923,7 +923,7 @@ async function work(
923923 // else's run waits for a grant rather than spend what is not theirs.
924924 if (picked.kind === "none") {
925925 const said =
926 "no model credential reaches this repository; ask an admin to grant one";
926 "This repository has no model account: ask an admin to grant one";
927927 write(said);
928928 return { state: "failed", summary: said };
929929 }
test/crew-nocredential.test.ts +62 −1
@@ -8,7 +8,11 @@ import { existsSync } from "node:fs";
88 import type { App } from "../src/app.ts";
99 import type { Repo } from "../src/core/repos.ts";
1010 import type { User } from "../src/core/users.ts";
11 import { addToCrew } from "../src/actions/crew.ts";
12 import { commitToBranch, createRepo } from "../src/actions/repos.ts";
13 import { createUser } from "../src/core/users.ts";
1114 import { assignIssue, openIssue } from "../src/actions/issues.ts";
15 import { getUsage } from "../src/core/usage.ts";
1216 import { readRunLog } from "../src/runs/log.ts";
1317 import {
1418 agentRunSettled,
@@ -47,7 +51,7 @@ describe("the crew, with a stand-in Claude Code: no credential reaches the repos
4751 (r) => r.issueId === issue.value.id,
4852 );
4953 const said =
50 "no model credential reaches this repository; ask an admin to grant one";
54 "This repository has no model account: ask an admin to grant one";
5155 expect(run.state).toBe("failed");
5256 expect(run.summary).toContain(said);
5357 expect(readRunLog(standIn.dataDir, run.id).lines.join("\n")).toContain(
@@ -55,8 +59,65 @@ describe("the crew, with a stand-in Claude Code: no credential reaches the repos
5559 );
5660 // The agent never started, so no credential was handed to it.
5761 expect(existsSync(standIn.argsFile)).toBe(false);
62 // Nothing spent: no ledger row for the run, no cost on it.
63 expect(getUsage(app.db, run.id)?.costUsd ?? 0).toBe(0);
64 expect(run.costUsd).toBe(0);
5865 } finally {
5966 delete process.env.ANTHROPIC_API_KEY;
6067 }
6168 }, 60_000);
69
70 test("a fresh outsider's repository with an existing agent does not start; an admin's does", async () => {
71 process.env.ANTHROPIC_API_KEY = "sk-forge-own-token";
72 try {
73 const o = await createUser(app.db, {
74 trusted: true,
75 username: "otto",
76 email: "[email protected]",
77 password: "a-good-passphrase",
78 });
79 if (!o.ok) throw new Error(o.error);
80 const otto = o.value;
81 const made = await createRepo(app, otto, {
82 name: "yard",
83 visibility: "private",
84 });
85 if (!made.ok) throw new Error(String(made.error));
86 const yard = made.value;
87 const seeded = await commitToBranch(app, otto, yard, {
88 message: "Start",
89 files: [{ path: "README.md", content: "# yard\n" }],
90 });
91 if (!seeded.ok) throw new Error(JSON.stringify(seeded.error));
92 expect(addToCrew(app, otto, yard, "bot").ok).toBe(true);
93 const issue = openIssue(app, otto, yard, { title: "Mow", body: "Soon." });
94 if (!issue.ok) throw new Error(String(issue.error));
95 expect(assignIssue(app, otto, yard, issue.value, bot).ok).toBe(true);
96 const run = await agentRunSettled(
97 app,
98 yard,
99 (r) => r.issueId === issue.value.id,
100 );
101 expect(run.state).toBe("failed");
102 expect(run.summary).toContain("This repository has no model account");
103 expect(run.costUsd).toBe(0);
104 expect(getUsage(app.db, run.id)?.costUsd ?? 0).toBe(0);
105 expect(existsSync(standIn.argsFile)).toBe(false);
106
107 // The same flow, the owner an admin: the run starts.
108 app.db.query("UPDATE user SET is_admin = 1 WHERE id = ?1").run(otto.id);
109 const again = openIssue(app, otto, yard, { title: "Rake", body: "Now." });
110 if (!again.ok) throw new Error(String(again.error));
111 expect(assignIssue(app, otto, yard, again.value, bot).ok).toBe(true);
112 const started = await agentRunSettled(
113 app,
114 yard,
115 (r) => r.issueId === again.value.id,
116 );
117 expect(started.summary).not.toContain("no model account");
118 expect(existsSync(standIn.argsFile)).toBe(true);
119 } finally {
120 delete process.env.ANTHROPIC_API_KEY;
121 }
122 }, 90_000);
62123 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs