---
title: "MGS1026: a cacheable target reaches for a credential"
description: Fires when a target body calls magus.secret.read or magus.secret.endpoint but the target can still replay from cache. A credential is not part of the cache key, so rotating or repointing it invalidates nothing and the target reports success without ever authenticating.
tags: [MGS1026, magusfile, secrets, cache, skip_cache, credentials, replay]
---

# MGS1026: a cacheable target reaches for a credential

A target reaches for a credential - `magus\secret.read` or `magus\secret.endpoint` - and
is still cacheable:

```text
[fail] cacheable secret reads: 1 target(s) read a credential but may replay from cache,
so a rotated or revoked credential invalidates nothing and the target reports success
without authenticating; declare skip_cache with a reason
    .: target "image-login" reads a secret and is cacheable (magusfile.buzz)
```

## Why this is a failure you would not otherwise see

A resolved credential contributes **nothing** to the [cache key](../../../concepts/cache.md).
That is deliberate: hashing one would write the credential into cache metadata and partition
your cache per rotation. The consequence is that rotating or revoking a credential invalidates
nothing.

For most targets that is harmless. For an authentication target it is not, and the
[`-login` convention](../../../concepts/secrets.md) magus recommends makes it _more_ likely
rather than less. Giving authentication its own small target is good practice for every other
reason - it keeps the privileged, interactive step one you asked for by name - but it also
means the target's sources almost never change. So it becomes a permanent cache hit that never
contacts the provider, never authenticates, and **reports success**.

The build does not fail there. It fails later, in the push or deploy that assumed the login
worked, with the registry's own 401 and nothing connecting it to the green step above it.

## The fix

Declare the target uncacheable, with the reason:

```buzz
magus\project({
    "targets": {
        "image-login": {"skip_cache": "authenticates to a registry per invocation; a replay would reuse stale credentials"},
    },
})
```

`skip_cache` takes a reason string rather than a boolean on purpose, and this is the case it
was written for: six months from now the reason is what tells a reader the target is
uncacheable by decision rather than by accident.

The same applies to any target whose output is a function of a credential - a signed artifact,
a fetch from a private registry, a deploy. The test is simple: **if revoking the credential
should change what the target produces, the target cannot be replayable.**

## When to ignore it

This is a warning, not a load error, because a target can legitimately read a credential and
still produce a cacheable artifact - fetching a dependency whose content is addressed by hash,
for instance, where the credential authorizes the fetch but does not determine the result. Only
the author knows. Declaring `skip_cache` with a reason is still the cheaper answer unless the
target is expensive and provably content-addressed.

## What this check cannot see

It is a static read of the target body, so it finds a **direct** `magus\secret.read` call. A
read behind a helper the static walk cannot reach is invisible to it, exactly like an unreached
`ctx.readsFiles` ([MGS1004](MGS1004.md)). It under-reports rather than over-reports, which is
the right direction for a check whose remedy is to opt a target out of the cache.

It also does not see `Command.secrets` on a spell op, which resolves at spawn rather than in a
target body.

## See also

- [Secrets](../../../concepts/secrets.md) - why a credential is not in the cache key
- [Cache](../../../concepts/cache.md) - what the key does hash
- [MGS1004](MGS1004.md) - the sibling check for footprint declarations the walk cannot reach

## An endpoint carries this in a sharper form

`magus\secret.endpoint` counts here too, and the hazard is worse than for a read rather
than milder.

With a read, the credential at least passes through the magusfile, so an author has some
chance of noticing the target never ran. With an endpoint the value never appears at all:
the file names a reference and a host, and magus attaches the credential on the way
upstream. So changing that reference - staging to production, a rotated vault path, a
different service account - alters **nothing the cache can see**. The sources are
identical, the target replays, and the output it replays was produced with the _other_
credential.

That is a wrong build rather than a stale login, which is why the fix is the same and the
reason to apply it is stronger:

```buzz
ctx.skip_cache("reaches for a credential, which contributes nothing to the cache key");
```
