Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
22a6e8a
feat(web): add robots option to buildPageMetadata
Sep 22, 2026
21769f7
docs(web): describe Auto as declared default plus opt-in router and c…
Sep 22, 2026
e20f95d
fix(web): route /download, /desktop, /pricing to install in one hop
Sep 22, 2026
5b005e8
docs(web): README hygiene, docs index, one canonical constitution URL
Sep 22, 2026
9731569
docs(web): correct subagent clamp, Work mode, Auto-Review, credential…
Sep 22, 2026
1c4edf6
fix(web): review follow-up for S9a+S14
Sep 22, 2026
28754c4
refactor(web): split globals.css into per-surface partials
Sep 22, 2026
3cd160e
feat(web): GPUI role tokens from set_theme via the palette pipeline
Sep 22, 2026
ce14c57
ci(gh): label-only intake bots, fail negated closing keywords
Sep 22, 2026
c55d016
ci(release): propose the release record after every publish
Sep 22, 2026
e6029d0
fix: review follow-up for CI-E
Sep 22, 2026
ea59edd
feat(web): site-wide OS-following theme contract
Sep 22, 2026
091f052
fix(web): match MCP hook tool_name globs on the owning server (S15a, …
Sep 22, 2026
e648bb8
fix: review follow-up for CI-B
Sep 22, 2026
fc7cfb0
ci: make budget ratchets block same-repo PRs, with same-PR --update r…
Sep 22, 2026
e9c222f
feat(web): Shannon Sans everywhere, system mono, no all-caps (S3)
Sep 22, 2026
1f09d09
feat(web): GPUI radius grammar, state roles, spring motion, one icon …
Sep 22, 2026
adb3a1c
fix(web): review follow-up for S1a: split web/app/globals.css into pe…
Sep 22, 2026
0ec64f3
fix(web): review follow-up for S4 — Radii, state roles, motion, primi…
Sep 22, 2026
0850791
fix(web): review follow-up for S2 — Site-wide theme contract (design …
Sep 22, 2026
3aaf473
feat(runtime-api): computer display WS, human control lease, client t…
Sep 22, 2026
6b75b4f
fix(tools): never let a git spawn prompt; bound git_fetch with a timeout
Sep 22, 2026
2fce1aa
fix(web): review follow-up for S15a
Sep 22, 2026
389619a
fix(tui): drive the composer send cue from the draft, not the paste-b…
Sep 22, 2026
d85cc37
fix(engine): retry a clean empty stop before failing the turn
Sep 22, 2026
52d6942
fix: review follow-up for P3
Sep 22, 2026
1f99bce
fix(context): headline /context with the pressure estimate the meter …
Sep 22, 2026
48df015
fix: make shipped status text match shipped behavior (0.10.1 item 10)
Sep 22, 2026
6dca8a8
fix(fleet): name the bad field in spec errors; deflake sub-agent timi…
Sep 22, 2026
5b1dcbd
fix(approvals): model cannot grant itself Computer Use consent (K1, K…
Sep 22, 2026
3943ae2
docs(fleet): TOML specs may be a single task, not only a document
Sep 22, 2026
7e4b216
fix(approvals): name irreversible-action confirms and truncated scrip…
Sep 22, 2026
bfe00b0
fix(plugins): stop appending <recommended_plugins> to the user turn (…
Sep 22, 2026
eb59a59
fix(plugins): never advertise built-ins, generic words, or other-OS p…
Sep 22, 2026
d5cec11
fix(plugins): one surface, one switch, one budget for plugin offers (…
Sep 22, 2026
d1e3456
fix(plugins): gate request_plugin_install in every mode, not only Pla…
Sep 22, 2026
5ad772f
fix(web): regenerate gt-catalog from en dictionaries
Sep 22, 2026
1fb9ac3
test(tui): stop treating the steady [↵] cue as an Enter-will-submit s…
Sep 22, 2026
be7f477
fix(runtime-api): computer display clippy, socket-path validation, te…
Sep 22, 2026
4c7a1a9
fix(context): one pressure number across meter, gate, preflight, /con…
Sep 22, 2026
479db05
ci: keep fork PRs under the macOS limit and the Actions cache under i…
Sep 22, 2026
feb55b3
ci(release): one parity gate for RC and release; refuse a tag without…
Sep 22, 2026
2178e69
docs(release): run the RC before a recut; say how to clear a missing …
Sep 22, 2026
791b775
fix(alloc): stop macOS reporting the mimalloc heap as IOAccelerator G…
Sep 22, 2026
55b6dea
fix(engine): make a stalled turn report itself (0.10.1 item 1)
Sep 22, 2026
28a0fea
fix(onboarding): honest first run: chat-capable Ollama pick, no-model…
Sep 22, 2026
e9c1ab7
fix(subagent): never drop a child's terminal AgentComplete (0.10.1 it…
Sep 23, 2026
74d8572
fix(onboarding): keep the launch card restorable when the telemetry n…
Sep 23, 2026
84442f3
fix(fleet): cancelling a write-scoped child keeps and names its work …
Sep 23, 2026
9e7e5e7
fix(fleet): say why a sub-agent is queued and how much budget it has …
Sep 23, 2026
d91125a
Sync vendored Computer Use @ 17c30c5: agent never drives the user's c…
Sep 23, 2026
73f4fcb
fix(fleet): queued row names its wall-budget end time, not a frozen c…
Sep 23, 2026
94514a8
fix(plugins): keep built-in trust across upgrades (K4)
Sep 23, 2026
655ad3e
feat(plugins): vendor Computer Use 0.11.3 @ 0f54bf6 and re-pin the ca…
Sep 23, 2026
1e2e2a4
feat(acp): make the Full Access posture discoverable without letting …
Sep 23, 2026
7af7549
feat(status): warn when the session's pinned model left its provider'…
Sep 23, 2026
e0755a5
docs(providers): AICraft serves Claude and Gemini ids; point to its /…
Sep 23, 2026
96f5aeb
docs(fleet): record the #6298 re-scope — 0.10.1 ships no grant-model …
Sep 23, 2026
6dc4690
fix(runtime): tag engine plumbing items internal; drop the deferred-t…
Sep 23, 2026
5cf9db3
fix(prompts): narrate the user's task, not sandbox or tool plumbing (E4)
Sep 23, 2026
7946927
fix(web.run): browser-agent fallback, per-source receipts, neutral si…
Sep 23, 2026
593be41
fix(security): harden snapshot ids, auto-merge args and diagnostics r…
Sep 23, 2026
af4858e
fix(approvals): session grant never changes posture; approvals carry …
Sep 23, 2026
42a6dae
fix(approvals): scope web.run open grants by host; keep line breaks i…
Sep 23, 2026
57bfdc5
fix(status): pin drift reads the roster an earlier process persisted
Sep 23, 2026
53aee22
feat(fleet): `fleet run --check` validates a spec without launching (F8)
Sep 23, 2026
a518b2f
fix(plugins): a revocation blocks carrying built-in trust only until …
Sep 23, 2026
b905903
feat(approvals): honour readOnlyHint from reviewed plugins (CW-11)
Sep 23, 2026
735fb50
feat(plugins): list and reset suggestion dismissals (policy rule 9)
Sep 23, 2026
05ff7c7
fix(doctor): lead with a verdict and next step; drop the stale checkp…
Sep 23, 2026
acfa16c
feat(runtime-api): stop an agent run from the desktop (F2)
Sep 23, 2026
9cb912f
perf(tui): highlight code with Oniguruma instead of fancy-regex
Sep 23, 2026
df57b4c
perf(tui): share catalog rows between the live layer and the merged view
Sep 23, 2026
1a7da69
perf(tui): compare journal entries without cloning the transcript
Sep 23, 2026
be2140e
fix(fleet): `fleet run --check` leaves the workspace untouched (F8)
Sep 23, 2026
d2b1430
fix(doctor): a named route without a confirmed credential is not set …
Sep 23, 2026
7b16039
chore(deny): drop the fancy-regex 0.16.2 skip syntect no longer pulls
Sep 23, 2026
669c32d
fix(approvals): keep Full Access for destructive MCP tools; doctor an…
Sep 23, 2026
c949fee
chore(scripts): warn-only product lexicon lint, run from preflight
Sep 23, 2026
952307f
fix(web): keep maintainer source notes off rendered docs pages
Sep 23, 2026
5a5ef02
ci(codeql): add advanced setup workflow that honours codeql-config.yml
Sep 23, 2026
802002a
fix(approvals): end session grants when a thread is archived or deleted
Sep 23, 2026
59c2a16
fix(approvals): an approval survives a broader posture change (E2)
Sep 23, 2026
3026bba
docs: correct stale provider, crate, script and source anchors
Sep 23, 2026
7b5a76a
fix(engine): stop sending the deferred-tool retry hint to users (E3)
Sep 23, 2026
993cb20
test(tui): wait for the Fleet model picker to close before the next key
Sep 23, 2026
0f13f9a
feat(runtime-api): expose composer argument shape on GET /v1/commands
Sep 23, 2026
89c60ad
fix(approvals): do not record a session grant on an archived thread
Sep 23, 2026
b0a9f9b
docs: anchor telemetry counters by symbol; fix locale counts and crat…
Sep 23, 2026
bce7284
perf(engine): move synced history through the restore projection (M3)
Sep 23, 2026
75cd6ac
fix(cli): harden metrics --since, lane start worktree flags, lane sto…
Sep 23, 2026
7e5793a
fix(hooks): treat bash, Bash and exec_shell as one tool in tool_name …
Sep 23, 2026
26cfaf8
fix(fleet): search <workspace>/.codewhale/fleets as the workspace origin
Sep 23, 2026
b4cee56
fix(fleet): workflow(fleet:) runs saved v2 Fleets as a frozen exact s…
Sep 23, 2026
473f7b1
docs: sync zh_hans telemetry anchors and id locale paths with English
Sep 23, 2026
ca7a281
fix(fleet): map saved-Fleet reasoning onto exact tiers when freezing
Sep 23, 2026
6142998
fix(hooks): keep tool_category_for's doc comment on its own function
Sep 23, 2026
afb96ba
fix(engine): one failure, one true sentence, one next step (mark 2)
Sep 23, 2026
b9a3f14
fix(tui): harden /cache, /stash, /config and session prune edge cases
Sep 23, 2026
4c6f180
fix(tui): say agent, Fleet, Permissions, Work and Making room in Engl…
Sep 23, 2026
6e7410b
fix(tui): help lists English aliases only and every summary fits one row
Sep 23, 2026
afa53c8
fix(tui): the pet tank paints a resting whale and says when it is off…
Sep 23, 2026
f712c2a
fix(tui): provider rows you cannot use yet say "needs key" once
Sep 23, 2026
273b994
chore(scripts): lexicon lint ignores {placeholders}
Sep 23, 2026
39fd503
fix(tui): /setup says what it sets up
Sep 23, 2026
12f2fd3
fix(onboarding): a keyless first message leaves a durable recovery (U1)
Sep 23, 2026
caa5f94
docs(tui): reattach /stash preview doc and describe /cache arg errors
Sep 23, 2026
c52a5c4
feat(tui): approval card leads with the plain summary; footer labels …
Sep 23, 2026
1eb4ea3
fix(tui): a refused pet action keeps pet mode; model switch says save…
Sep 23, 2026
b98973b
fix(engine): posture notice and does-not-fit line say only what is true
Sep 23, 2026
7ec19fb
fix(tui): repo-rule approval card drops "law" and "postures"
Sep 23, 2026
ec507a0
Merge remote-tracking branch 'origin/main' into integration/2026-09-2…
Hmbown Sep 23, 2026
8f07e4d
fix(cli): first-contact text names codewhale, the right updater, and …
Sep 23, 2026
2bc541a
test(tui): serialize env stragglers, replace task_manager sleep races…
Sep 23, 2026
1b7c748
refactor(doctor): reuse current_install_method for update advice
Sep 23, 2026
105ad9d
fix(tools): point models at visible read/bash, not hidden File/Bash
Sep 23, 2026
d771281
fix(tui): localized voice status, distinct ASCII markers, NO_COLOR cu…
Sep 23, 2026
94d5f3c
perf(tui): load session-picker previews off the event loop
Sep 23, 2026
c86c54b
perf(tui): trim transcript and pager hot loops; prewarm syntax sets
Sep 23, 2026
c74da1b
Merge remote-tracking branch 'origin/integration/2026-09-22-web-ci'
Sep 23, 2026
f9c186d
docs(changelog): write the Unreleased 0.10.1 notes with issue receipts
Sep 23, 2026
425bfe0
ci(codeql): run advanced setup only when CODEQL_ADVANCED_SETUP is 'true'
Sep 23, 2026
df5012b
docs(changelog): use §19 vocabulary in the Unreleased notes
Sep 23, 2026
4f5ccce
fix(tui): align lane tests with ratified copy; fix Windows dead code,…
Sep 23, 2026
a1b7888
fix(tui): repaint when a session preview lands; voice status says how…
Sep 23, 2026
15a5228
docs(changelog): correct Unreleased issue receipts and credit #6406
Sep 23, 2026
5724b01
fix(tui): align idle-metrics and inline-modal tests with mark 4/8 copy
Sep 23, 2026
a030a07
fix(ci): repair release gates and reject invalid workflow paths befor…
Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ crates/*/assets/**/*.json text eol=lf
crates/*/assets/**/*.md text eol=lf
crates/*/locales/*.json text eol=lf
workflows/*.js text eol=lf
# Executable documentation: the Fleet tutorial's JSON fence is parsed by the
# task-spec contract test, so its bytes must agree on Windows and Unix.
docs/FLEET_WORKFLOW_TUTORIAL.md text eol=lf
# The dsh bundle scene is include_str!() into the generated client.js and
# hashed for stale detection; CRLF would change both across platforms.
crates/tui/src/integrations/dsh/*.js text eol=lf
Expand Down
33 changes: 33 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# CodeQL configuration for code scanning.
#
# Test code is out of scope for security alerts: it runs only in CI, talks to
# in-process loopback servers, and routinely prints fixture secrets in
# assertion messages to prove they are redacted elsewhere. Those paths are
# excluded here so alerts point at product code.
#
# Inline `#[cfg(test)] mod tests` blocks inside product files cannot be
# excluded by path; alerts there are dismissed as "used in tests".
#
# This file takes effect only with CodeQL advanced setup:
# .github/workflows/codeql.yml passes it to `github/codeql-action/init`.
# Default setup ignores it, so the repository's code scanning setting must be
# switched from Default to Advanced for either to apply.
name: codewhale-codeql

paths-ignore:
# Rust integration tests and split-out unit test modules.
- "**/tests/**"
- "**/tests.rs"
- "**/*_tests.rs"
- "**/test_support.rs"
- "**/*_test_support.rs"
# JavaScript / TypeScript test suites.
- "**/test/**"
- "**/__tests__/**"
- "**/*.test.js"
- "**/*.test.mjs"
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.spec.js"
- "**/*.spec.ts"
- "**/*.spec.tsx"
50 changes: 40 additions & 10 deletions .github/scripts/release-workflows.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const nightly = read(".github/workflows/nightly.yml");
const candidate = read(".github/workflows/release-candidate.yml");
const artifacts = read(".github/workflows/release-artifacts.yml");
const release = read(".github/workflows/release.yml");
const parityWorkflow = read(".github/workflows/release-parity.yml");
const republish = read(".github/workflows/release-republish.yml");
const releaseDockerfile = read("packaging/docker/Dockerfile.release");
const cnb = read(".cnb.yml");
Expand Down Expand Up @@ -83,15 +84,22 @@ const npmSmokeCases = [
["main Ubuntu", "push", true, "ubuntu-latest", true, true, false, false, true],
["main macOS", "push", true, "macos-latest", true, true, true, false, false],
["main Windows", "push", true, "windows-latest", true, true, true, false, false],
["main cache failure", "push", true, "macos-latest", true, false, true, false, false],
["light main", "push", false, "ubuntu-latest", true, true, false, false, false],
["schedule", "schedule", true, "ubuntu-latest", true, true, false, false, false],
];
// The sccache GitHub Actions backend is main-only, mirroring rust-cache's
// save-if: pull requests never install or enable it (cache bloat, PLAN D).
const sccacheInstallStep = "mozilla-actions/sccache-action@v0.0.11";
for (const [label, event, heavy, os, trusted, cache, execute, linuxDeps, cnb] of npmSmokeCases) {
const ref = event === "pull_request" ? "refs/pull/1/merge" : "refs/heads/main";
const onMain = ref === "refs/heads/main";
const installed = execute && onMain;
const context = {
needs: { changes: { outputs: { heavy: String(heavy), trusted: String(trusted) } } },
github: { event_name: event },
github: { event_name: event, ref },
matrix: { os },
steps: { sccache: { outcome: cache ? "success" : "failure" } },
steps: { sccache: { outcome: installed ? (cache ? "success" : "failure") : "skipped" } },
};
const jobGuard = npmSmokeJob.match(/^ if: (.+)$/m)?.[1];
assert.ok(jobGuard, "the wrapper job must retain its event guard");
Expand All @@ -104,7 +112,8 @@ for (const [label, event, heavy, os, trusted, cache, execute, linuxDeps, cnb] of
if (name === "Skip npm wrapper smoke for light change") expected = !heavy;
else if (name === "Install Linux system dependencies") expected = linuxDeps;
else if (name === "Linux smoke location") expected = cnb;
else if (name === "Enable sccache" || name === "sccache stats") expected = execute && cache;
else if (name === sccacheInstallStep) expected = installed;
else if (name === "Enable sccache" || name === "sccache stats") expected = installed && cache;
assert.equal(
Boolean(jobEnabled && vm.runInNewContext(guard, context)),
expected,
Expand Down Expand Up @@ -366,8 +375,22 @@ for (const block of rustCacheBlocks) {
assert.doesNotMatch(block, /github\.(event|ref|sha)|inputs\./);
}

const parity = release.match(/\n parity:\n([\s\S]*?)\n artifacts:\n/);
assert.ok(parity, "public release must retain a parity job");
// One parity gate, called by the release candidate and the public release,
// and the release refuses a tag without a green RC receipt for its exact SHA.
const parity = parityWorkflow.match(/\n parity:\n([\s\S]*)$/);
assert.ok(parity, "release-parity.yml must define the parity job");
assert.match(parityWorkflow, /^on:\n workflow_call:\n/m, "parity must be a reusable workflow");
for (const [name, source] of [["release.yml", release], ["release-candidate.yml", candidate]]) {
const caller = source.match(/\n parity:\n([\s\S]*?)\n\n/);
assert.ok(caller, `${name} must run the parity job`);
assert.match(caller[1], /name: Parity\n/, `${name}: the RC receipt check matches the "Parity" job name`);
assert.match(caller[1], /uses: \.\/\.github\/workflows\/release-parity\.yml/, `${name} must call the shared parity gate`);
}
assert.match(
namedStep(release, "Require a green release-candidate receipt for this exact SHA"),
/require-rc-receipt\.sh "\$\{GITHUB_REPOSITORY\}" "\$\{SHA\}"/,
);
assert.match(release, /^ resolve:\n(?:.*\n)*? actions: read\n/m, "resolve needs actions: read for the RC receipt");
assert.doesNotMatch(
parity[1],
/ref: \$\{\{ needs\.resolve\.outputs\.sha \}\}/,
Expand Down Expand Up @@ -500,11 +523,17 @@ assert.doesNotMatch(

// Cover every test invocation, including named parity and narrow crate gates.
// These launchers protect production dependencies as well as cfg(test) code.
// `release` is 4 rather than 3: parity runs the workspace under nextest for the
// same one-process-per-test isolation CI's lanes use, and keeps a separate
// doctest invocation because nextest does not run doctests.
// `release parity` is 4 rather than 3: parity runs the workspace under nextest
// for the same one-process-per-test isolation CI's lanes use, and keeps a
// separate doctest invocation because nextest does not run doctests. release.yml
// itself runs none: its parity job calls release-parity.yml.
let hermeticInvocations = 0;
for (const [label, workflow, expected] of [["CI", ci, 5], ["release", release, 4], ["CNB", cnb, 3]]) {
for (const [label, workflow, expected] of [
["CI", ci, 5],
["release", release, 0],
["release parity", parityWorkflow, 4],
["CNB", cnb, 3],
]) {
const commands = workflow.split("\n").filter((line) =>
!line.trimStart().startsWith("#") && /\bcargo (?:test|nextest run)\b/.test(line),
);
Expand Down Expand Up @@ -659,6 +688,7 @@ for (const [name, source] of [
["release-candidate.yml", candidate],
["release-artifacts.yml", artifacts],
["release.yml", release],
["release-parity.yml", parityWorkflow],
["release-republish.yml", republish],
["ci.yml", ci],
["nightly.yml", nightly],
Expand Down Expand Up @@ -701,7 +731,7 @@ assert.equal(jobTimeout(nightly, "build"), 90);
assert.equal(jobTimeout(release, "resolve"), 10);
// The v0.9.12 tag push finished every parity step and was then cancelled at
// 20 minutes inside rust-cache's post-run save; 45 keeps that margin.
assert.equal(jobTimeout(release, "parity"), 45);
assert.equal(jobTimeout(parityWorkflow, "parity"), 45);

console.log(
"Workflow contracts OK: 6-target/12-asset single-runtime nightly and exact-head 7-target/34-asset release candidate.",
Expand Down
48 changes: 18 additions & 30 deletions .github/workflows/approve-contributor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,27 @@ jobs:
]);
const scope = scopeByCommand.get(command);

if (!scope) return;
if (!privileged.has(comment.author_association)) return;
if (scope === 'pr' && !issue.pull_request) {
await github.rest.issues.createComment({
// Answer the maintainer's command with a reaction, never a comment
// (founder, 2026-09-22). The run log carries the detail, and the
// allowlist PR body links back here, so the thread still shows it.
async function react(content, message) {
core.notice(message);
await github.rest.reactions.createForIssueComment({
owner,
repo,
issue_number: issue.number,
body: '`/lgtm` grants PR access and must be used on a pull request. Use `/lgtmi` to grant issue access.',
comment_id: comment.id,
content,
});
}

if (!scope) return;
if (!privileged.has(comment.author_association)) return;
if (scope === 'pr' && !issue.pull_request) {
await react('confused', '`/lgtm` grants PR access and must be used on a pull request. Use `/lgtmi` to grant issue access.');
return;
}
if (scope === 'issue' && issue.pull_request) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: '`/lgtmi` grants issue access and must be used on an issue. Use `/lgtm` to grant PR access.',
});
await react('confused', '`/lgtmi` grants issue access and must be used on an issue. Use `/lgtm` to grant PR access.');
return;
}

Expand Down Expand Up @@ -116,12 +119,7 @@ jobs:

const existing = parseAllowlist(content);
if (existing.has(entry) || existing.has(`all:${normalizedLogin}`)) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: `@${targetLogin} is already approved for ${scope} contributions in \`${path}\`.`,
});
await react('eyes', `@${targetLogin} is already approved for ${scope} contributions in \`${path}\`.`);
return;
}

Expand All @@ -145,12 +143,7 @@ jobs:
});

if (pendingPr) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: `@${targetLogin} already has a pending allowlist update PR for ${scope} contributions: ${pendingPr.html_url}`,
});
await react('eyes', `@${targetLogin} already has a pending allowlist update PR for ${scope} contributions: ${pendingPr.html_url}`);
return;
}

Expand Down Expand Up @@ -210,9 +203,4 @@ jobs:
].join('\n'),
});

await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: `Created allowlist update PR: ${pr.html_url}`,
});
await react('rocket', `Created allowlist update PR: ${pr.html_url}`);
82 changes: 82 additions & 0 deletions .github/workflows/cache-janitor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Cache janitor

