Skip to content
Insights · Engineering

Which cache did that job restore?

Name the cache key that job restored, and whether the match was exact. A different job can restore another key, and another client does not list this one.

Scroll
EngineeringOct 30, 202614 min readBy Salman Naqvi, Founder & CEO
Which cache did that job restore?

The client asks why the build was slow, or why it installed a dependency they did not pin. Tonight, three proofs, in a repository the agency or the client can open. Name the cache that job restored: the key, the path, and whether the log called the match exact. Show a different job whose key or match is not this one. Show another client's repository that does not list this cache. If any of the three needs the person who edited the workflow and then left, you have not shown the cache. You have a green check and a guess.

This is the dependency cache a job restored or missed. It is not the runner that took the job. Which runner did that job use? is a runs-on label and the machine the log named. A label can be ubuntu-latest while the cache key is a hash of a lockfile, and a cache hit is not a CPU count. It is not the file the run kept. Which artifact did that run keep? is a named upload with a retention. A cache can be evicted while the artifact is still downloadable, and an artifact is not a restore key. It is not the image the deploy ran. Which image did that deploy run? is a reference and a digest. A digest is not a cache key. It is not the version the lockfile pinned. Name the package the lockfile pinned is a resolved version. A hash of the lockfile can be part of a key, and the hash is still not the version.

None of what follows is a hit rate for this client's repository, and none of it is legal advice. The limits below are one vendor's current rules for a workflow cache. The counts below that are a 2025 study of other people's workflow files, through May 5, 2025. A quota is not a census of your jobs. A study of 952 repositories cannot name the key on the run you opened tonight. They do tell you which numbers people mix up when they try to sound precise about a cache they have not opened.

GitHub's dependency-caching reference says a key is required and has a maximum length of 512 characters. A longer key fails the action. 512 is a character limit. It is not 512 megabytes, and it is not a count of caches. path is also required. restore-keys is optional. An exact match on key is a cache hit, and the action's cache-hit output is true only for that exact match. A restore through a prefix is not cache-hit true. On a miss, if the job succeeds, the action creates a new cache with the key you provided. You cannot change the contents of an existing cache. A new key is a new cache. The example expression hash of package-lock.json is printed as npm-d5ea0750. That string is an example key. It is not your lockfile, and it is not a commit. The same page's workflow example pins actions/checkout at v6 and actions/cache at v4. v6 is not v4. Do not tell the client the cache action is on the checkout pin (GitHub, Dependency caching reference).

The search order is a list, and a shorter list is a different search. For a pull request whose head is feature and whose base is main, the example walks six steps: the exact key on feature, then the feature-prefix on feature, then the npm- prefix on feature, then the same three on main. Six lookups. A hit on step 4 is a default-branch cache, not a feature-branch cache. A child branch cannot restore a cache that a workflow on its parent created for the child, and a sibling cannot restore a sibling. A cache created by pull_request is stored on the merge ref and can be restored only by re-runs of that pull request. It is not restored by the base branch. pull_request is not one of the triggers that may write a cache into the default branch's scope.

Those write triggers, for the default branch, are push, workflow_dispatch, repository_dispatch, delete, registry_package, page_build, and schedule. Seven names. A run on any other trigger that resolves to the default branch gets read-only access unless the workflow opts out. Read-only means restore is allowed and save fails as a warning. The step and the job do not fail. cache-mode is read, write, write-only, or none. Four names. read restores and does not save. write does both. write-only saves and does not restore. none does neither. If you omit cache-mode, a trusted trigger defaults to write and a low-trust trigger defaults to read. A skipped restore is treated as a miss. A skipped save is not performed. write-only is not write, because write-only cannot restore, and a caller that allows only write-only cannot call a reusable workflow that declares read. The two grants do not overlap.

The size limits are a different table from the key length. GitHub removes caches that have not been accessed in over 7 days. There is no limit on the number of caches. The default total size is 10 GB per repository. 10 GB is not 7 days, and 7 days is not 512 characters. Usage beyond 10 GB is billed, and once the repository is at its maximum, eviction deletes caches from oldest last-access to newest. You can upload at 200 per minute per repository and download at 1500 per minute. 200 is not 1500. User-owned repositories can be configured up to 10 TB. 10 TB is not 10 GB. The illustrative monthly prices, if the storage is fully used, are $2.80 for 50 GB, $13.30 for 200 GB, and $69.30 for 1000 GB. $2.80 is not $13.30, and $13.30 is not $69.30. 1000 GB is not 10 TB. A budget of $0 with a configured maximum of 20 GB goes read-only once storage passes the free 10 GB. $0 is not $2.80. 20 GB is not 10 GB. A read-only cache caused by a budget is not the read cache-mode you set on purpose.

