Vault "token is expired" / "permission denied" from an expired token in CI
The token CI is using has passed its TTL. Vault rejects expired tokens with "token is expired" (or a plain "permission denied" once the token is fully gone), so the read fails partway through a long job.
What this error means
A call that worked earlier in the job now fails with "Code: 403 ... * token is expired", often on a long-running or retried pipeline.
Error making API request.
URL: GET https://vault.example.com/v1/secret/data/ci/app
Code: 403. Errors:
* token is expiredCommon causes
The token TTL is shorter than the job
A login-issued token with a small token_ttl expires before a long build finishes, so later reads fail.
The token was not renewed and is not renewable
The pipeline held one token for the whole run without renewing it, and once past TTL it is invalid.
How to fix it
Raise the role token TTL
- Set a
token_ttlon the auth role that comfortably exceeds the longest job. - Keep a
token_max_ttlcap for safety. - Re-run so the freshly issued token lives long enough.
vault write auth/approle/role/ci \
token_ttl=30m token_max_ttl=1hFetch secrets once, early
Read all needed secrets right after login into masked env vars, so a later expiry cannot break the job.
- uses: hashicorp/vault-action@v3
with:
url: ${{ secrets.VAULT_ADDR }}
method: approle
roleId: ${{ secrets.VAULT_ROLE_ID }}
secretId: ${{ secrets.VAULT_SECRET_ID }}
secrets: secret/data/ci/app dbUrl | DB_URLHow to prevent it
- Size
token_ttlto exceed your slowest pipeline. - Fetch secrets once at the start rather than repeatedly late in the job.
- Do not depend on a single short-lived token surviving a long run.