Search guides, workflows, and reference pages.

Docs/tools

setup-preflight

Capability: substrate:setup

Harness: agnostic

Resolve a project’s Magpie setup state, and one skill’s fingerprint, into a machine-readable verdict.

Every framework skill has to answer the same question before it runs: is this project set up for the framework version now installed? That question used to be answered by prose the agent re-read on every invocation of every skill. Most of it was not judgement at all — read a lock, compare two hashes, order two versions, subtract two dates — so it is answered here instead, once, deterministically, and testably.

What stays prose is what a model is actually for: which proposal to make, how to word it, and the prohibitions. Those live in each skill’s preflight-detail.md, and a finding names the section that applies.

Prerequisites

  • Runtime — Python 3.11+. Standard library only, deliberately: the module is copied into an adopter’s gitignored .apache-magpie-local/ and run with bare python3, where nothing else is available.
  • CLIs — none required. The harness CLI (claude plugin list --json) is consulted when present and its absence is a supported state, not an error.
  • Credentials / auth — none.
  • Network — none. Every input is a file in the project.
  • Optional — --plugin-list accepts a listing the caller already read, so the tool never has to shell out.

Why it is installed into the project

The obvious home is the plugin, and it does not work. Under the sandbox the framework itself recommends, ~/.claude/plugins/cache/ is read-denied, so a script shipped in the plugin can be read by the agent’s file tool but never executed by a shell — and a sandboxed marketplace install is exactly the environment the reconciliation check was written for. So /magpie-setup config copies this module into .apache-magpie-local/, beside the configuration it already writes, and /magpie-setup upgrade refreshes that copy against the framework version now installed. It is removed when that directory is — uninstall deliberately preserves .apache-magpie-local/, personal configuration included, so it does not delete the checker either.

That has a consequence worth stating plainly: pre-flight may run config unattended, so an unattended run can place an executable in the checkout. It is framework code of the same provenance as the plugin the adopter installed, it is gitignored, and it goes away with the directory — but it is a step beyond writing configuration files.

Invocation

python3 .apache-magpie-local/setup_preflight/cli.py \
    --skill magpie-pr-management-triage \
    --hash sha256:9f1c4e… \
    --requires pr-management-config.md

In the framework checkout, the same thing through the workspace:

uv run --directory tools/setup-preflight --project . setup-preflight --skill … --hash …

Output

{ "verdict": "ok" }

…or a list of findings, each naming its scope and the preflight-detail.md section whose rules apply:

{
  "verdict": "action",
  "findings": [
    { "scope": "project", "code": "below-floor",       "section": "step-3", "facts": {…} },
    { "scope": "skill",   "code": "fingerprint-moved", "section": "step-4", "facts": {…} }
  ]
}

Exit status is 0 whenever a verdict was reached, findings included. A finding is the answer, not a failure. A non-zero exit means the check could not run, and the caller falls back to the detail file rather than assuming the project is fine.

The scopes

project findings are true of the checkout and identical for every skill invoked in it — the lock, snapshot drift, the marketplace floor. They are memoised in .apache-magpie-local/.preflight-cache.json, keyed on the lock files’ identity and the plugin listing and expiring after 15 minutes, so the second and later skills in a session pay only for their own fingerprint comparison.

skill findings differ per skill: its fingerprint against the reconciliation stamp, and whether its requires_config: entries resolve.

machine findings are about the isolated (secure agent) setup on this machine, reported only where it is used: isolated-setup-changed when the fingerprint of the secure-setup files differs from the one the last setup-isolated-setup-update run recorded, and isolated-setup-update-due when that run or its last reminder is older than isolated_setup_update_interval_days (default 7). The fingerprint is computed from the framework source when the checkout has it, and otherwise read from the generated isolated_fingerprint.py, which the isolated-setup-fingerprint prek hook keeps current. python3 -m setup_preflight.isolated record-update | record-reminder writes the stamp.

end-of-run is the periodic /magpie-setup verify suggestion, which is settled when the run finishes rather than before it starts.

Two rules that are the reason this is code

Unknown is never absent. A plugin listing that could not be read is None, not {}. Inside a sandboxed session the plugin cache is read-denied and claude plugin list --json prints [], which reads exactly like “nothing installed”; acting on it would propose installing a project’s entire floor on every sandboxed run. An empty parsed listing is treated as unknown for the same reason — it is indistinguishable from the denied case.

A dev build is a version like any other. Nothing strips or rounds a .devN segment. 0.2.0.dev202609110041 is below 0.2.0, and 0.10.0 is above 0.9.0 — the two orderings a string comparison gets wrong in opposite directions.

Tests

uv run --directory tools/setup-preflight --project . python -m pytest

Each test is named for the rule it pins. A failure is a change to what every skill does before it runs, not merely a refactor.

Suggest a change