The study people reach for when they want an adoption rate is a different document, and it counted files, not this job's log. Kazi Amit Hasan and Yuan Tian at Queen's University, Safwat Hassan at the University of Toronto, and Steven H. H. Ding at McGill studied GitHub Actions caching. The abstract says they analyzed 952 repositories, 266 cache adopters and 686 non-adopters, spanning 1,556 workflow files, 10,373 commits, and 17,185 workflow configuration changes, with an average of 9.37 cache-related changes per repository. Histories run through May 5, 2025. The source set is the 952 repositories in Bouzenia and Pradel, 2024. 266 plus 686 is 952. 9.37 is not 17,185. The introduction says 2,494 of the 17,185 changes are caching-related, which they also print as 14.53%. 2,494 is not 17,185. 14.53% is not 9.37. 2,494 divided by 266 is the average they print as 9.37. Do not divide 17,185 by 266 and call that 9.37 (Hasan, Tian, Hassan, and Ding, How Developers Adopt, Use, and Evolve CI/CD Caching).

The language table does not use one rate for the whole 952. PHP is 26 adopting and 22 not, printed as 54.2%. Java is 36 and 35, printed as 50.7%. Kotlin is 12 and 21, 36.4%. TypeScript is 45 and 79, 36.3%. 36.4 is not 36.3. Go is 22 and 54, 28.9%. JavaScript is 37 and 109, 25.3%. C++ is 17 and 52, 24.6%. Python is 42 and 146, 22.3%. C is 9 and 38, 19.1%. Shell is 4 and 17, 19.0%. 19.1 is not 19.0. Ruby is 6 and 28, 17.6%. Others is 10 and 79, 11.2%. The adopter column sums to 266. The non-adopter column, as printed, sums to 680. 680 is not 686. The page does not print the six non-adopters that sit between those two figures. A chi-square test is reported at p less than 0.001. That p-value is not an adoption rate, and it is not 54.2%.

Table 4 is a shape of the 266, not a hit rate. Workflow files per adopting repository: minimum 1, mean 5.85, standard deviation 8.02, 25th percentile 2, median 3, 75th percentile 6, maximum 75. The standard deviation is larger than the mean. Jobs: minimum 1, mean 6.57, standard deviation 11.14, 25th 2, median 4, 75th 7, maximum 116. 5.85 is not 6.57. 75 is not 116. 3 files is not 4 jobs. 8.02 is not 11.14. None of these is 10 GB.

Table 5 is jobs, and Table 6 is steps, and the two totals are not the same population. Table 5 prints build 247 of 662 (37.31%), integration 15 of 41 (36.58%), lint 22 of 79 (27.84%), test 141 of 543 (25.96%), release 76 of 332 (22.89%), linux 6 of 29 (20.69%), analyze 20 of 126 (15.87%), sync 3 of 59 (5.08%), and other 51 of 457 (11.16%). The job total is 581 of 2,328, printed as 24.95%. The prose says test and lint show notable prevalence and lists 25.96% before 27.84%. Lint is higher. Sync is called rare at 5.08%, and other is 11.16%, which is higher than sync and lower than analyze's 15.87%. 15 on the integration row is a count of jobs. It is not 15.87%. 24.95% is not the abstract's 27.9% of repositories, which is their label on 266 of 952.

Table 6's step total is 898, not 581. Explicit caching is 637 (70.9%), package-manager caching is 220 (24.5%), Docker layer caching is 2 (0.2%), and other is 39 (4.3%). Those four percentages sum to 99.9, not 100. Build steps are 282, 71, 1, and 32, summing to 386, with printed shares 73.1%, 18.4%, 0.3%, and 8.3%. Those four sum to 100.1, not 100, and not 99.9. Release steps are 41% explicit and 59.0% package-manager. 59.0 is not the table total's 24.5. On release, the package-manager share is the larger one. Linux steps are 7 of 7, printed as 100.0% explicit. 7 steps is not Table 5's 6 linux jobs of 29. 386 build steps is not 247 build jobs. 898 is not 2,328. A later sentence says the build self-loop on cache-version updates is 77.63% of outgoing transitions from that state, with a median of 0 days. 0 days is not 7 days of eviction. Removing a cache and adding one, in build jobs, is 21.84% with a median of 0.01 days. In test jobs the same transition is 30.19% with a median of 0.00 days. 0.00 is not 0.01, and 30.19 is not 21.84.

