Skip to content

Commit 80b2efd

Browse files
committed
feat: add ca-156 cache save silently fails with read-only token (pull_request_target)
1 parent c7cbe44 commit 80b2efd

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
id: ca-156
2+
title: 'actions/cache save silently fails with misleading ''Unable to reserve cache with key'' message when token has read-only cache access'
3+
category: caching-artifacts
4+
severity: silent-failure
5+
tags:
6+
- actions-cache
7+
- read-only
8+
- pull_request_target
9+
- fork
10+
- cache-write-denied
11+
- misleading-error
12+
- permissions
13+
patterns:
14+
- regex: 'Unable to reserve cache with key.*another job may be creating this cache'
15+
flags: 'i'
16+
- regex: 'cache write denied|cache.*read.only.*token|read.only.*cache.*token'
17+
flags: 'i'
18+
- regex: 'Warning: Unable to reserve cache with key'
19+
flags: 'i'
20+
error_messages:
21+
- 'Warning: Unable to reserve cache with key cache-key-123, another job may be creating this cache.'
22+
- 'Warning: cache write denied'
23+
root_cause: |
24+
`actions/cache` silently swallows the error when the GITHUB_TOKEN does not have write access to
25+
the Actions cache service. Instead of surfacing a meaningful error, the action logs a misleading
26+
warning: "Unable to reserve cache with key X, another job may be creating this cache." — implying
27+
a concurrency race when the real cause is a permission denial.
28+
29+
This silent failure occurs in two scenarios:
30+
31+
1. **Fork pull_request workflows** — the GITHUB_TOKEN for PRs from forks is scoped read-only by
32+
default. `actions/cache` save steps complete without error but the cache is never written.
33+
Subsequent runs pay full restore cost since no cache was saved.
34+
35+
2. **pull_request_target workflows — UPCOMING** — GitHub is extending read-only cache access to
36+
`pull_request_target` workflows (actions/cache#1768, June 2026). These workflows receive a
37+
write-scoped GITHUB_TOKEN for repository operations, but the cache layer will enforce a
38+
separate read-only cache token for security. Existing `pull_request_target` workflows that
39+
rely on cache saves will silently stop caching without any obvious error.
40+
41+
Prior to `actions/cache@v5.1.0`, the "Unable to reserve cache with key" message was the only
42+
signal — developers typically assume it means a race condition and add retries or `fail-on-cache-miss`,
43+
which does not help. Version 5.1.0 surfaces the correct "cache write denied" message to make the
44+
permission cause visible.
45+
fix: |
46+
1. **Upgrade to actions/cache@v5.1.0+** — this version emits a "cache write denied" warning
47+
instead of the misleading "another job may be creating this cache" message, making the root
48+
cause visible in the workflow log.
49+
50+
2. **For pull_request workflows from forks** — cache restores (reads) still work; only saves
51+
(writes) are denied. Use a two-workflow split: a `pull_request` workflow for tests that
52+
restores cache and uploads artifacts, and a `workflow_run` or `push` workflow that writes
53+
cache from the base branch.
54+
55+
3. **For pull_request_target** — if cache saves are required, use a PAT or GitHub App token
56+
scoped to cache write, or restructure so caches are populated from a `push` or `schedule`
57+
workflow on the base branch and only restored in `pull_request_target`.
58+
59+
4. **Do NOT diagnose as concurrency** — "Unable to reserve cache" is almost never a race between
60+
jobs in the same run. If you see it consistently on every run for the same key, suspect
61+
read-only token before investigating concurrency.
62+
fix_code:
63+
- language: yaml
64+
label: 'Upgrade actions/cache to v5.1.0+ for accurate cache-write-denied error message'
65+
code: |
66+
jobs:
67+
build:
68+
runs-on: ubuntu-latest
69+
steps:
70+
- uses: actions/checkout@v4
71+
72+
- name: Cache dependencies
73+
uses: actions/cache@v5 # v5.1.0+ emits "cache write denied" instead of misleading message
74+
with:
75+
path: ~/.npm
76+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
77+
restore-keys: |
78+
${{ runner.os }}-node-
79+
- language: yaml
80+
label: 'Two-workflow split — restore cache in pull_request, populate from push'
81+
code: |
82+
# Workflow 1: pr-ci.yml — runs on pull_request (cache restores work, saves are denied)
83+
on: pull_request
84+
85+
jobs:
86+
test:
87+
runs-on: ubuntu-latest
88+
steps:
89+
- uses: actions/checkout@v4
90+
91+
- name: Restore cache (read-only in fork PRs — saves will be denied silently)
92+
uses: actions/cache/restore@v5
93+
with:
94+
path: ~/.npm
95+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
96+
restore-keys: ${{ runner.os }}-node-
97+
98+
- name: Run tests
99+
run: npm test
100+
101+
---
102+
# Workflow 2: cache-prime.yml — runs on push to default branch (has cache write access)
103+
on:
104+
push:
105+
branches: [main]
106+
107+
jobs:
108+
prime-cache:
109+
runs-on: ubuntu-latest
110+
steps:
111+
- uses: actions/checkout@v4
112+
- name: Populate cache from base branch
113+
uses: actions/cache@v5
114+
with:
115+
path: ~/.npm
116+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
117+
- run: npm ci
118+
prevention:
119+
- 'Treat a recurring "Unable to reserve cache with key" warning on every run for the same key as a permission issue, not a concurrency race.'
120+
- 'Use actions/cache@v5.1.0+ which surfaces "cache write denied" for clear diagnosis.'
121+
- 'Know that pull_request workflows from forks have read-only cache access — cache restores work, saves do not.'
122+
- 'Expect pull_request_target workflows to also receive read-only cache tokens in a future platform update (June 2026+); populate caches from push/schedule workflows instead.'
123+
- 'Use actions/cache/restore@v5 explicitly in read-only contexts to make the intent clear and avoid confusing save failures.'
124+
docs:
125+
- url: 'https://github.com/actions/cache/pull/1768'
126+
label: 'actions/cache PR #1768: Bump @actions/cache to v5.1.0 — handle read-only cache access (June 18, 2026)'
127+
- url: 'https://github.com/actions/toolkit/pull/2435'
128+
label: 'actions/toolkit PR #2435: Handle cache write error due to read-only token'
129+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request'
130+
label: 'GitHub Docs: pull_request event — fork token restrictions'
131+
- url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target'
132+
label: 'GitHub Docs: pull_request_target event'

0 commit comments

Comments
 (0)