feat(ci): re-run failed PR jobs once when every failure was a lost runner (#19640)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributors wait for PR CI before merge, so CI wall-clock time sets
the pace of work
> - PR CI runs on self-hosted AWS runners, and these runners are
sometimes lost mid-job
> - On 2026-11-06, 21 of 51 failed jobs on commits that later passed
were runner loss. The losses came in fleet-wide bursts, for example 10
jobs on 10 instances in 51 seconds
> - After each loss, a person must notice the failure and click "Re-run
failed jobs"
> - This pull request adds a workflow that re-runs the failed jobs once,
only when every failure was a lost runner
> - The benefit is fewer manual re-runs and a shorter wait for CI to
pass

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The recovery of PR CI runs that failed only because a self-hosted runner
was lost.

**Subsystem affected**

CI: the trusted PR workflow (`pr.yml` → `pr-trusted.yml`).

**Current behavior**

When an AWS runner is lost, the job fails with "The self-hosted runner
lost communication with the server" or "The runner has received a
shutdown signal". The PR stays red until a person re-runs the failed
jobs.

**Proposed behavior**

When the `PR` workflow completes with a failure on attempt 1, a new
workflow examines the failed jobs. If every failed job lost its runner,
it re-runs the failed jobs one time. If any job failed for another
reason, it does nothing.

**Reason and benefit**

A runner loss is not a code problem. The re-run starts without waiting
for a person to notice the failure.

**Breaking changes**

None.

## What Changed

- Add `.github/workflows/ci-runner-loss-rerun.yml`. It triggers on
`workflow_run` for `PR`, and it acts only on attempt 1 failures of
`pull_request` runs in this repository. It runs on `ubuntu-latest`, so
it does not depend on the AWS fleet.
- Add `.github/scripts/runner-loss-rerun.mjs`. The script:
- lists the attempt-1 jobs and ignores the aggregate jobs `ci / verify`
and `ci / e2e`;
- finds runner loss in the check-run annotations. If the only annotation
is "The operation was canceled.", it reads the `##[error]` lines in the
job log;
- calls `rerun-failed-jobs` only if every remaining failed job lost its
runner;
- does not re-run when the gate job failed, the PR head moved, the PR is
closed, or a newer run exists for the same commit;
- writes the decision and a job table to the step summary. A `--dry-run`
mode never posts.
- Add 16 tests, with trimmed fixtures from four real runs.
- Add a paragraph about the workflow to the "Trusted PR Workflow"
section of `doc/DEVELOPING.md`.

## Verification

- `node --test .github/scripts/tests/runner-loss-rerun.test.mjs`: 16/16
pass. The tests cover:
- re-run when all jobs were lost, including the case where the signal is
only in the log;
- no re-run for mixed failures, real failures, only aggregate failures,
attempt 2, a failed gate, or a stale run;
- pagination, incomplete listings, a cross-host link that is refused,
and the workflow guards.
- `node --test '.github/scripts/tests/*.test.mjs'`: 760/760 pass.
- A read-only dry run against the real GitHub API for six recent runs
classified all six correctly.
- The workflow can run on GitHub only after it is on master. Reviewers
can make sure that the first runner-loss failure after the merge
triggers one re-run.

## Risks

- **Trust:** a re-run of failed jobs uses the outputs of the gate job
that succeeded on attempt 1, including its AWS runner admission. A
person who clicks "Re-run failed jobs" gets the same result. The only
difference is that `github-actions[bot]` triggers attempt 2. The
workflow never checks out or runs PR code.
- **Failed gate:** if the gate job itself failed, the script does
nothing. A re-run by the bot would put all jobs on GitHub-hosted
runners.
- **Profiling:** `scripts/ci-profile/export.mjs` exports only attempts
triggered by authorized users. The bot-triggered attempt 2 is not
exported to Honeycomb. Attempt 1 is still exported.
- **Events:** GitHub may not create `workflow_run` events for attempts
that `GITHUB_TOKEN` started. Then other `workflow_run` consumers do not
react to attempt 2. Required checks still update.
- **Permissions:** `actions: write`, `checks: read`, `contents: read`
and `pull-requests: read`. The last one lets the script confirm that the
run still belongs to the open PR's current head.
- **Cancelled jobs:** a runner loss where the lost jobs end as
`cancelled` and only the aggregate jobs show `failure` is not re-run.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use and
code execution. A Claude sub-agent wrote the change, and the parent
session reviewed it.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nicky LeachandClaude Opus 5.5 authored and GitHub committed 2026-11-06 15:05:42 -08:00
1 parent 913102870d
commit f2299cb00f
8 files changed
+4651

No files matched your search

+205
View File
@@ -0,0 +1,205 @@
#!/usr/bin/env node
/**
* runner-loss-rerun.mjs
*
* Re-runs the failed jobs of a first PR CI attempt once, but only when every
* failed job failed because its runner was lost. Runs from
* .github/workflows/ci-runner-loss-rerun.yml on workflow_run. It reads GitHub
* API metadata only and never touches the PR's code or artifacts.
*
* Local dry run against a real run (no rerun is requested):
* GITHUB_TOKEN=$(gh auth token) node .github/scripts/runner-loss-rerun.mjs --run-id <id> --dry-run
*/
import { appendFileSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
export const REPOSITORY = "paperclipai/paperclip";
export const REPOSITORY_ID = 1170821064;
export const PR_WORKFLOW_PATH = ".github/workflows/pr.yml";
// The runner's own messages when an AWS instance disappears mid-job.
export const RUNNER_LOSS_PATTERN = /lost communication with the server|received a shutdown signal/;
// `verify` and `e2e` in pr-trusted.yml only assert their `needs` results. They
// fail whenever a dependency failed and re-run with it.
export const AGGREGATE_JOBS = new Set(["ci / verify", "ci / e2e"]);
// ci-routing.yml checks the attempt's triggering actor. A bot-triggered attempt
// would move the whole run to GitHub-hosted runners, so leave it to a human.
export const GATE_JOB_PREFIX = "ci / gate / ";
// A shutdown signal often reaches the check run only as this annotation; the
// signal itself is logged as an error line in the job log.
const CANCELLED_MESSAGE = "The operation was canceled.";
export function createGithubClient({ token, fetcher = fetch, apiUrl = "https://api.github.com" }) {
if (!token) throw new Error("GITHUB_TOKEN is required");
const origin = new URL(apiUrl).origin;
const request = async (target, { method = "GET" } = {}) => {
const url = new URL(target, apiUrl);
if (url.origin !== origin) throw new Error(`Refusing to call ${url.origin}`);
const response = await fetcher(url.href, {
method,
headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${token}`, "X-GitHub-Api-Version": "2022-11-28" },
signal: AbortSignal.timeout(60_000),
});
if (!response.ok) throw new Error(`GitHub API ${method} ${url.pathname} failed (HTTP ${response.status})`);
return response;
};
return {
json: async (target) => (await request(target)).json(),
// Follows Link rel="next" until the last page. `field` selects the array
// in wrapped responses such as { total_count, jobs }.
paginate: async (target, field) => {
const items = [];
let totalCount;
for (let next = target, pages = 0; next; pages += 1) {
if (pages >= 100) throw new Error(`Too many pages for ${target}`);
const response = await request(next);
const body = await response.json();
const page = field ? body[field] : body;
if (!Array.isArray(page)) throw new Error(`Unexpected page shape for ${target}`);
items.push(...page);
if (field && Number.isInteger(body.total_count)) totalCount = body.total_count;
next = /<([^>]+)>;\s*rel="next"/.exec(response.headers.get("link") ?? "")?.[1];
}
if (totalCount !== undefined && items.length !== totalCount) {
throw new Error(`Incomplete listing for ${target}: ${items.length} of ${totalCount}`);
}
return items;
},
// Job logs answer with a redirect to a signed download URL.
text: async (target) => (await request(target)).text(),
post: async (target) => { await request(target, { method: "POST" }); },
};
}
export function logShowsRunnerLoss(log) {
return log.split("\n").some((line) => line.includes("##[error]") && RUNNER_LOSS_PATTERN.test(line));
}
/** Classifies one failed job. Logs are read only when annotations are inconclusive. */
export async function classifyJob(job, { annotations, readLog }) {
const failures = annotations.filter((annotation) => annotation.annotation_level === "failure");
const isRunnerLoss = (annotation) => RUNNER_LOSS_PATTERN.test(annotation.message ?? "");
// A step can fail for a real reason before the runner is lost (for example a
// test failure, then a loss during an always() upload step). Any failure
// annotation other than runner loss or cancellation marks a real failure.
const real = failures.find((annotation) => !isRunnerLoss(annotation) && annotation.message !== CANCELLED_MESSAGE);
if (real) return { lost: false, evidence: firstLine(real.message) };
const match = failures.find(isRunnerLoss);
if (match) return { lost: true, evidence: firstLine(match.message) };
// Only cancellations are left: the shutdown signal, if any, is in the log.
if (failures.length > 0) {
if (logShowsRunnerLoss(await readLog(job))) {
return { lost: true, evidence: "job log: runner received a shutdown signal or lost communication" };
}
return { lost: false, evidence: `${CANCELLED_MESSAGE} (no runner-loss error in the job log)` };
}
return { lost: false, evidence: "no failure annotation" };
}
function firstLine(message) {
const line = String(message).split("\n")[0];
return line.length > 160 ? `${line.slice(0, 157)}...` : line;
}
export function eligibleRun(run) {
if (run.repository?.full_name !== REPOSITORY || run.repository?.id !== REPOSITORY_ID) return "run is not in the canonical repository";
if (run.path !== PR_WORKFLOW_PATH) return `workflow is ${run.path}, not ${PR_WORKFLOW_PATH}`;
if (run.event !== "pull_request") return `event is ${run.event}, not pull_request`;
if (run.status !== "completed" || run.conclusion !== "failure") return `run is ${run.status}/${run.conclusion}, not completed/failure`;
// One automatic re-run only. A higher attempt also means someone re-ran it.
if (run.run_attempt !== 1) return `run is on attempt ${run.run_attempt}; only attempt 1 is re-run automatically`;
return undefined;
}
/**
* Re-running an older run joins the PR's `cancel-in-progress` concurrency
* group and would cancel newer CI. Require the run to be the latest CI for the
* PR's current head.
*/
export async function staleReason(client, run) {
const pulls = await client.paginate(`/repos/${REPOSITORY}/commits/${run.head_sha}/pulls?per_page=100`);
const open = pulls.filter((pr) => pr.state === "open" && pr.base?.repo?.id === REPOSITORY_ID
&& pr.head?.repo?.id === run.head_repository?.id && pr.head?.sha === run.head_sha);
if (open.length === 0) return "no open pull request still has this run's head commit";
const runs = await client.paginate(
`/repos/${REPOSITORY}/actions/workflows/pr.yml/runs?event=pull_request&head_sha=${run.head_sha}&per_page=100`,
"workflow_runs",
);
const newer = runs.find((candidate) => candidate.id > run.id && candidate.conclusion !== "skipped");
if (newer) return `a newer PR run (${newer.id}) exists for this head commit`;
return undefined;
}
export async function decide(client, run) {
const decision = { runId: run.id, attempt: run.run_attempt, rerun: false, jobs: [] };
const ineligible = eligibleRun(run);
if (ineligible) return { ...decision, reason: ineligible };
const jobs = await client.paginate(`/repos/${REPOSITORY}/actions/runs/${run.id}/attempts/1/jobs?per_page=100`, "jobs");
const failed = jobs.filter((job) => job.conclusion === "failure" && !AGGREGATE_JOBS.has(job.name));
if (failed.length === 0) return { ...decision, reason: "no failed jobs other than the verify/e2e aggregates" };
for (const job of failed) {
if (job.name.startsWith(GATE_JOB_PREFIX)) {
decision.jobs.push({ id: job.id, name: job.name, lost: false, evidence: "routing gate failed; a bot re-run would lose AWS routing" });
continue;
}
const annotations = await client.paginate(`/repos/${REPOSITORY}/check-runs/${job.id}/annotations?per_page=100`);
const readLog = (entry) => client.text(`/repos/${REPOSITORY}/actions/jobs/${entry.id}/logs`);
decision.jobs.push({ id: job.id, name: job.name, ...(await classifyJob(job, { annotations, readLog })) });
}
const real = decision.jobs.filter((job) => !job.lost);
if (real.length) return { ...decision, reason: `${real.length} of ${failed.length} failed jobs did not lose their runner` };
const stale = await staleReason(client, run);
if (stale) return { ...decision, reason: stale };
return { ...decision, rerun: true, reason: `all ${failed.length} failed jobs lost their runner` };
}
export function renderSummary(decision, { dryRun = false } = {}) {
const action = decision.rerun ? (dryRun ? "Would re-run failed jobs (dry run)" : "Re-ran failed jobs") : "No automatic re-run";
const lines = [
"### CI runner-loss re-run", "",
`**${action}** for run ${decision.runId} attempt ${decision.attempt}: ${decision.reason}.`, "",
];
if (decision.jobs.length) {
lines.push("| Job | Runner lost | Evidence |", "| --- | --- | --- |");
for (const job of decision.jobs) {
lines.push(`| ${escapeCell(job.name)} | ${job.lost ? "yes" : "no"} | ${escapeCell(job.evidence)} |`);
}
lines.push("");
}
return `${lines.join("\n")}\n`;
}
const escapeCell = (value) => String(value).replace(/\|/g, "\\|").replace(/[\r\n]+/g, " ");
export async function main({ env = process.env, argv = process.argv.slice(2), fetcher = fetch, log = console.log } = {}) {
const dryRun = argv.includes("--dry-run");
const argRunId = argv[argv.indexOf("--run-id") + 1];
const event = env.GITHUB_EVENT_PATH ? JSON.parse(readFileSync(env.GITHUB_EVENT_PATH, "utf8")) : undefined;
const runId = argv.includes("--run-id") ? argRunId : event?.workflow_run?.id;
if (!/^[1-9]\d*$/.test(String(runId))) throw new Error("Provide --run-id or a workflow_run event");
const client = createGithubClient({ token: env.GITHUB_TOKEN, fetcher });
// Read the run's current state rather than the event snapshot: its attempt
// number is above 1 if anyone re-ran it after this event was queued.
const run = await client.json(`/repos/${REPOSITORY}/actions/runs/${runId}`);
const decision = await decide(client, run);
if (decision.rerun && !dryRun) {
await client.post(`/repos/${REPOSITORY}/actions/runs/${run.id}/rerun-failed-jobs`);
}
const summary = renderSummary(decision, { dryRun });
log(summary);
if (env.GITHUB_STEP_SUMMARY) appendFileSync(env.GITHUB_STEP_SUMMARY, summary);
return decision;
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
}
@@ -0,0 +1,976 @@
{
"source": "GET /repos/paperclipai/paperclip/actions/runs/42264785415/attempts/1 (+ jobs and annotations), trimmed",
"run": {
"id": 42264785415,
"name": "PR",
"head_branch": "fix/rc16-bootstrap-proof-after-connect",
"head_sha": "f418091616c6f7b0af1d4c3115fa47c500015a8a",
"path": ".github/workflows/pr.yml",
"run_number": 60214,
"event": "pull_request",
"status": "completed",
"conclusion": "failure",
"workflow_id": 249194268,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415",
"created_at": "2026-11-06T17:47:06Z",
"run_attempt": 1,
"run_started_at": "2026-11-06T17:47:06Z",
"actor": {
"login": "cryppadotta",
"id": 34892728,
"type": "User"
},
"triggering_actor": {
"login": "cryppadotta",
"id": 34892728,
"type": "User"
},
"repository": {
"id": 1170821064,
"full_name": "paperclipai/paperclip"
},
"head_repository": {
"id": 1170821064,
"full_name": "paperclipai/paperclip"
}
},
"jobs": [
{
"id": 128053744882,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128053744882",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:47:07Z",
"started_at": "2026-11-06T17:47:30Z",
"completed_at": "2026-11-06T17:48:06Z",
"name": "ci / gate / Select trusted runner",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128053744882",
"labels": [
"runs-on/fleet=paperclip-ci-routing-x64/env=public-ci"
],
"runner_name": "runs-on--i-0bd15c639391d3334--e87997a5-9",
"runner_group_name": "paperclip-ci-routing"
},
{
"id": 128054259560,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259560",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:32Z",
"completed_at": "2026-11-06T17:58:38Z",
"name": "ci / Typecheck + Release Registry",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259560",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0f9c9e372436e224a--ce576ff8-f",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054259668,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259668",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:33Z",
"completed_at": "2026-11-06T17:50:19Z",
"name": "ci / policy",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259668",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-02004b0b62771f088--836450ae-0",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054259676,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259676",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:30Z",
"completed_at": "2026-11-06T17:53:43Z",
"name": "ci / Verify Paperclip Runner (static checks)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259676",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0499d8944ad722cc8--d269ace8-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054259678,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259678",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:34Z",
"completed_at": "2026-11-06T17:49:18Z",
"name": "ci / Docker context integrity",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259678",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0f415a1e7415eded5--1ead060a-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054259685,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259685",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:33Z",
"completed_at": "2026-11-06T17:57:02Z",
"name": "ci / Build",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259685",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-04162f556ced67989--89ceb20a-2",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054259696,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259696",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:32Z",
"completed_at": "2026-11-06T17:56:06Z",
"name": "ci / Canary Dry Run",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259696",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0e73a58cd878a2eba--ed01493c-d",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054259836,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259836",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:33Z",
"completed_at": "2026-11-06T18:02:15Z",
"name": "ci / Verify Paperclip Runner (vitest package)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259836",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-03b78c874a221b14d--3f9580f1-c",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054259837,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259837",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:34Z",
"completed_at": "2026-11-06T17:50:33Z",
"name": "ci / Select Vitest files",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259837",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0965b01d7ca22acc7--46a2c286-5",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054259878,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054259878",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:34Z",
"completed_at": "2026-11-06T17:55:01Z",
"name": "ci / Verify Paperclip Runner (vitest native server)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054259878",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0a3c1c296733234fa--24120234-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054260016,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054260016",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:33Z",
"completed_at": "2026-11-06T17:55:03Z",
"name": "ci / e2e shard (2/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054260016",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-05170a3c06ca46460--d09cbbe4-4",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054260029,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054260029",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:33Z",
"completed_at": "2026-11-06T17:56:53Z",
"name": "ci / e2e shard (3/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054260029",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-03be127710fc5e3f3--1232d471-3",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054260068,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054260068",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:34Z",
"completed_at": "2026-11-06T17:57:28Z",
"name": "ci / e2e shard (4/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054260068",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0e30b923f731a8617--7f3f286f-a",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054260075,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054260075",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:33Z",
"completed_at": "2026-11-06T17:55:25Z",
"name": "ci / e2e shard (6/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054260075",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0afa242a4729d2f5e--1e1e7e9a-4",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054260099,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054260099",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:33Z",
"completed_at": "2026-11-06T17:54:46Z",
"name": "ci / e2e shard (8/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054260099",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-08fc2533358d5e023--bc9c002a-0",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054260141,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054260141",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:34Z",
"completed_at": "2026-11-06T17:53:23Z",
"name": "ci / e2e shard (7/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054260141",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0e1920f3a20679a6d--4eaf0fed-f",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054260232,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054260232",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:35Z",
"completed_at": "2026-11-06T17:54:31Z",
"name": "ci / e2e shard (5/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054260232",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-01730060988e5694a--f4f43bb3-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128054260285,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128054260285",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:48:07Z",
"started_at": "2026-11-06T17:48:36Z",
"completed_at": "2026-11-06T17:53:55Z",
"name": "ci / e2e shard (1/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128054260285",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0398f5a4a13d3bb86--06283b1d-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055537875,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055537875",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:08Z",
"completed_at": "2026-11-06T17:54:45Z",
"name": "ci / Verify serialized server suites (6/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055537875",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-03863cc26751ee81d--ed7003ac-e",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055537897,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055537897",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:09Z",
"completed_at": "2026-11-06T17:54:56Z",
"name": "ci / Verify serialized server suites (5/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055537897",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-07c73636f0cb7025c--f79ac0af-8",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055537952,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055537952",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:50:57Z",
"completed_at": "2026-11-06T17:54:28Z",
"name": "ci / Verify serialized server suites (8/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055537952",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-03b2eebb3d82945ba--deae2c56-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055537956,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055537956",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:11Z",
"completed_at": "2026-11-06T17:54:27Z",
"name": "ci / Verify serialized server suites (3/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055537956",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0efadc3ad82b120d2--95e7dcf3-7",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055537962,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055537962",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:50:57Z",
"completed_at": "2026-11-06T17:57:23Z",
"name": "ci / Verify serialized server suites (2/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055537962",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-07d521bfd6e73257a--19c15d8d-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538017,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538017",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:17Z",
"completed_at": "2026-11-06T17:54:58Z",
"name": "ci / Verify serialized server suites (7/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538017",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-01e662eefaa655648--55d2ece6-3",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538089,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538089",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:17Z",
"completed_at": "2026-11-06T17:56:27Z",
"name": "ci / Verify serialized server suites (1/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538089",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0ad89fde02e893c90--06ed32bb-a",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538125,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538125",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:17Z",
"completed_at": "2026-11-06T17:54:49Z",
"name": "ci / Verify serialized server suites (4/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538125",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-064cd806776d929f4--274b294f-f",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538149,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538149",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:09Z",
"completed_at": "2026-11-06T18:06:26Z",
"name": "ci / General tests (server (2/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538149",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0eea471a99f85b880--00c81037-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538165,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538165",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:08Z",
"completed_at": "2026-11-06T17:58:45Z",
"name": "ci / General tests (server (1/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538165",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-04800a2d79c810238--b9ce8e93-3",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538174,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538174",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:08Z",
"completed_at": "2026-11-06T17:59:43Z",
"name": "ci / General tests (server (6/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538174",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0efaf057cc5a98d48--edea4c47-2",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538185,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538185",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:17Z",
"completed_at": "2026-11-06T17:57:34Z",
"name": "ci / General tests (server (7/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538185",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0205811b68f4b3fd1--52920e1d-9",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538207,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538207",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:14Z",
"completed_at": "2026-11-06T18:00:06Z",
"name": "ci / General tests (server (5/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538207",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0a37eb2d9fde459d4--36952ebd-0",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538219,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538219",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:19Z",
"completed_at": "2026-11-06T17:59:30Z",
"name": "ci / General tests (server (3/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538219",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0188e96e97af2f59c--b8e855e1-4",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538250,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538250",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:13Z",
"completed_at": "2026-11-06T18:00:22Z",
"name": "ci / General tests (server (9/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538250",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-00c505418cc8ee70e--fb114f23-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538253,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538253",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:12Z",
"completed_at": "2026-11-06T17:56:10Z",
"name": "ci / General tests (workspaces-b)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538253",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-05fc778dbd976d597--c58725c9-c",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538265,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538265",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:17Z",
"completed_at": "2026-11-06T17:54:31Z",
"name": "ci / Verify serialized server suites (9/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538265",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-06a3bf8d19333dd51--c4258f53-2",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538302,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538302",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:33Z",
"started_at": "2026-11-06T17:51:17Z",
"completed_at": "2026-11-06T17:58:51Z",
"name": "ci / General tests (server (12/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538302",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-07605db9087df8358--8137b272-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538306,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538306",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:13Z",
"completed_at": "2026-11-06T17:59:36Z",
"name": "ci / General tests (server (11/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538306",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0d357b1cfb24c69f8--9cc138ac-0",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538334,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538334",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:14Z",
"completed_at": "2026-11-06T17:57:39Z",
"name": "ci / General tests (server (8/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538334",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-06bee8abdadd855f6--4777aea8-3",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538337,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538337",
"status": "completed",
"conclusion": "failure",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:17Z",
"completed_at": "2026-11-06T17:57:43Z",
"name": "ci / General tests (server (4/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538337",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-00861ea950b0c2df8--e7934d4c-7",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538364,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538364",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:16Z",
"completed_at": "2026-11-06T17:56:44Z",
"name": "ci / General tests (chat (2/3))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538364",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-04505acda283d309b--e4997141-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538368,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538368",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:23Z",
"completed_at": "2026-11-06T17:57:14Z",
"name": "ci / General tests (workspaces-a (1/2))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538368",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-01559d90b1c03dafc--39658d89-4",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538374,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538374",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:20Z",
"completed_at": "2026-11-06T17:57:56Z",
"name": "ci / General tests (server (10/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538374",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-05115335e959346eb--0b11d7ba-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538399,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538399",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:16Z",
"completed_at": "2026-11-06T17:57:35Z",
"name": "ci / General tests (chat (1/3))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538399",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-020c27d3a16ef9c2b--ea30bd71-2",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538415,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538415",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:18Z",
"completed_at": "2026-11-06T17:55:40Z",
"name": "ci / General tests (workspaces-a (2/2))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538415",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0c674a4b6f26936cf--ec849767-9",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128055538546,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128055538546",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:50:34Z",
"started_at": "2026-11-06T17:51:19Z",
"completed_at": "2026-11-06T17:59:30Z",
"name": "ci / General tests (chat (3/3))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128055538546",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-05c610ca30a34300b--725c544b-c",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128059102519,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128059102519",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T17:57:28Z",
"started_at": "2026-11-06T17:58:02Z",
"completed_at": "2026-11-06T17:58:07Z",
"name": "ci / e2e",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128059102519",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-02635cc9003444823--4f0acae5-e",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128063690619,
"run_id": 42264785415,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42264785415/job/128063690619",
"status": "completed",
"conclusion": "failure",
"created_at": "2026-11-06T18:06:26Z",
"started_at": "2026-11-06T18:07:00Z",
"completed_at": "2026-11-06T18:07:08Z",
"name": "ci / verify",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128063690619",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-007fb864dc5bb537d--f46a91cb-e",
"runner_group_name": "paperclip-public-pr"
}
],
"annotations": {
"128055538337": [
{
"path": ".github",
"start_line": 285371,
"end_line": 285371,
"annotation_level": "failure",
"title": "",
"message": "Process completed with exit code 1.",
"raw_details": ""
},
{
"path": "server/src/__tests__/connection-management.integration.test.ts",
"start_line": 1806,
"end_line": 1806,
"annotation_level": "failure",
"title": "[@paperclipai/server] src/__tests__/connection-management.integration.test.ts > connection management with production default agents > reads only the authorized Anthropic account identity through actual agent authentication",
"message": "AssertionError: expected 3 to be 2 // Object.is equality\n\n- Expected\n+ Received\n\n- 2\n+ 3\n\n ❯ src/__tests__/connection-management.integration.test.ts:1806:21\n\n",
"raw_details": ""
},
{
"path": ".github",
"start_line": 162423,
"end_line": 162423,
"annotation_level": "failure",
"title": "",
"message": "command (/home/runner/_work/paperclip/paperclip/) /home/runner/setup-pnpm/node_modules/.bin/bin/pnpm run test:run:general -- --group general-server-without-chat --shard-index 3 --shard-count 12 exited (1)",
"raw_details": ""
}
],
"128063690619": [
{
"path": ".github",
"start_line": 120,
"end_line": 120,
"annotation_level": "failure",
"title": "",
"message": "Process completed with exit code 1.",
"raw_details": ""
}
]
},
"logs": {},
"total_count": 47
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,979 @@
{
"source": "GET /repos/paperclipai/paperclip/actions/runs/42277969418/attempts/1 (+ jobs and annotations), trimmed",
"run": {
"id": 42277969418,
"name": "PR",
"head_branch": "fix/status-card-compile-inline-guidance",
"head_sha": "85a1ebd70ebb687bac36cd7d3aa035f08b1b76fe",
"path": ".github/workflows/pr.yml",
"run_number": 60340,
"event": "pull_request",
"status": "completed",
"conclusion": "failure",
"workflow_id": 249194268,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418",
"created_at": "2026-11-06T19:15:10Z",
"run_attempt": 1,
"run_started_at": "2026-11-06T19:15:10Z",
"actor": {
"login": "cryppadotta",
"id": 34892728,
"type": "User"
},
"triggering_actor": {
"login": "cryppadotta",
"id": 34892728,
"type": "User"
},
"repository": {
"id": 1170821064,
"full_name": "paperclipai/paperclip"
},
"head_repository": {
"id": 1170821064,
"full_name": "paperclipai/paperclip"
}
},
"jobs": [
{
"id": 128099176127,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099176127",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:11Z",
"started_at": "2026-11-06T19:15:35Z",
"completed_at": "2026-11-06T19:15:51Z",
"name": "ci / gate / Select trusted runner",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099176127",
"labels": [
"runs-on/fleet=paperclip-ci-routing-x64/env=public-ci"
],
"runner_name": "runs-on--i-05f6b9b2930a13c18--04e93093-7",
"runner_group_name": "paperclip-ci-routing"
},
{
"id": 128099518899,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099518899",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:14Z",
"completed_at": "2026-11-06T19:18:48Z",
"name": "ci / Select Vitest files",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099518899",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-022ce6a3fe4cb976a--9b74ec42-3",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099518966,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099518966",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:13Z",
"completed_at": "2026-11-06T19:17:52Z",
"name": "ci / policy",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099518966",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-06dd55e2393795fc3--8bdf1065-e",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099518969,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099518969",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:14Z",
"completed_at": "2026-11-06T19:30:17Z",
"name": "ci / Verify Paperclip Runner (vitest package)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099518969",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-09eb0ccea8ce6e3f1--bf3c948a-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099518970,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099518970",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:14Z",
"completed_at": "2026-11-06T19:26:13Z",
"name": "ci / Typecheck + Release Registry",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099518970",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-06107c2f4ad4e0afa--e8a52bad-e",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519017,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519017",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:14Z",
"completed_at": "2026-11-06T19:21:53Z",
"name": "ci / Verify Paperclip Runner (static checks)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519017",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0ce2155a81766d11b--1c931e39-3",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519063,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519063",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:16Z",
"completed_at": "2026-11-06T19:17:03Z",
"name": "ci / Docker context integrity",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519063",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0e251a79a0c87658b--54fd3399-0",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519192,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519192",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:14Z",
"completed_at": "2026-11-06T19:24:53Z",
"name": "ci / Build",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519192",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-005e12d124d262094--566fec4c-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519255,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519255",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:14Z",
"completed_at": "2026-11-06T19:23:21Z",
"name": "ci / e2e shard (2/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519255",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0c236e72756ee5767--da386f7f-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519269,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519269",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:15Z",
"completed_at": "2026-11-06T19:26:36Z",
"name": "ci / e2e shard (6/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519269",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0460327d9a9ad85b9--4e8bc75b-2",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519294,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519294",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:22Z",
"completed_at": "2026-11-06T19:25:00Z",
"name": "ci / e2e shard (3/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519294",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0ff835844ee842210--66852b42-8",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519310,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519310",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:14Z",
"completed_at": "2026-11-06T19:22:48Z",
"name": "ci / e2e shard (4/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519310",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0b5b047d9eb22c701--5e90d2e6-0",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519324,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519324",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:18Z",
"completed_at": "2026-11-06T19:22:50Z",
"name": "ci / Verify Paperclip Runner (vitest native server)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519324",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0f33e5da352722dec--88ad2175-7",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519328,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519328",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:15Z",
"completed_at": "2026-11-06T19:23:26Z",
"name": "ci / e2e shard (5/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519328",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0b4a144abcf5b5ad0--13d99f06-4",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519373,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519373",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:19Z",
"completed_at": "2026-11-06T19:23:19Z",
"name": "ci / Canary Dry Run",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519373",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-026bbfb8e1f2b02f6--fcd31fd2-9",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519399,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519399",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:17Z",
"completed_at": "2026-11-06T19:22:50Z",
"name": "ci / e2e shard (8/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519399",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-06fecb3fefefd2b1e--48a7f824-3",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519420,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519420",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:19Z",
"completed_at": "2026-11-06T19:22:36Z",
"name": "ci / e2e shard (1/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519420",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0b1eec8b49f261f40--0ee7ea96-8",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099519640,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099519640",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:16:19Z",
"completed_at": "2026-11-06T19:22:41Z",
"name": "ci / e2e shard (7/8)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099519640",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0c06a9265636bb3c1--29213d2a-c",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128099521660,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128099521660",
"status": "completed",
"conclusion": "skipped",
"created_at": "2026-11-06T19:15:51Z",
"started_at": "2026-11-06T19:15:51Z",
"completed_at": "2026-11-06T19:15:51Z",
"name": "ci / Sandbox provider tests",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128099521660",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": null,
"runner_group_name": null
},
{
"id": 128101015827,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101015827",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:36Z",
"completed_at": "2026-11-06T19:23:28Z",
"name": "ci / Verify serialized server suites (3/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101015827",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0f29d5a87a13a3137--0ae30aa5-9",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101015873,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101015873",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:37Z",
"completed_at": "2026-11-06T19:25:47Z",
"name": "ci / Verify serialized server suites (1/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101015873",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0a92a42f0bc1f9c02--30c81b0a-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101015886,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101015886",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:57Z",
"completed_at": "2026-11-06T19:24:10Z",
"name": "ci / Verify serialized server suites (4/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101015886",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-050c1bc7159c094e4--1d0ecbb0-5",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101015895,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101015895",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:39Z",
"completed_at": "2026-11-06T19:24:10Z",
"name": "ci / Verify serialized server suites (6/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101015895",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0473b976117b1a23d--148e3608-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101015916,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101015916",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:43Z",
"completed_at": "2026-11-06T19:24:43Z",
"name": "ci / Verify serialized server suites (2/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101015916",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-06aa99d6dfd3eefa2--6d5659d3-7",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101015954,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101015954",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:41Z",
"completed_at": "2026-11-06T19:24:11Z",
"name": "ci / Verify serialized server suites (8/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101015954",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-02e59035275e65dc1--f14d4574-a",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101015978,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101015978",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:43Z",
"completed_at": "2026-11-06T19:24:43Z",
"name": "ci / Verify serialized server suites (5/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101015978",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0fc8f7dbd4ebf8e25--306b0039-6",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016102,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016102",
"status": "completed",
"conclusion": "failure",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:43Z",
"completed_at": "2026-11-06T19:24:31Z",
"name": "ci / Verify serialized server suites (7/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016102",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0f30ba7b9604359f6--7247346a-7",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016153,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016153",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:41Z",
"completed_at": "2026-11-06T19:24:43Z",
"name": "ci / General tests (server (1/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016153",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0a9149d343f722f73--6c14189b-2",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016167,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016167",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:36Z",
"completed_at": "2026-11-06T19:27:21Z",
"name": "ci / General tests (server (5/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016167",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0c26f035abc2181de--db86c3ca-7",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016193,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016193",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:37Z",
"completed_at": "2026-11-06T19:27:48Z",
"name": "ci / General tests (chat (1/3))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016193",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-09edcc5cc037cb21b--a7821027-c",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016200,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016200",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:37Z",
"completed_at": "2026-11-06T19:24:42Z",
"name": "ci / General tests (server (8/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016200",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0d85343085d2a7e66--3919a1b5-e",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016203,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016203",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:41Z",
"completed_at": "2026-11-06T19:24:01Z",
"name": "ci / Verify serialized server suites (9/9)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016203",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0f2a0ad6f8136ed03--3f5b22b1-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016204,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016204",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:38Z",
"completed_at": "2026-11-06T19:25:44Z",
"name": "ci / General tests (workspaces-b)",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016204",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-022720c628aea9909--3489c3bc-e",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016209,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016209",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:37Z",
"completed_at": "2026-11-06T19:27:03Z",
"name": "ci / General tests (server (3/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016209",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-05cd168e2c805dfa1--2db2f96e-9",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016247,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016247",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:37Z",
"completed_at": "2026-11-06T19:24:39Z",
"name": "ci / General tests (workspaces-a (2/2))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016247",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0a76b068c81f7024a--8a17bf0c-7",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016250,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016250",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:39Z",
"completed_at": "2026-11-06T19:26:41Z",
"name": "ci / General tests (server (12/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016250",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-05867958d44053ee3--8a8e3069-9",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016265,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016265",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:39Z",
"completed_at": "2026-11-06T19:26:58Z",
"name": "ci / General tests (server (4/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016265",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0bf649f272db17bac--41ee1099-5",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016277,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016277",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:39Z",
"completed_at": "2026-11-06T19:34:40Z",
"name": "ci / General tests (server (2/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016277",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-057aa4559787e702b--99da1fc5-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016291,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016291",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:39Z",
"completed_at": "2026-11-06T19:27:31Z",
"name": "ci / General tests (server (6/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016291",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0204b721d120e8305--4a8cbdcf-5",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016304,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016304",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:42Z",
"completed_at": "2026-11-06T19:24:43Z",
"name": "ci / General tests (workspaces-a (1/2))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016304",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-083ececdc0bee80c6--049cc912-b",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016340,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016340",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:41Z",
"completed_at": "2026-11-06T19:24:43Z",
"name": "ci / General tests (server (7/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016340",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0a46ba5536afd6715--10ddddf7-c",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016380,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016380",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:39Z",
"completed_at": "2026-11-06T19:27:21Z",
"name": "ci / General tests (server (11/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016380",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0f1df9c61064c6c00--bfe5ea08-c",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016402,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016402",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:43Z",
"completed_at": "2026-11-06T19:24:42Z",
"name": "ci / General tests (server (9/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016402",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0cd6219458ec3772c--7a914eac-5",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016432,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016432",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:39Z",
"completed_at": "2026-11-06T19:24:43Z",
"name": "ci / General tests (chat (3/3))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016432",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0fdc6b32e8998c1ba--0724a4a5-2",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016453,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016453",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:44Z",
"completed_at": "2026-11-06T19:24:43Z",
"name": "ci / General tests (server (10/12))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016453",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-05195ce82810fc9ab--39894cef-6",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128101016461,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128101016461",
"status": "completed",
"conclusion": "cancelled",
"created_at": "2026-11-06T19:18:48Z",
"started_at": "2026-11-06T19:19:43Z",
"completed_at": "2026-11-06T19:24:42Z",
"name": "ci / General tests (chat (2/3))",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128101016461",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-0722bda62fe122e78--24305ada-1",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128104989524,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128104989524",
"status": "completed",
"conclusion": "success",
"created_at": "2026-11-06T19:26:36Z",
"started_at": "2026-11-06T19:27:42Z",
"completed_at": "2026-11-06T19:27:47Z",
"name": "ci / e2e",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128104989524",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-03d74c0aaf2a6f9a8--5077b150-0",
"runner_group_name": "paperclip-public-pr"
},
{
"id": 128109082787,
"run_id": 42277969418,
"workflow_name": "PR",
"run_attempt": 1,
"html_url": "https://github.com/paperclipai/paperclip/actions/runs/42277969418/job/128109082787",
"status": "completed",
"conclusion": "failure",
"created_at": "2026-11-06T19:34:41Z",
"started_at": "2026-11-06T19:35:37Z",
"completed_at": "2026-11-06T19:35:43Z",
"name": "ci / verify",
"check_run_url": "https://api.github.com/repos/paperclipai/paperclip/check-runs/128109082787",
"labels": [
"runs-on/fleet=paperclip-public-pr-x64/env=public-ci"
],
"runner_name": "runs-on--i-05ba3f428ff571fab--c14c92a3-1",
"runner_group_name": "paperclip-public-pr"
}
],
"annotations": {
"128101016102": [
{
"path": ".github",
"start_line": 2,
"end_line": 2,
"annotation_level": "failure",
"title": "",
"message": "The operation was canceled.",
"raw_details": ""
}
],
"128109082787": [
{
"path": ".github",
"start_line": 124,
"end_line": 124,
"annotation_level": "failure",
"title": "",
"message": "Process completed with exit code 1.",
"raw_details": ""
}
]
},
"logs": {
"128101016102": "2026-11-06T19:24:29.1450769Z ##[error]The runner has received a shutdown signal. This can happen when the runner service is stopped, or a manually started runner is canceled.\n2026-11-06T19:24:29.1898916Z ##[error]The operation was canceled.\n2026-11-06T19:24:29.1977091Z Cleaning up orphan processes\n"
},
"total_count": 48
}
@@ -0,0 +1,317 @@
import assert from "node:assert/strict";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import {
classifyJob, createGithubClient, decide, logShowsRunnerLoss, main, renderSummary,
} from "../runner-loss-rerun.mjs";
// Trimmed attempt-1 snapshots of real PR runs: jobs, check-run annotations for
// each failed job, and the error lines of job logs whose only annotation is
// "The operation was canceled.".
const fixture = (runId) => JSON.parse(readFileSync(
new URL(`./fixtures/runner-loss-rerun/run-${runId}.json`, import.meta.url), "utf8",
));
const MIXED = 42271918832; // six lost runners, one shutdown in the log, one real test timeout
const REAL = 42264785415; // a real assertion failure
const ALL_LOST = 42272083862; // one lost runner
const LOG_ONLY = 42277969418; // shutdown signal visible only in the job log
const workflow = readFileSync(new URL("../../workflows/ci-runner-loss-rerun.yml", import.meta.url), "utf8");
const json = (body, headers = {}) => new Response(JSON.stringify(body), {
status: 200, headers: { "content-type": "application/json", ...headers },
});
/**
* Serves a fixture as the GitHub REST API. By default the run is the latest
* CI for an open PR's current head.
*/
function fakeGithub(data, {
jobsPageSize = 100, pulls, prRuns, mutateRun = (run) => run,
} = {}) {
const calls = [];
const run = mutateRun(structuredClone(data.run));
const openPr = {
number: 1, state: "open",
base: { repo: { id: data.run.repository.id } },
head: { sha: data.run.head_sha, repo: { id: data.run.head_repository.id } },
};
const fetcher = async (href, init) => {
const url = new URL(href);
calls.push({ method: init.method, path: url.pathname, query: url.search, auth: init.headers.Authorization });
const base = `/repos/paperclipai/paperclip`;
const route = url.pathname.slice(base.length);
if (init.method === "POST" && route === `/actions/runs/${run.id}/rerun-failed-jobs`) {
return new Response(null, { status: 201 });
}
if (route === `/actions/runs/${run.id}`) return json(run);
if (route === `/actions/runs/${run.id}/attempts/1/jobs`) {
const page = Number(url.searchParams.get("page") ?? 1);
const jobs = data.jobs.slice((page - 1) * jobsPageSize, page * jobsPageSize);
const last = page * jobsPageSize >= data.jobs.length;
const next = new URL(url);
next.searchParams.set("page", String(page + 1));
return json({ total_count: data.total_count, jobs }, last ? {} : { link: `<${next.href}>; rel="next"` });
}
let match = route.match(/^\/check-runs\/(\d+)\/annotations$/);
if (match) return json(data.annotations[match[1]] ?? []);
match = route.match(/^\/actions\/jobs\/(\d+)\/logs$/);
if (match) return new Response(data.logs[match[1]] ?? "", { status: 200 });
if (route === `/commits/${run.head_sha}/pulls`) return json(pulls ?? [openPr]);
if (route === "/actions/workflows/pr.yml/runs") {
const runs = prRuns ?? [{ id: run.id, conclusion: "failure" }];
return json({ total_count: runs.length, workflow_runs: runs });
}
return new Response("not found", { status: 404 });
};
return { calls, fetcher, run, client: createGithubClient({ token: "test-token", fetcher }) };
}
const posted = (calls) => calls.filter((call) => call.method === "POST");
async function runMain(data, options = {}, eventRun = {}) {
const github = fakeGithub(data, options);
const directory = mkdtempSync(path.join(tmpdir(), "runner-loss-rerun-"));
try {
const eventPath = path.join(directory, "event.json");
const summaryPath = path.join(directory, "summary.md");
writeFileSync(eventPath, JSON.stringify({ workflow_run: { id: data.run.id, run_attempt: 1, ...eventRun } }));
writeFileSync(summaryPath, "");
const decision = await main({
env: { GITHUB_TOKEN: "test-token", GITHUB_EVENT_PATH: eventPath, GITHUB_STEP_SUMMARY: summaryPath },
argv: [], fetcher: github.fetcher, log: () => {},
});
return { decision, calls: github.calls, summary: readFileSync(summaryPath, "utf8") };
} finally {
rmSync(directory, { recursive: true, force: true });
}
}
test("all failed jobs lost their runner: re-runs failed jobs once", async () => {
const { decision, calls, summary } = await runMain(fixture(ALL_LOST));
assert.equal(decision.rerun, true);
assert.deepEqual(decision.jobs.map((job) => [job.name, job.lost]), [["ci / General tests (server (3/12))", true]]);
assert.deepEqual(posted(calls).map((call) => call.path),
[`/repos/paperclipai/paperclip/actions/runs/${ALL_LOST}/rerun-failed-jobs`]);
assert.ok(calls.every((call) => call.auth === "Bearer test-token"));
// `ci / verify` failed too, but only because its dependency failed.
assert.equal(calls.some((call) => call.path.endsWith("/check-runs/128088995071/annotations")), false);
assert.match(summary, /\*\*Re-ran failed jobs\*\*/);
assert.match(summary, /\| ci \/ General tests \(server \(3\/12\)\) \| yes \| The self-hosted runner lost communication/);
});
test("a shutdown signal found only in the job log counts as runner loss", async () => {
const data = fixture(LOG_ONLY);
assert.deepEqual(data.annotations["128101016102"].map((annotation) => annotation.message), ["The operation was canceled."]);
const { decision, calls } = await runMain(data);
assert.equal(decision.rerun, true);
assert.match(decision.jobs[0].evidence, /job log/);
assert.ok(calls.some((call) => call.path.endsWith("/actions/jobs/128101016102/logs")));
assert.equal(posted(calls).length, 1);
});
test("mixed runner loss and a real test failure: no re-run", async () => {
const { decision, calls, summary } = await runMain(fixture(MIXED));
assert.equal(decision.rerun, false);
assert.equal(decision.jobs.length, 8);
assert.deepEqual(decision.jobs.filter((job) => !job.lost).map((job) => job.name), ["ci / General tests (workspaces-a (1/2))"]);
assert.equal(decision.jobs.find((job) => job.name === "ci / Verify Paperclip Runner (vitest package)").lost, true);
assert.equal(posted(calls).length, 0);
assert.match(decision.reason, /1 of 8 failed jobs did not lose their runner/);
assert.match(summary, /\*\*No automatic re-run\*\*/);
});
test("real test failures: no re-run", async () => {
const { decision, calls } = await runMain(fixture(REAL));
assert.equal(decision.rerun, false);
assert.deepEqual(decision.jobs.map((job) => [job.name, job.lost]), [["ci / General tests (server (4/12))", false]]);
assert.equal(posted(calls).length, 0);
});
test("only the verify and e2e aggregates failed: no re-run", async () => {
const data = fixture(ALL_LOST);
data.jobs = data.jobs.map((job) => job.name.startsWith("ci / General tests") ? { ...job, conclusion: "success" } : job);
const { decision, calls } = await runMain(data);
assert.equal(decision.rerun, false);
assert.match(decision.reason, /no failed jobs other than the verify\/e2e aggregates/);
assert.equal(calls.some((call) => call.path.includes("/check-runs/")), false);
assert.equal(posted(calls).length, 0);
});
test("attempt 2 or later is never re-run automatically", async () => {
// The event was for attempt 1, but someone re-ran the run before this job started.
const { decision, calls } = await runMain(fixture(ALL_LOST), { mutateRun: (run) => ({ ...run, run_attempt: 2 }) });
assert.equal(decision.rerun, false);
assert.match(decision.reason, /attempt 2/);
assert.deepEqual(calls.map((call) => call.path), [`/repos/paperclipai/paperclip/actions/runs/${ALL_LOST}`]);
});
test("a failed routing gate is left for a human re-run", async () => {
const data = fixture(ALL_LOST);
const gate = data.jobs.find((job) => job.name === "ci / gate / Select trusted runner");
gate.conclusion = "failure";
data.annotations[gate.id] = [{ annotation_level: "failure", message: "The self-hosted runner lost communication with the server." }];
const { decision, calls } = await runMain(data);
assert.equal(decision.rerun, false);
assert.match(decision.jobs.find((job) => job.id === gate.id).evidence, /routing gate/);
assert.equal(posted(calls).length, 0);
});
test("runs that are no longer the PR's latest CI are not re-run", async () => {
const data = fixture(ALL_LOST);
const moved = await decide(fakeGithub(data, {
pulls: [{ state: "open", base: { repo: { id: 1170821064 } }, head: { sha: "0".repeat(40), repo: { id: 1170821064 } } }],
}).client, data.run);
assert.equal(moved.rerun, false);
assert.match(moved.reason, /no open pull request/);
const merged = await decide(fakeGithub(data, {
pulls: [{ state: "closed", base: { repo: { id: 1170821064 } }, head: { sha: data.run.head_sha, repo: { id: 1170821064 } } }],
}).client, data.run);
assert.equal(merged.rerun, false);
// A later full-ci label run on the same head shares the PR concurrency group;
// re-running the older run would cancel it.
const newer = await decide(fakeGithub(data, {
prRuns: [{ id: data.run.id + 1, conclusion: null }, { id: data.run.id, conclusion: "failure" }],
}).client, data.run);
assert.equal(newer.rerun, false);
assert.match(newer.reason, /newer PR run/);
// A later label event that did not request CI skips its only job; it is not CI.
const skipped = await decide(fakeGithub(data, {
prRuns: [{ id: data.run.id + 1, conclusion: "skipped" }, { id: data.run.id, conclusion: "failure" }],
}).client, data.run);
assert.equal(skipped.rerun, true);
});
test("ineligible runs are rejected before any job is read", async () => {
const data = fixture(ALL_LOST);
for (const [change, reason] of [
[{ event: "push" }, /event is push/],
[{ path: ".github/workflows/other.yml" }, /workflow is/],
[{ conclusion: "success" }, /completed\/success/],
[{ repository: { id: 1, full_name: "someone/paperclip" } }, /canonical repository/],
]) {
const github = fakeGithub(data);
const decision = await decide(github.client, { ...data.run, ...change });
assert.equal(decision.rerun, false);
assert.match(decision.reason, reason);
assert.equal(github.calls.length, 0);
}
});
test("job listing follows Link pagination across pages", async () => {
const data = fixture(MIXED);
const github = fakeGithub(data, { jobsPageSize: 20 });
const decision = await decide(github.client, data.run);
const pages = github.calls.filter((call) => call.path.endsWith("/attempts/1/jobs"));
assert.deepEqual(pages.map((call) => new URLSearchParams(call.query).get("page")), [null, "2", "3"]);
// Failed jobs on pages 2 and 3 are all classified.
assert.equal(decision.jobs.length, 8);
assert.equal(decision.rerun, false);
});
test("an incomplete job listing fails instead of deciding on partial data", async () => {
const data = fixture(ALL_LOST);
data.total_count += 1;
const github = fakeGithub(data);
await assert.rejects(decide(github.client, data.run), /Incomplete listing/);
assert.equal(posted(github.calls).length, 0);
});
test("the client refuses to follow pagination to another host", async () => {
const client = createGithubClient({
token: "test-token",
fetcher: async () => json([], { link: '<https://example.com/steal?page=2>; rel="next"' }),
});
await assert.rejects(client.paginate("/repos/paperclipai/paperclip/commits/x/pulls"), /Refusing to call https:\/\/example.com/);
});
test("classification is conservative about cancelled jobs", async () => {
const cancelled = [{ annotation_level: "failure", message: "The operation was canceled." }];
const noSignal = await classifyJob({ id: 1 }, { annotations: cancelled, readLog: async () => "##[error]The operation was canceled.\n" });
assert.equal(noSignal.lost, false);
// A job timeout also cancels the step, but adds its own failure annotation.
const timedOut = await classifyJob({ id: 1 }, {
annotations: [{ annotation_level: "failure", message: "The job has exceeded the maximum execution time of 30m0s" }, ...cancelled],
readLog: async () => { throw new Error("log must not be read"); },
});
assert.equal(timedOut.lost, false);
// Notices and warnings are not failure evidence.
const warning = await classifyJob({ id: 1 }, {
annotations: [{ annotation_level: "warning", message: "The self-hosted runner lost communication with the server." }],
readLog: async () => "",
});
assert.equal(warning.lost, false);
// Test output that merely prints the phrase is not a runner error line.
assert.equal(logShowsRunnerLoss("2026-11-06T00:00:00Z expected 'received a shutdown signal'\n"), false);
assert.equal(logShowsRunnerLoss(fixture(MIXED).logs["128078674151"]), true);
});
test("a real failure before the runner was lost is not counted as runner loss", async () => {
const lostMessage = "The self-hosted runner lost communication with the server. Verify the machine is running.";
const noLog = async () => { throw new Error("log must not be read"); };
// A test fails, then the runner is lost during an always() upload step.
const testThenLost = await classifyJob({ id: 1 }, {
annotations: [
{ annotation_level: "failure", message: "Error: Test timed out in 15000ms." },
{ annotation_level: "failure", message: "Process completed with exit code 1." },
{ annotation_level: "failure", message: lostMessage },
],
readLog: noLog,
});
assert.equal(testThenLost.lost, false);
assert.match(testThenLost.evidence, /Test timed out/);
// The order of the annotations does not matter.
const lostThenExit = await classifyJob({ id: 1 }, {
annotations: [
{ annotation_level: "failure", message: lostMessage },
{ annotation_level: "failure", message: "Process completed with exit code 1." },
],
readLog: noLog,
});
assert.equal(lostThenExit.lost, false);
// Runner loss with a cancellation only is still runner loss.
const lostAndCancelled = await classifyJob({ id: 1 }, {
annotations: [
{ annotation_level: "failure", message: lostMessage },
{ annotation_level: "failure", message: "The operation was canceled." },
],
readLog: noLog,
});
assert.equal(lostAndCancelled.lost, true);
});
test("summary cells cannot break the table", () => {
const summary = renderSummary({
runId: 1, attempt: 1, rerun: false, reason: "test",
jobs: [{ name: "ci / a|b", lost: false, evidence: "line one\nline | two" }],
});
assert.match(summary, /\| ci \/ a\\\|b \| no \| line one line \\\| two \|/);
});
test("workflow acts only on failed first attempts of the canonical PR workflow", () => {
const condition = workflow.match(/^ {4}if: >-\n((?: {6}.+\n)+)/m)?.[1].replace(/\s+/g, " ").trim();
assert.equal(condition, [
"github.repository == 'paperclipai/paperclip' &&",
"github.repository_id == '1170821064' &&",
"github.event.workflow_run.event == 'pull_request' &&",
"github.event.workflow_run.path == '.github/workflows/pr.yml' &&",
"github.event.workflow_run.conclusion == 'failure' &&",
"github.event.workflow_run.run_attempt == 1",
].join(" "));
assert.match(workflow, /^on:\n {2}workflow_run:\n {4}workflows: \[PR\]\n {4}types: \[completed\]$/m);
assert.match(workflow, /^ {2}group: ci-runner-loss-rerun-\$\{\{ github\.event\.workflow_run\.id \}\}$/m);
const permissions = workflow.match(/^permissions:\n((?: {2}.+\n)+)/m)[1]
.split("\n").filter((line) => /^ {2}[a-z-]+:/.test(line)).map((line) => line.trim());
assert.deepEqual(permissions, ["actions: write", "checks: read", "contents: read", "pull-requests: read"]);
});
test("workflow runs only trusted default-branch code", () => {
assert.match(workflow, /ref: \$\{\{ github\.sha \}\}\n {10}persist-credentials: false\n {10}sparse-checkout: \.github\/scripts\n/);
assert.doesNotMatch(workflow, /head_sha|head_branch|download-artifact|secrets\.|pull_request_target/);
assert.equal([...workflow.matchAll(/uses: /g)].length, 1);
assert.match(workflow, /runs-on: ubuntu-latest/);
});
@@ -0,0 +1,52 @@
name: CI runner-loss rerun
# Self-hosted AWS runners are sometimes lost mid-job. When every failed job of
# a first PR CI attempt lost its runner, re-run the failed jobs once. This is
# what a maintainer's "Re-run failed jobs" click does; github-actions[bot]
# becomes attempt 2's triggering actor. See "Trusted PR Workflow" in
# doc/DEVELOPING.md.
on:
workflow_run:
workflows: [PR]
types: [completed]
permissions:
# rerun-failed-jobs, and reading jobs and job logs.
actions: write
# Check-run annotations carry the runner-loss message.
checks: read
contents: read
# Confirms the run still belongs to an open PR's current head.
pull-requests: read
concurrency:
group: ci-runner-loss-rerun-${{ github.event.workflow_run.id }}
cancel-in-progress: false
jobs:
rerun:
# Attempt 1 only: this allows one automatic re-run and cannot loop.
if: >-
github.repository == 'paperclipai/paperclip' &&
github.repository_id == '1170821064' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.path == '.github/workflows/pr.yml' &&
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.run_attempt == 1
# GitHub-hosted on purpose: this job must not depend on the fleet it repairs.
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# workflow_run is privileged. Never check out its head SHA or run code
# from downloaded artifacts. This checkout is the trusted default branch.
- name: Checkout trusted re-run script
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.sha }}
persist-credentials: false
sparse-checkout: .github/scripts
- name: Re-run failed jobs if every failure was runner loss
env:
GITHUB_TOKEN: ${{ github.token }}
run: node .github/scripts/runner-loss-rerun.mjs
+13
View File
@@ -186,6 +186,19 @@ Authorized test jobs and the final `verify` and `e2e` aggregates use the selecte
AWS fleet. A stale admission roster can allocate only the short metadata gate;
the fresh CODEOWNERS check still denies PR execution for a removed owner.
AWS runners are sometimes lost mid-job. `ci-runner-loss-rerun.yml` re-runs the
failed jobs of a PR's first CI attempt once when every failed job lost its
runner. A lost runner is recognized by the check-run annotation "lost
communication with the server" or "received a shutdown signal". When the only
annotation is "The operation was canceled.", the job log is checked for the
same messages. The `verify` and `e2e` aggregates are not counted. Any other
failure, a failed routing gate, or a PR whose head has moved prevents the
automatic re-run. The workflow job summary lists each failed job and its
evidence. Like a maintainer's "Re-run failed jobs", the re-run reuses the
gate's successful outputs, including its AWS admission. Attempt 2's
triggering actor is `github-actions[bot]`, so `profile-ci` does not export
that attempt. Later attempts are never re-run automatically.
The routing workflow also selects the CI scope. Middle PRs in a stack and PRs
whose diff changes only release notes (`releases/v*.md` or
`releases/beta/v*.md`) skip the build, test, and e2e lanes. The `policy` job