The timing sentences do not share one median. Parameter-update self-loops are 65.53% in build jobs, median 4.08 days, and 66.05% in test jobs, median 2.93 days. 65.53 is not 66.05. 4.08 is not 2.93. From enablement to a parameter update, build jobs have a median of 105.93 days. From enablement to a cache-version update, release jobs have a median of 270.37 days and analyze jobs 384.02 days. 105.93 is not 270.37, and 270.37 is not 384.02. From enablement to adding a cache, the median is 53.14 days in build jobs and 41.97 days in test jobs. 53.14 is not 105.93. A day count in someone else's history is not the key on this run. 10,373 commits is not 10 GB.

Do the three proofs on one real job, in the repository that actually ran it. First, the binding. Copy the key, the path, and the cache-hit output. If the output is not true, write whether a restore-key matched, and write that the match was not exact. If the log does not contain the key, write that. Do not infer npm-d5ea0750 because an example prints it. Second, the other job. A different job, with a different key or a different hit, is not this one. A paper's adoption rate is not that second job. If you cannot show a job that is not this cache, you have a counter, not a record. Third, the other client. A repository that belongs to someone else does not list this key. If the only cache is a shared organization bucket and you cannot say whose key it is, write that. A client you cannot name is the finding. It is not a reason to tell them the build was cached.

Three moves feel like the proof and are a second problem. Deleting the cache because the key was embarrassing, and then describing the miss as a cleanup. Quoting 70.9% because it looks precise, while the job in front of you is one restore, and 70.9% is a share of 898 steps in other repositories. Asking a model which key was used. The model will invent a hash. It will not open the log. The runner that took the job, which you have not named, is which runner did that job use?. Opening that page does not name the key.

The path is ordinary when someone can open one branch's workflow without opening the next branch's log as the way to prove it. Concordia is a student-information platform deployed per branch, with one branch's data kept apart from the next. The shape that matters here is one job you can point at, in a repository the client can open, distinct from the next client's run. That case is not a cache ledger. Nothing in the description is a headcount, a duration, or a result figure. If the only control on the workflow screen restores every client's dependencies as one key, you do not have this proof. You have a shared bucket.

There is a version of this call where hiring the path is the wrong purchase. The work is not sold, so there is no client repository to open. The client has asked to meet the people who will write the code. A cache key will not hide that. The criteria for choosing a firm, when you are still choosing, sit on the page for a top SaaS development company. That page is criteria. It does not rank anyone, including us. The room only has a slide. Or the buyer only wants a template of a workflow, with no repository to put it in. Or the buyer wants a legal opinion about who owns a cached file. Hire the lawyer. A white label software development partner is the wrong purchase for a blank.

When the job is real and the repository cannot name the key, the missing object is specific. The key and the cache-hit line on that job, a second job whose key is not this one, and a second client's repository that does not list it. A white label software development partner that expects this will put the repository where the client can read the log, before the first week is billed, because a cache on a laptop you do not hold is not a key you can name. One that can only paste a screenshot of a green check is telling you who holds the workflow. Where the next slice is one capability and the acceptance sentence names the key, an AI integration sprint is the shape that can require the three proofs as the definition of done. Where you are not sure which repository the current engagement runs from, a two-week AI readiness assessment maps the systems. The findings are yours. They are not 70.9%, and they are not 10 GB.

The session a checkout opened, and the cache write a prompt recorded, are different objects: which session did that checkout open? and which write did that cache record?. Do not record either on this page. A payment status is not a cache key. A token write is not a restore.

Before you tell them the build used the cache, do the three proofs and write the key. Name the key, the path, and whether the match was exact, or write that the log did not store them. Show a different job that is not that cache. Show another client's repository that does not hold it. Do not delete the cache to avoid the row, and do not ask a model what the key was. A 2025 count can tell you how often other repositories configured a cache step. The binding is the log you can defend.

Questions

Three records, in a repository the agency or the client can open. The key, the path, and whether the log called the match exact. A different job whose key or match is not this one. Another client's repository that does not list this cache. If the log only shows a green check, write that the key was not stored.

No. A share of steps in someone else's workflows is not the key on this run. A cache you can defend is the key and the cache-hit line, or a written line that the log did not record them. 70.9% of 898 steps is not a key, and 10 GB is not 512 characters.

When the work is not sold under the agency's name, when the client asked to meet the builders, when the room only has a slide, when the buyer only wants a template, or when the buyer wants a legal opinion.

Find this useful? Tell Google to show you more of it.

Let's put AI to work in your business.

A 30-minute call. You bring the workflow or the roadmap — we'll tell you what's feasible, what it costs, and what we'd build first.

Book a call→