---
title: "MGS1037: a tool's observation is keyed as its version"
description: Fires when a spell declares one command as both a tool's version probe and its observation probe without a VersionKey. The two probes reach different targets, so the feed's identity ends up keying targets that never run the tool.
tags: [MGS1037, magusfile, cache, observations, versions, scanners, spells]
---

# MGS1037: a tool's observation is keyed as its version

A spell declares the same command as a tool's `probe` and its `observe`, and declares no
`key` to narrow the version half:

```text
[fail] observation keyed as version: 1 tool(s) declare one command as both version and
observation probe without narrowing the version half, so a value moving on the feed's
clock invalidates targets that never run the tool
    spell "go" tool "govulncheck" declares `govulncheck -version` as both probe and
    observe with no key, so everything that command prints keys every target in every
    project binding the spell, not just the targets driving the tool
```

## The two probes differ in reach, not in what they run

That is the whole of it, and it is easy to miss because the command can be identical.

An **observation** is scoped. It reaches the targets whose ops drive the binary, worked
out from the same static op list `magus describe target` prints. A target that never
composes the op carries no `obs:` line.

A **version probe** is not scoped. It runs for every project that binds the spell and
lands in the key of every target in that project, because a toolchain upgrade changes
what every target produces. That is correct for a version and wrong for anything else.

Declare one command as both and narrow neither, and the output's observation-class half
travels down the unscoped channel. Whatever moves on the feed's clock then invalidates
targets that never run the tool.

## What it cost when it happened

The go spell declared `govulncheck -version` as both. That command prints four lines, and
the last is the vulnerability database's publication date:

```text
Go: go1.26.6
Scanner: govulncheck@v1.3.0
DB: https://vuln.go.dev
DB updated: 2026-09-16 18:00:43 +0000 UTC
```

With no `key`, the whole output keyed the cache, so `build` in a project that never
scanned anything carried the database's publication date. Every release of that database
invalidated every build, test and lint entry in every Go project. The scoped `observe`
probe was already declared and already correct; the version probe was a second path
around it.

## Resolve it

Which of the two applies depends on whether the command can report the tool's own version
separately from the feed's.

**Drop the version probe, keep the observation.** Right when it cannot.
`govulncheck -version` prints the Go version first, so extracting a semver takes the
wrong tool's version and throws the database date away. The observation still carries all
four lines to the targets that drive the op, so nothing that should invalidate stops
invalidating. Check first whether the tool declares `supported` bounds: dropping the
probe drops the version window with it, and a tool with no bounds was feeding no gate.

**Declare a `VersionKey` that extracts the version alone.** Right when it can. A genuine
tool upgrade then keys every target, as it should, while the feed keys only its drivers.

Keying on a command's whole output stays legal. The zero `VersionKey` is deliberately
conservative, because it is the only setting that cannot silently discard something the
tool considers part of its identity. This check does not forbid that; it requires that
you meant it, in the one case where conservative is the wrong default.

## Why it is declared, not measured

The hazard is visible in the spell's tool table, so nothing has to run to find it. That
also means the check cannot tell you whether a probe's output actually contains something
that moves. It reports the shape that makes the leak possible, and the two fixes above
are how an author says which they intended.

## See also

- [MGS1033](MGS1033.md) names the other half of this pairing: a cacheable target
  composing a `reads-external` op with no probe and no `skip_cache`.
- [Cache](../../../concepts/cache.md) on observations versus versions, and the invariant a
  probed observation has to satisfy.
