Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
f425f64
chore: project borg from Mono a5293e119fc2
Oct 10, 2026
21986f7
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 10, 2026
96cf58f
chore: project borg from Mono 3bba595c27f8
Oct 10, 2026
165446f
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 10, 2026
b273b08
chore: project borg from Mono 95eee04b8127
Oct 10, 2026
56f235b
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 10, 2026
cf8a4c5
chore: project borg from Mono d85864165c24
Oct 10, 2026
ff51120
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 10, 2026
df40113
chore: project borg from Mono ef5e513984ed
Oct 10, 2026
122ea41
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 11, 2026
7e22b64
chore: project borg from Mono 7c0e3ee59b04
Oct 11, 2026
20c167b
chore: project borg from Mono b271bbbd34fa
Oct 11, 2026
e3c1d95
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 11, 2026
00e3a20
chore: project borg from Mono 6efbce212025
Oct 11, 2026
1d32694
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 11, 2026
3a4a5d6
chore: project borg from Mono 5af6e0de217f
Oct 11, 2026
be90ea0
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 11, 2026
e20de24
chore: project borg from Mono 7bba3d56ad22
Oct 11, 2026
c53bd9d
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 12, 2026
03f7919
chore: project borg from Mono 459f737d5b9e
Oct 12, 2026
9f49807
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 12, 2026
6fb07e4
chore: project borg from Mono 2e833192cc63
Oct 12, 2026
7ee93e1
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 12, 2026
810c48d
chore: project borg from Mono 6a106187736f
Oct 12, 2026
336d445
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 12, 2026
985a0a0
chore: project borg from Mono e52156fbb589
Oct 12, 2026
43700f4
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 12, 2026
eb79fe9
chore: project borg from Mono 50bb0b080bad
Oct 12, 2026
3242663
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 12, 2026
a74b3af
chore: project borg from Mono fcb4796cdc8e
Oct 12, 2026
0d6fbdf
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 12, 2026
256f424
chore: project borg from Mono 004e0bd914d0
Oct 12, 2026
99c134b
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 16, 2026
1af517a
chore: project borg from Mono 0fd8f678d750
Oct 16, 2026
9ad1940
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 16, 2026
273c4f6
chore: project borg from Mono 4578fdbb3924
Oct 16, 2026
8eedadd
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 16, 2026
a3a4c6d
chore: project borg from Mono 91f10217da2a
Oct 16, 2026
d9a58b7
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 16, 2026
3594dd6
chore: project borg from Mono 296fa1359ec1
Oct 16, 2026
29beea4
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 17, 2026
1824b8d
chore: project borg from Mono 54af7346321a
Oct 17, 2026
4d1d278
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 17, 2026
46f6739
chore: project borg from Mono 6b41db420e1a
Oct 17, 2026
d1bb9d0
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 17, 2026
a1bad3e
chore: project borg from Mono ee64d86fe58c
Oct 17, 2026
03ad971
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 17, 2026
e0ec7c1
chore: project borg from Mono 6077c90a491c
Oct 17, 2026
7d52ff1
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 17, 2026
225f431
chore: project borg from Mono 9604c2352669
Oct 17, 2026
ea2613c
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 18, 2026
1d12608
chore: project borg from Mono 2ba0ca8a9a83
Oct 18, 2026
8bb9571
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 18, 2026
aed2d19
chore: project borg from Mono 9e043be5f33b
Oct 18, 2026
8c7caa2
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 18, 2026
810c6b2
chore: project borg from Mono 0e6e12bf977e
Oct 18, 2026
a35c394
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 18, 2026
54d185b
chore: project borg from Mono 2fbc08e02f94
Oct 18, 2026
a219f79
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 18, 2026
c8aa06d
chore: project borg from Mono faa2360ea605
Oct 18, 2026
68bb067
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 18, 2026
8bd9040
chore: project borg from Mono 7c1c4ccabe91
Oct 18, 2026
1bbee45
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Oct 18, 2026
b2fdd46
chore: project borg from Mono 6aa9d4a0331c
Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .repository-projection.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"projection": "borg",
"projectionSchemaVersion": 1,
"sourceRepository": "dx-corp/mono",
"sourceSha": "7c1c4ccabe912b319ffbeda00acc89d238a25604",
"sourceSha": "6aa9d4a0331c653a7ce5b5335624c334de979977",
"destinationRepository": "dx-corp/borg",
"priorProjectedBase": "5ea97abffad434077e597a41f8a183118e41124b",
"priorProjectedBase": "ddb6362e141a28af70e6641c3ae09d60f70ac2b1",
"definitionDigest": "78ba8d1b5794c261ff0afba6d99abc10b9297ea0dd94be4397fb1cbf63cf0c6e",
"toolDigest": "61f64178999b764105e6e5c5bc7c5971c164909e",
"contentDigest": "e0f71fcb7302dfac768903a8b223e61f5611416db2b8919bd101c3956975cbf6",
"contentDigest": "9e0e5f7acad9e39a50a5662518b27e7e7a292f38a693d24a3ab410efde4fa0ae",
"publicationEligible": true
}
4 changes: 4 additions & 0 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,9 @@ a newer push leaves behind:
`push` run on one of them is cancelled when a newer queued or running run of
the same workflow on the same branch is on another commit. Running push runs
are never cancelled, so the branch always finishes its newest started run.
GitHub reports a run as `queued` while any of its jobs waits for a runner, so
the janitor also reads the run's jobs and keeps the run when one is running
(`started_push`).

