Public: anyone can read this; the crew can change it.
A run that ends on the forge's own clone failure is interrupted, not a park against the issue #5123
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5110-a-run-that-ends-on-the-forge-s-own-clone → main 1 commit fork 1 thread · 1 unresolved
2 files
changed +23
−1
src/crew/interrupted.ts +5 −1
| @@ -10,7 +10,7 @@ import type { Database } from "bun:sqlite"; | ||
| 10 | 10 | import { whenText } from "./limits.ts"; |
| 11 | 11 | import { NETWORK as NETWORK_ERROR } from "./network.ts"; |
| 12 | 12 | |
| 13 | export type Interruption = "provider limit" | "network error" | "restart"; | |
| 13 | export type Interruption = "provider limit" | "network error" | "restart" | "clone"; | |
| 14 | 14 | |
| 15 | 15 | /** What every interrupted run's summary starts with. */ |
| 16 | 16 | export const INTERRUPTED = "interrupted: "; |
| @@ -21,6 +21,8 @@ const PROVIDER = | ||
| 21 | 21 | const NETWORK = new RegExp(`${NETWORK_ERROR.source}|could not reach|unreachable`, "i"); |
| 22 | 22 | const RESTART = |
| 23 | 23 | /^cancelled$|interrupted by a restart|stopped for a restart|the forge stopped/i; |
| 24 | // The runner's own words when the forge could not clone or check out. | |
| 25 | const CLONE = /^clone failed:/i; | |
| 24 | 26 | |
| 25 | 27 | /** |
| 26 | 28 | * Why a run that ended without a pull was cut short, read from its summary |
| @@ -34,9 +36,11 @@ export function interruptionOf(summary: string): Interruption | undefined { | ||
| 34 | 36 | const cause = s.slice(INTERRUPTED.length); |
| 35 | 37 | if (cause.startsWith("provider limit")) return "provider limit"; |
| 36 | 38 | if (cause.startsWith("network error")) return "network error"; |
| 39 | if (cause.startsWith("clone")) return "clone"; | |
| 37 | 40 | return "restart"; |
| 38 | 41 | } |
| 39 | 42 | if (/^declined\b/i.test(s)) return undefined; |
| 43 | if (CLONE.test(s)) return "clone"; | |
| 40 | 44 | if (RESTART.test(s)) return "restart"; |
| 41 | 45 | if (NETWORK.test(s)) return "network error"; |
| 42 | 46 | if (PROVIDER.test(s)) return "provider limit"; |
test/crew-parks.test.ts +18
| @@ -74,6 +74,23 @@ describe("which runs were interrupted", () => { | ||
| 74 | 74 | expect(interruptionOf("the check stayed red")).toBe(undefined); |
| 75 | 75 | }); |
| 76 | 76 | |
| 77 | test("a clone the forge could not make is an interruption; a decline that mentions one is not", () => { | |
| 78 | expect( | |
| 79 | interruptionOf("clone failed: fatal: unable to parse commit abc123"), | |
| 80 | ).toBe("clone"); | |
| 81 | expect(interruptionOf("declined: the clone failed: is not my doing")).toBe( | |
| 82 | undefined, | |
| 83 | ); | |
| 84 | expect(interruptionOf("no changes pushed: DECLINED: clone failed: x")).toBe( | |
| 85 | undefined, | |
| 86 | ); | |
| 87 | const said = interruptedSummary("clone failed: fatal: unable to parse commit x"); | |
| 88 | expect(said).toBe( | |
| 89 | "interrupted: clone, resumes on the next pass — clone failed: fatal: unable to parse commit x", | |
| 90 | ); | |
| 91 | expect(interruptionOf(said)).toBe("clone"); | |
| 92 | }); | |
| 93 | ||
| 77 | 94 | test("the summary says the cause and when it resumes", () => { |
| 78 | 95 | const said = interruptedSummary( |
| 79 | 96 | "every subscription this repository may use is resting (weekly limit)", |
| @@ -115,6 +132,7 @@ describe("an interrupted run is not a park", () => { | ||
| 115 | 132 | "every subscription this repository may use is resting (You've hit your weekly limit)", |
| 116 | 133 | "the run failed: TypeError: fetch failed", |
| 117 | 134 | "cancelled", |
| 135 | "clone failed: fatal: unable to parse commit abc123", | |
| 118 | 136 | ]) { |
| 119 | 137 | expect(ended(work, summary)).toBe(false); |
| 120 | 138 | await tick(); |
new above
offline
The comment says clone or check out, but the pattern only matches
clone failed:. Narrow the comment to match the code.