# The Actions cache held 11,099,951,127 bytes across 2,896 entries, past the
# repo's 10 GiB cap, so GitHub evicted live main entries first. A cache is
# readable only from its own ref and the default branch: once a PR closes or
# a release finishes, its refs/pull/N or refs/tags/vX entries are dead weight.
# This deletes them. Branch caches (main included) are never touched; see
# scripts/release/prune-actions-caches.sh.
on:
# pull_request_target so fork PRs get a token that can delete caches. It
# never checks out or runs PR code: the checkout below is the base branch.
pull_request_target:
types: [closed]
workflow_run:
workflows: [Release]
types: [completed]
schedule:
- cron: '17 4 * * *'
workflow_dispatch:
inputs:
dry_run:
description: List what the sweep would delete without deleting it
required: false
default: true
type: boolean

permissions:
contents: read

concurrency:
group: cache-janitor-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.workflow_run.id || 'sweep' }}
cancel-in-progress: false

jobs:
prune:
name: Prune dead caches
if: github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
timeout-minutes: 15
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
# Read PR state for the sweep.
pull-requests: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
# Base-branch script only. Never the PR head.
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- name: Prune
shell: bash
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
script=scripts/release/prune-actions-caches.sh
case "${EVENT_NAME}" in
pull_request_target)
"${script}" --ref "refs/pull/${PR_NUMBER}/merge" --ref "refs/pull/${PR_NUMBER}/head"
;;
workflow_run)
# A tag-push Release run reports the tag as head_branch. The
# script refuses anything that is not a refs/tags/<tag> ref.
"${script}" --ref "refs/tags/${RUN_HEAD_BRANCH}"
;;
workflow_dispatch)
if [[ "${DRY_RUN}" == "true" ]]; then
"${script}" --dry-run --sweep
else
"${script}" --sweep
fi
;;
*)
"${script}" --sweep
;;
esac
Loading
Loading