102

I am working on an R package and using GitHub Action (GHA) as a Continuous Integration (CI) provider. I cache R packages (dependencies) by using actions/cache. And now I want to clear all cache. How can I do that?


A part of GHA Workflow I use:
on: push

name: R-CMD-check

jobs:
  R-CMD-check:
    runs-on: ${{ matrix.config.os }}

    name: ${{ matrix.config.os }} (${{ matrix.config.r }})

    strategy:
      fail-fast: false
      matrix:
        config:
          # - {os: windows-latest, r: 'devel'}
          - {os: macOS-latest,   r: 'release'}

    env:
      R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
      RSPM: ${{ matrix.config.rspm }}
      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

    steps:
      - uses: actions/checkout@v2

      - uses: r-lib/actions/setup-r@master

      - name: Query dependencies
        run: |
          repos <- c("https://r-hyperspec.github.io/hySpc.pkgs/", getOption("repos"))
          saveRDS("remotes::dev_package_deps(dependencies = TRUE)", ".github/depends.Rds", version = 2)
          writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
        shell: Rscript {0}

      - name: Cache R packages
        if: runner.os != 'Windows'
        uses: actions/cache@v1
        with:
          path: ${{ env.R_LIBS_USER }}
          key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}
          restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-

      - name: Install dependencies
        run:   remotes::install_deps(dependencies = TRUE)
        shell: Rscript {0}

      - name: Session info
        run: |
          options(width = 100)
          pkgs <- installed.packages()[, "Package"]
          sessioninfo::session_info(pkgs, include_base = TRUE)
        shell: Rscript {0}
KyleMit
  • 30,350
  • 66
  • 462
  • 664
GegznaV
  • 4,938
  • 4
  • 23
  • 43

10 Answers10

145

Update (October 20, 2022)

You can now manage caches via the UI:

https://github.com/<OWNER>/<REPO>/actions/caches

Update (June 27, 2022)

You can now manage caches via the GitHub Actions Cache API:

  • GET list of caches for a repository:

    $ curl \
        -H "Accept: application/vnd.github.v3+json" \
        -H "Authorization: token <TOKEN>" \
        https://api.github.com/repos/OWNER/REPO/actions/caches
    
  • DELETE cache for a repository using a cache ID:

    $ curl \
        -X DELETE \
        -H "Accept: application/vnd.github.v3+json" \
        -H "Authorization: token <TOKEN>" \
        https://api.github.com/repos/OWNER/REPO/actions/caches/CACHE_ID
    

Alternatively, you can also use the GitHub CLI to interact with the API, using the gh-actions-cache extension.

Original Post (November 13, 2020)

As pointed out in the corresponding issue, two practical workarounds can be used to force the use of a new cache. This is not exactly the same as clearing the current cache (with regards to the cache usage limits), but it does the job.

In order to do so, you have to change the cache key (and any restore-keys). Because if the key(s) is/are different, this is considered a cache miss and you start with a new one.

You can change the cache key either by modifying the workflow file directly, e.g., by adding a version number:

key: ${{ runner.os }}-mycache-v1-${{ hashFiles(...) }}

If you now want to use a new cache, all you have to do is to commit a different version number:

key: ${{ runner.os }}-mycache-v2-${{ hashFiles(...) }}

If you don't want to modify the workflow file and prefer using the UI, you can abuse secrets:

key: ${{ runner.os }}-mycache-${{ secrets.CACHE_VERSION }}-${{ hashFiles(...) }}

Whenever the secret changes, a new cache will be used.