When the janitor is enabled, `GET /status` has a `janitor` object:

Expand All @@ -735,6 +738,7 @@ When the janitor is enabled, `GET /status` has a `janitor` object:
| `dedup_cancelled` | Queued duplicates cancelled. |
| `would_cancel` | Runs `dry_run` would have cancelled, of any kind. |
| `deferred` | Runs left for a later pass by `max_cancels_per_pass`. |
| `started_push` | Superseded push runs kept because one of their jobs was running. |
| `raced` | Runs whose status, head or pull request changed between the listing and the re-read just before the cancel; left alone. |
| `uncancellable` | Runs whose cancel GitHub answered with 409. Runs stuck in `queued` for days (for example `workflow_run` runs GitHub never started) answer 409 on every pass and are counted each time. |
| `errors` | Failed GitHub calls: a run listing for a repository, a pulls API read, a run re-read or a cancel. Each is logged as a `janitor:` warning in the queen journal. |
Expand Down
5 changes: 5 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2098,6 +2098,11 @@ pub fn render_metrics(status: &Status) -> String {
"Runs left for a later pass by the per-pass cap",
t.deferred,
),
(
"started_push",
"Superseded push runs kept because a job was running",
t.started_push,
),
(
"raced",
"Runs that changed state before the janitor cancelled them",
Expand Down
46 changes: 45 additions & 1 deletion src/janitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ pub struct PassReport {
pub would_cancel: u32,
/// Runs left for a later pass by the per-pass cap.
pub deferred: u32,
/// Superseded push runs left alone because one of their jobs is running.
/// GitHub shows a run as `queued` while any job waits for a runner.
pub started_push: u32,
/// Runs that changed state between the listing and the cancel and were
/// left alone.
pub raced: u32,
Expand All @@ -97,6 +100,7 @@ impl PassReport {
self.dedup_cancelled += o.dedup_cancelled;
self.would_cancel += o.would_cancel;
self.deferred += o.deferred;
self.started_push += o.started_push;
self.raced += o.raced;
self.uncancellable += o.uncancellable;
self.errors += o.errors;
Expand Down Expand Up @@ -280,7 +284,7 @@ impl Janitor {
}
}
tracing::debug!(
"janitor pass: considered {} superseded {} closed_pr {} superseded_push {} cancelled {} cancelled_running {} duplicates {} dedup_cancelled {} would_cancel {} deferred {} raced {} uncancellable {} errors {}",
"janitor pass: considered {} superseded {} closed_pr {} superseded_push {} cancelled {} cancelled_running {} duplicates {} dedup_cancelled {} would_cancel {} deferred {} started_push {} raced {} uncancellable {} errors {}",
report.considered,
report.superseded,
report.closed_pr,
Expand All @@ -291,6 +295,7 @@ impl Janitor {
report.dedup_cancelled,
report.would_cancel,
report.deferred,
report.started_push,
report.raced,
report.uncancellable,
report.errors
Expand Down Expand Up @@ -376,6 +381,18 @@ impl Janitor {
report.deferred += 1;
continue;
}
match self.has_running_job(repo, run.id).await {
Ok(false) => {}
Ok(true) => {
report.started_push += 1;
continue;
}
Err(e) => {
tracing::warn!("janitor: {repo} run {}: {e}", run.id);
report.errors += 1;
continue;
}
}
let why = format!(
"on {}: superseded by run {newer}",
run.head_branch.as_deref().unwrap_or("?")
Expand Down Expand Up @@ -447,6 +464,33 @@ impl Janitor {
}

/// Cancel `run` unless a fresh read shows its status or head changed.
/// Whether any job of `run` is running. A run reads `queued` while one job
/// waits for a runner, even when others have started.
async fn has_running_job(&self, repo: &str, run: u64) -> Result<bool, GithubError> {
#[derive(Deserialize)]
struct Jobs {
jobs: Vec<Job>,
}
#[derive(Deserialize)]
struct Job {
status: String,
}
for page in 1..=MAX_PAGES {
let jobs: Jobs = self
.get(&format!(
"/repos/{repo}/actions/runs/{run}/jobs?filter=latest&per_page=100&page={page}"
))
.await?;
if jobs.jobs.iter().any(|j| j.status == "in_progress") {
return Ok(true);
}
if jobs.jobs.len() < 100 {
break;
}
}
Ok(false)
}

async fn cancel_if_unchanged(
&self,
repo: &str,
Expand Down
27 changes: 27 additions & 0 deletions tests/janitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,33 @@ async fn older_queued_main_pushes_are_cancelled_and_running_ones_kept() {
assert_eq!(cancelled, [1, 2]);
}

#[tokio::test]
async fn a_superseded_main_push_with_a_running_job_is_kept() {
// GitHub shows a run as queued while any job waits for a runner, so a
// publish run building one image while its siblings wait reads `queued`.
let h = Harness::start(with(json!({"superseded_push_branches": ["main"]}))).await;
let push = |id: u64, sha: &str| {
json!({"id": id, "workflow_id": 1, "status": "queued", "event": "push",
"head_sha": sha, "head_branch": "main", "pull_requests": []})
};
{
let mut st = h.state();
st.pr_runs.push(push(1, "a"));
st.pr_runs.push(push(2, "b"));
st.jobs
.push(json!({"id": 10, "run_id": 1, "status": "in_progress"}));
st.jobs
.push(json!({"id": 11, "run_id": 1, "status": "queued"}));
}
let r = h.janitor_pass().await;
assert_eq!(
(r.superseded_push, r.started_push, r.cancelled),
(1, 1, 0),
"{r:?}"
);
assert!(h.state().cancelled.is_empty());
}

#[tokio::test]
async fn the_per_pass_cap_defers_the_rest_to_later_passes() {
let h = Harness::start(enabled(false, 2)).await;
Expand Down
9 changes: 7 additions & 2 deletions tests/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,19 @@ async fn runs(
async fn jobs(
State(s): State<Shared>,
h: HeaderMap,
Path(_p): Path<(String, String, u64)>,
Path((_o, _r, run)): Path<(String, String, u64)>,
) -> Result<Json<Value>, StatusCode> {
check_token(&h)?;
// The janitor reads a run's jobs with its own token; queen jobs carry no run_id.
let janitor = check_janitor(&h).is_ok();
if !janitor {
check_token(&h)?;
}
let state = lock(&s);
let jobs: Vec<_> = state
.jobs
.iter()
.filter(|job| !state.hide_completed_jobs || job["status"] != "completed")
.filter(|job| !janitor || job["run_id"] == run)
.cloned()
.collect();
Ok(Json(json!({"jobs": jobs})))
Expand Down