Docker images are kept and served on machines whose files are in memory (superci/scratch=ramdisk). The cache was never switched in there: the kernel mounts no EROFS file from memory’s filesystem, so its setup turned it off, while each job still said its layers came from the cache (it counted what the cache’s index listed). The layers’ files now go on a disk in memory, with no disk of their own beside it (and none in the price). A job says layers came from the cache only when they did, and says why the cache was off when it was. On a 16-CPU machine with files in memory, a local Supabase starts in 27 s instead of 32 s, with 6.2 GB of memory used instead of 11.6 (the images are no longer held both compressed and unpacked).
A machine gets every layer’s link with the cache’s index at its start, and fetches the layers its job’s last run used right away, before Docker asks for them (106 layers, 3 GB, in 3.3 s): its pull then finds them there. It asked for links layer by layer as Docker pulled, and a burst of those waited seconds on the control plane. Layers a job used are kept in the cache as long as jobs use them, as before.
The cache converts up to 64 of a region’s Docker image layers at once (it was 16, the rest a minute apart), so a stack’s images (a hundred layers) are there for its next run, not filled over five.
A job’s Docker images say why what was pulled was not kept: no whole file of it on the machine, the cache would not take it, or its upload failed (with the failure’s words).
A job’s Docker images count what the cache had already among what was kept: a layer pulled that the cache had (from another run, or another repository’s public one) is there for the next job too, and was said as “none uploaded to the cache”. Its machine says those apart (had).
superci limits retrieve lists every limit spending counts against, each with the command that changes it: the CI’s, its AWS account’s for a month and a day (its spot and on-demand machines together), and a provider’s own. It said “no limit is set” while the AWS account’s limit was what refused jobs. superci accounts retrieve aws says the account’s limits too.
Whether AWS itself emails is said with them: with no budget in AWS, nothing emails at 80% or 100% (superci accounts update aws --aws-budget=emails --email=ADDRESS adds one). With one, superci accounts retrieve aws says what AWS has counted against it so far. That is AWS’s own bill, hours behind SuperCI’s estimate, and its emails follow that count.
A job refused at an account’s or a provider’s limit says the command that raises it: “the AWS account reached its $100 a month (superci accounts update aws --monthly-usd=N raises it)”.
A label added while the control plane does not answer (it is starting with a new setting) says to try again in a few seconds; it said to update the control plane.
superci connections delete github OWNER for the first organization a control plane was connected with says that its App stays while the control plane does (superci leave stops SuperCI); it said “That organization is not one added here”.
A job of a run from a computer started again after AWS took back its spot machine keeps its artifacts anew, as a new attempt of a job does at GitHub: its first attempt’s upload-artifact of the same name made the second fail with a name conflict, failing the run though every test had passed. Another job of the run keeping an artifact of that name is still refused, as GitHub refuses it.
superci runs create says a job that was started again, and which job its first attempt was: “Started again on an on-demand machine: AWS took back its spot machine”.
superci jobs list --run=ID lists that run’s jobs (it took the flag and listed every run’s).
A step whose result its runner never sent is judged by its own lines: tests that failed there mark it failed (✗, not ·), and each is named with its error, as for a step said to have failed. The summary also waits for steps’ results as it waits for their logs (a minute at most).
A GitHub job that waited for spot (your spot limit reached) or was started again after AWS took back its spot machine registers its runner anew: GitHub took its name once, its next try was refused (“A runner with the name … already exists”) and the job failed at Set up runner without running. The registration from the try before is taken out first, and a name GitHub still has gets a new one.
A dashboard page opened while the one before it was still loading stays open: that page’s answer, coming after, put it back (a click on Runners or Settings right after the dashboard opened could land on Overview again).
A job that waited for room under your AWS spot limit says that wait apart in its start: “1m 7s waited for room under your AWS spot limit”, then “2.5 s AWS gave a machine” (it said AWS took 70 s).
superci runs create says when the run is over and its summary waits for the steps’ logs (its runner sends them after the job, a minute at most): it read as finished, then went on.
The cache keeps a job’s Docker images again: a machine offers what it pulled once its job is over, and by then the control plane had ended the job’s record of its runner and answered every layer “no” (always for a run from a computer, often for a GitHub job), so nothing was kept. A job’s page and superci jobs retrieve say what was uploaded: “97 layers pulled (1.9 GB), none uploaded to the cache” said “kept for the next job”.
A control plane asked with a key it does not take says so (status 401, with its version), where it said “nothing here” (404); a read or run key asking for what needs a device’s key gets 403. superci says why and what to do, never “No runs yet” or a run not there: the key expired or was replaced (sign in again), SuperCI is older than the control plane (update it), or the control plane is from before 0.19.13 and reads the key under its old name (sign in again, or update it).
A key an older SuperCI kept (DASHBOARD_KEY_…) is kept under the new name and handed again, instead of being taken for one that expired: two versions used on one computer replaced each other’s key at each command, and a dashboard or a command running with the replaced key lost the control plane. A key that is replaced is left to expire, so a command still running with it goes on.
Commands run side by side on one computer (a coding agent’s, several at once) hand the control plane one key between them: each made and handed its own when the computer needed a new one (six commands, six keys, each listed as a device).
A key handed by a command (not only by a dashboard) says which device it is on: Settings → Keys and superci keys list named those “A device”.
superci secrets create and update ask for the value in a terminal, typed hidden; stdin and --value-env work as before.
superci planes update --dry-run says when the control plane is on this version already (it is deployed again, and what this version has beside it is made where missing), and from which version it would update. superci planes allow --dry-run reads the control plane’s AWS role and says what it lacks, or that nothing would change. An update’s step names both functions it uploads.
A job whose spot machine AWS takes back says why on GitHub: an error in its log and among its annotations, beside GitHub’s “The runner has received a shutdown signal”. Its row says plainly that SuperCI runs it again by itself, off spot machines, once its run has finished (GitHub runs a job again only then): “run again, off spot machines” read as something to do.
superci jobs retrieve gives a running job’s log as far as it goes: GitHub has it only as far as its runner has sent it in blocks (minutes behind at times), and what its machine sent after that comes too. It says where the log came from and when its last line was written; --out says a running job’s is its log so far. --run=ID --job=NAME without --out found the job and asked for no log.
A machine other than the first choice says why, with the price: “Not the first choice: AWS had no spot c8a.16xlarge in us-east-1a ($0.135 an hour)”, now in superci runs create and superci jobs list too. When the first choice has no machine, AWS is asked for the cheapest of the others of that size it has room in (it took the one it had most room in: an r8a.16xlarge at nearly three times a c8a’s price).
A job refused spot for your account’s spot limit while machines of SuperCI’s that just ended still count toward it (AWS counts a machine until it has shut down) waits up to two minutes for spot, asked every 20 seconds, before an on-demand machine. A job whose machine never took it, and so got another, counts what every machine cost.
--tests and --quiet name each test that failed as expected, however many ran (Playwright prints no error for them, and passes one that failed for another reason too), and count them over every test: a run of more than 200 tests counted those among the first 200 only. Each run of a test Playwright repeats (--repeat-each) is counted, as Playwright counts it: ten runs that failed as expected were said as one. Where several processes run tests at once ([slot 2] …), each one’s counts are said, and a failed step’s last lines are each one’s own (they were the last one to end’s).
A runner slow to reach the control plane says what it logged meanwhile at the end of superci runs create (a fixed wait of 30 seconds is its retry after a request that failed).
superci runs create waits up to a minute (it waited 15 seconds) for each step’s own log once the run is over before it counts the tests: the runner sends a large log after the job has ended, and a step read from the lines sent as it went, some dropped, counted too few (tests that failed as expected came to 6, 9 or 11 where whole logs have 86). When a log still has not come, the counts say they are from what came, and superci runs retrieve ID --tests counts it all.
A run from a computer cancelled before its machine took its job keeps nothing of its secrets (they stayed in the control plane’s store for a day). A run key cancels runs from a computer only: it could cancel a GitHub run by its id.
superci runs wait follows a run with a lot of output, also one already over: a run’s status gives a job’s lines 1.5 MB at a time (asked for whole, one answer was over what Lambda answers with, and the control plane answered 502).
A job’s steps show as GitHub shows them: a parallel group’s steps under its row, folded with it, and each step marked background, parallel (with how many steps), wait, wait all or cancel, with the steps it names. For a job of GitHub’s, its machine says it once the job begins (from the start of the runner’s own log).
A workspace looks after one CI. Control planes found in its signed-in clouds that are not its own (another CI in the same AWS account, a teammate’s) are listed apart, with their names and whose jobs they run, and never taken in by themselves: a new workspace picks one (Use this one, or superci workspaces update ID --plane=PLANE) or sets up a new one, and one another workspace here looks after opens that workspace. superci planes list lists them apart. A control plane writes whose jobs it runs where its account’s sign-in reads it (OWNERS), for that list.
Each CI in an AWS account has a name of its own: renaming one, or setting one up under the workspace’s name, is refused when another CI there is called that (whatever the case). Two workspaces on this computer that look after the same CI say so in the switcher and in superci workspaces list (“same CI as …”).
Settings → Keys lists who reaches the CI: each computer’s dashboard (the computer’s name, when its key was made and last used) and the read and run keys made for coding agents and scripts, each with Remove. superci keys list lists the computers too, and superci keys delete DASHBOARD_KEY_… takes one out (this computer’s own: superci logout).
Control planes in one AWS account share one network in each region (made by the first, deleted with the last, and only with no machine in it): AWS gives an account five networks in a region unless asked for more, and each control plane made its own. One made before keeps its own.
Secrets for runs from a computer, as a repository’s are kept for its runs on GitHub: superci secrets create NAME (the value from stdin or --value-env=VAR, never from the command), list, update and delete, and Settings → Secrets. Each is written with your sign-in into the control plane’s own cloud and never read back out: lists say names, where each may be used, when it was set and last given to a run, and who started it. --repo, --branch, --workflow and --environment narrow where one may be used, GitHub’s patterns too (release/*); a job gets the narrowest under each name, and a value given with the run (--secret) wins. A run gets only the secrets its workflows name; one named and not kept is empty, as on GitHub, and superci runs create says which before the jobs start. Run keys start runs that get them. Jobs GitHub sends get GitHub’s own secrets, as before. Moving to another control plane carries them along. Needs superci planes update.
The cache keeps the Docker images jobs pull, for their next runs, on Ubuntu 26.04 machines (superci/image=ubuntu26): the next job reads them from your bucket in its region instead of the internet (a local Supabase started in 37 s instead of 100 s, a 2 GB Playwright image pulled in 6 s instead of 22 s). Nothing is asked of you and nothing changes in workflows. After a job, its machine keeps the layers it pulled that the cache lacks, before it goes off, so the job’s own time is never touched; a repository’s first run is Docker as it comes. A layer any registry gives to anyone may be read by every repository; any other only by the repositories that pulled it. A job’s page and superci jobs retrieve say what it had from the cache and what it pulled. A job whose last run used images gets a fast disk of its own for them, in its price (about $0.02 an hour); one that used none gets nothing. The Cache page and superci caches retrieve say what is kept; superci caches update --images=false turns it off. A layer goes after the cache’s days unused. A control plane on AWS has a second function for it, superci-plane-ID-convert, with a role that may only read and write the layers/ part of its buckets; superci planes update adds it.
The same words everywhere: a device key (each device’s dashboard and commands, named after the device), a read key and a run key (superci keys create NAME [--runs], for coding agents and scripts); a launcher for what SuperCI deploys in Cloudflare or Modal to start machines there for a control plane elsewhere (it was a runner agent; its Worker stays superci-runners-ID). The control plane keeps them under the same names (DEVICE_KEY_…, LAUNCHERS): superci planes update moves what it kept under the old ones (DASHBOARD_KEY_…, AGENTS) and takes those out. A device still on an older SuperCI reaches an updated control plane once it is updated too. Settings → Keys lists them in rows that fit the card.
A run from a computer no longer prints a line twice, or says a line will follow when nothing was missing: GitHub’s runner leaves empty lines out of what it sends as a job goes, but counts them. Lines really missing are printed once the step’s log is in, as “which came late”.
Machines on AWS can run Ubuntu 26.04, with Docker 29: runs-on: superci/image=ubuntu26 for a job, or superci machine update --image=ubuntu26 (and the dashboard’s default machine) for every job. Unset, the Ubuntu follows GitHub’s ubuntu-latest: 24.04 until 19 November 2026, 26.04 from then on. image=ubuntu24 keeps 24.04 after that. Cloudflare’s and Modal’s containers stay on 24.04 for now, and a label asking for 26.04 is not sent there.
A run from a computer’s checkout on a macOS machine no longer fails now and then after unpacking the whole tree: macOS’s tar stops reading at the archive’s end, and what still fed it was taken for a failure.
A failed job’s summary names the steps that failed (“Failed: Run npm run typecheck”), with runs create, runs wait and runs retrieve --tests: when every test passed and a type-check failed, only “The run ended: failure” said so.
The tests’ totals count those that failed as expected (test.fail()) and the flaky ones (“Tests: 0 failed, 65 passed (playwright), of the passed 3 failed as expected, 1 flaky”), also when the list of tests is left out.
superci runs create notes a --var or --secret value with characters a shell runs (|, ;, &, a backquote, $(): a step that puts it in a run: line runs them.
--tests and --quiet keep a test’s own bracketed title (“shows the page (old layout)”): only Playwright’s time and retry marks are taken off. Two tests alike but for that showed as one line, and a test that really failed, titled “… (issue #25)”, was listed as failed as expected.
An AWS sign-in is renewed by one SuperCI at a time, as Cloudflare’s already was: several commands at once (agents in parallel) each renewed it, AWS took the first renewal and refused the rest, and those took it for the sign-in’s end and signed everything out.
A run from a computer sends what changed since that folder’s last run, and says so (“Sending what changed since this folder’s last run, 3 minutes ago: 2 files, 0 removed”): each run’s changes are kept as a layer on the last run’s, eight at most, then all of it again. It sent what changed since the folder’s first run, which grew with every edit and read as wrong both ways.
A run from a computer’s checkout on a macOS machine unpacks what changed since a run before: macOS’s tar refused how it was asked.
Docker on AWS machines pulls sixteen of an image’s layers at once (it pulled three): a stack of many images, as a local Supabase’s, is pulled sooner. Its own settings stay.
superci runs create and runs wait say what each job’s tests came to at the end with or without --quiet, and when a test runner ran no tests (a run that passed having run nothing said so only with --quiet or --tests).
A run from a computer’s checkout says the line it stopped at, when it stops.
The dashboard keeps sign-ins made in another terminal while it runs, however many: after it took one in, a second made before its next write was put back as it had been (a second AWS account, signed in from a terminal, was lost).