⚠️ WARNING: Secrets used for cache keys are "revealed" in the UI.

Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
beatngu13
  • 7,201
  • 6
  • 37
  • 66
  • It also seems like, if you re-run a different workflow on the same cache it get's updated. – red-isso Feb 07 '21 at 10:34
  • @red-isso what exactly do you mean? If two (or more) different workflows share the same cache? – beatngu13 Feb 07 '21 at 10:53
  • 2
    @beatngu13 that is a beautifully clever use of secrets for busting caches! Thank you! – James Apr 05 '21 at 16:19
  • Thanks for the answer. I guess yet another option would be using build parameters, via on/workflow_dispatch/CACHE_VERSION, so that one could set a new version when issuing a build manually. – zakmck Apr 06 '21 at 12:45
  • 1
    @zakmck basically yes, but when the build isn't triggered manually and the cache version input is empty (or defaults to a certain value), then the old/corrupt cache is used again; at least until it is actually evicted. So, one could use this approach to make a quick test if the cache causes build issues. If that is the case, one still has to change the key(s) permanently. – beatngu13 Apr 07 '21 at 17:02
  • Thanks again. I think an invalidation option or expiration time are really needed. – zakmck Apr 07 '21 at 17:51
  • According to [matching a cache key](https://docs.github.com/en/actions/advanced-guides/caching-dependencies-to-speed-up-workflows#matching-a-cache-key) you could include `${{ runners.os }}-mycache-` in `restore-keys`. – Mikael Dúi Bolinder Jan 21 '22 at 00:07
17

This can be done from GH Actions as well.

name: Clear cache

on:
  workflow_dispatch:

permissions:
  actions: write

jobs:
  clear-cache:
    runs-on: ubuntu-latest
    steps:
      - name: Clear cache
        uses: actions/github-script@v6
        with:
          script: |
            console.log("About to clear")
            const caches = await github.rest.actions.getActionsCacheList({
              owner: context.repo.owner,
              repo: context.repo.repo,
            })
            for (const cache of caches.data.actions_caches) {
              console.log(cache)
              github.rest.actions.deleteActionsCacheById({
                owner: context.repo.owner,
                repo: context.repo.repo,
                cache_id: cache.id,
              })
            }
            console.log("Clear completed")

Make sure it's merged to HEAD branch and trigger it manually from Actions or add your custom triggers.

lukee
  • 843
  • 9
  • 12
  • 1
    Thanks, it works. However, if I understand correctly, this workflow must be in the main branch to be visible in the "Actions" tab to be run the first time. – lorenzo-bettini Oct 27 '22 at 08:26
  • Yes, that's how `workflow_dispatch` works. Any other trigger can be used as well, but only `workflow_dispatch` gives ability to trigger manually. For manual cache management there is a native UI available now: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#managing-caches – lukee Oct 27 '22 at 10:37
  • How can I remove caches based on 'created_at' key? Basically, I'm trying to remove the cache if it was created last week. – Ozer Ozdal Feb 23 '23 at 23:59
  • @ÖzerÖzdal Add `if` in the `for` loop and filter by `cache.created_at`. See https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#list-github-actions-caches-for-a-repository for details. But why would you do that? GitHub will automatically remove them after 7 days of being unused: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy – lukee Mar 10 '23 at 09:51
  • @lukee yes, I did it by running an if and for loop over cache.created_at. Because I have a package that I generate caches on weekly basis for the following week. So I remove old unused caches from the previous week by running a workflow that runs on daily basis. – Ozer Ozdal Mar 11 '23 at 16:41
11

You cannot force a clear cache currently and it seems there is an open feature request for it at the moment https://github.com/actions/cache/issues/2. If I were you, I would post the request there as well so that they know that more people want the feature implemented.

A few things to note about the action:

There are not parameters in the action and not even in the toolkit package that this action is build on top of.

Getting deep into the toolkit code they use a cache api url to do all the good stuff. This means we don't even know if that api supports it with the assumption that we try to test it out and see what else it provides by hitting it directly. Here is the line for the api call for which the base url is taken from env ACTIONS_CACHE_URL

https://github.com/actions/toolkit/blob/c2bc747506bf562195a02bd4fdb1ff2a95d8b7ed/packages/cache/src/internal/cacheHttpClient.ts#L44

npm package as reference https://www.npmjs.com/package/@actions/cache

If we take a step back for a moment and go back to the github docs now that we have looked deep into the action/cache code and how it works,

Per the github docs https://docs.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows

There are two things to note,

Once you create a cache, you cannot change the contents of an existing 
cache but you can create a new cache with a new key.
GitHub will remove any cache entries that have not been accessed in over 7 days.
Edward Romero
  • 2,905
  • 1
  • 5
  • 17
11

Based @beatngu13 answer I've created Makefile target that remove all cache from Github Actions:

GH_REPO ?= ALLATRA-IT/ephyr  # replace with your `org/repo`

# Clear Github Acions usage cache.
#
# Need to install github cli first [https://cli.github.com/] and authorize
#
# Usage:
#   make gh.clear

gh.clear:
    gh api --paginate -H "Accept: application/vnd.github+json" \
        /repos/$(GH_REPO)/actions/caches \
        | for ID in `jq '.actions_caches[].id'`; \
          do echo "Deleting $$ID"; \
             gh api --method DELETE /repos/$(GH_REPO)/actions/caches/$$ID | echo; done

Need to install Github CLI and jq tool tools first. After install Github CLI you need to authorize with:

gh auth login
eirenikos
  • 2,296
  • 25
  • 25
  • 1
    Add `--paginate` to delete all caches, not just the first 30: `gh api --paginate -H ...` – Hugo Feb 12 '23 at 12:17
5

One more easy and straightforward way to remove Actions' cache - Purge cache GH Action.

This action allows the cache of GitHub Actions to be automatically purged based on the last usage and configured TTL (default - 7 days).

Example:

name: Clear Cache

on:
  schedule:
    - cron: '0 0 * * *' # Runs once a day (https://crontab.guru/once-a-day)
jobs:
  clear:
    name: Clear cache
    runs-on: ubuntu-latest
    steps:
    - uses: MyAlbum/purge-cache@v1
      with:
      max-age: 604800 # Cache max 7 days since last use (this is the default)

The workflow above will run once a day and remove all the cache that was last used more than 7 days ago.

Andrii Bodnar
  • 1,672
  • 2
  • 17
  • 24
2

@GegznaV you can use something like tmate, and manually clear cache by sshing into a runner.

Rushi patel
  • 522
  • 7
  • 17
1

For anyone looking for a simple commandline solution, the following shell command will also do it. It requires the Github CLI + JQ:

gh api -H 'Accept: application/vnd.github+json' /repos/realm/realm-kotlin/actions/caches --paginate | jq -r '.actions_caches | .[].key' | sed -e 's/|/%7c/g' -e 's/\[/%5b/g' -e 's/\]/%5d/g' | xargs -I {} sh -c 'gh api --method DELETE -H "Accept: application/vnd.github+json" /repos/<OWNER>/<REPO>/actions/caches?key={} --silent'

In this case, I'm using sed to URL encode special characters in the key. I just needed to handle |, [ and ]. Others can be added as needed. See e.g. https://gist.github.com/jaytaylor/5a90c49e0976aadfe0726a847ce58736#file-url_encode-sh-L11

Christian Melchior
  • 19,978
  • 5
  • 62
  • 53
1

Since you can use the GitHub CLI gh in GitHub Actions, you now (July 2023) have with gh v2.32.0 the new gh cache delete:

# Delete a cache by id
$ gh cache delete 1234

# Delete a cache by key
$ gh cache delete cache-key

# Delete a cache by id in a specific repo
$ gh cache delete 1234 --repo cli/cli

# Delete all caches
$ gh cache delete --all
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
0

Here's another automated solution similar to @beatngu13's cache key invalidation method. This one uses a generated timestamp file that gets committed and then used in a hashFiles(...) prefix to the cache key.

Included is a set of GNU Makefile targets to make using this method very easy: make clear-github-cache

Note: Make sure each Makefile "recipe" section is indented with 1 tab character! StackOverflow converts tabs to spaces in the code block.

.github/clear_github_actions_cache:
    date  +%s > .github/clear_github_actions_cache

.PHONY: clean-github-cache-file clear-github-cache
clear-github-cache: clean-github-cache-file .github/clear_github_actions_cache ## Force GitHub Actions Cache key to change for fresh CI run
    git add .github/clear_github_actions_cache
    git commit -m "Clear GitHub Actions Cache @ $$(cat .github/clear_github_actions_cache)"

clean-github-cache-file: ## Remove GitHub Actions Cache timestamp invalidator file.
    [ -f '.github/clear_github_actions_cache' ] && rm -f '.github/clear_github_actions_cache' || true

Then, inside the GitHub Actions workflow YAML, add a cache key prefix that uses the hash of this file:

      - name: Cache R packages
        if: runner.os != 'Windows'
        uses: actions/cache@v2
        with:
          path: ${{ env.R_LIBS_USER }}
          key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/clear_github_actions_cache') }}-${{ hashFiles('.github/R-version') }}-${{ hashFiles('.github/depends.Rds') }}
          restore-keys: |
            ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/clear_github_actions_cache') }}-${{ hashFiles('.github/R-version') }}-${{ hashFiles('.github/depends.Rds') }}
            ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/clear_github_actions_cache') }}-${{ hashFiles('.github/R-version') }}-
            ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/clear_github_actions_cache') }}-

Now just run:

make clear-github-cache

This generates and commits a file, .github/clear_github_actions_cache, with Unix timestamp as contents. The hashFiles(...) cache key prefix uses this and will generate a unique hash of this file as a prefix to the rest of the cache / restore-key.

Next time you use git push, the cache key will be invalidated... effectively running GitHub Actions with an empty cache.

TrinitronX
  • 4,959
  • 3
  • 39
  • 66
0

keep the last cache with this bash function (add it to your .bashrc file)

function gh_clear_old_caches(){

  GH_REPO='your_github_user/your_repository'
  echo "deleting all caches, except the last"
  gh api --paginate -H "Accept: application/vnd.github+json" \
    "/repos/${GH_REPO}/actions/caches" \
        | for ID in `jq '.actions_caches | sort_by(.id) | .[:-1][] | .id '`; \
        do echo "Deleting cache id ${ID}"; \
        gh api --method DELETE -H "Accept: application/vnd.github+json" "/repos/${GH_REPO}/actions/caches/${ID}" | echo; \
    done 
 }

name completion works with bash functions as well:

gh_<tab>

the addition in this one is that jq first sorts the result by id (assuming that the most recent one has the largest).

[:-1] means every id, except the last.

Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33