Skip to content

Diagnostics

Two complementary read-only commands: check diagnoses problems with CI-safe exit codes; status is the always-exit-0 state dashboard.

check

Diagnose environment problems and suggest one actionable remediation per failure. Replaces the v1.x pyve doctor (diagnostics) and pyve validate (CI-safe exit codes) commands.

Usage:

pyve check [--fix [--yes]]

What it checks:

  • pyve.toml presence and schema validity (the manifest is the declaration)
  • Backend configured and implementation available (micromamba binary, when applicable)
  • Env runnability, not just existence — a per-env canary executes a console-script wrapper (bin/pip --version) in the root env and every declared + materialized named env, and classifies what it finds (see the verdict vocabulary below). A dangling python symlink or a relocated env with dead shebangs can no longer report green.
  • Manifest↔disk orphans — a materialized .pyve/envs/<name>/ tree with no [env.<name>] declaration, or a tree materialized under a root declared with a non-materializable backend, is flagged as a contradiction (the manifest is canonical)
  • Python version matches the pinned source-of-truth (.tool-versions / .python-version)
  • Venv path sanity (warns if project was relocated after creation)
  • .envrc / .env presence
  • conda-lock.yml presence and freshness (micromamba only)
  • Duplicate .dist-info directories in site-packages (micromamba only)
  • iCloud-Drive collision artifacts (macOS, micromamba only)
  • conda/pip native-library conflicts (micromamba only)
  • Env-spec drift — a non-empty diff between the planned env spec (§4 of docs/specs/env-dependencies.md, path read from .project-guide.yml's env_spec_path) and pyve.toml warns with a "run pyve env sync to reconcile" hint (exit stays 0)
  • An info-only [defaults] section reports when Pyve's built-in defaults changed since the project was created — reported, never applied retroactively
  • Resolution reasoning ([resolution]) — where each managed command (python, pip, project-guide) actually resolves from and why (see below)
  • Update availability ([pyve]) — info-only staleness hints for the hosted tools and pyve itself (see below)

Runnability verdicts (the canary)

Health used to be an existence test — and a broken env passes those: a relocated env keeps a valid bin/python symlink while every console script carries a dead baked-in shebang, so python -c 'import pytest' succeeds while pytest itself fails with bad interpreter. The canary executes a wrapper (the artifact that actually breaks) and classifies the result:

Verdict Meaning Repair (role-correct)
runnable wrapper executed and answered
dead shebang console scripts point at a deleted/relocated interpreter root → pyve init --force; named env → pyve env init <name> --force
dangling symlink the env's python symlink target is gone same rebuild verbs
missing interpreter no python in the env at all same rebuild verbs
orphan / contradiction materialized but not declared (or declared non-materializable, yet materialized) remove the tree
(silent) not materialized, or a declarative-only (none/advisory) backend not findings — legitimate states

Every broken verdict prints with the rebuild verb for its role — a broken root env always routes to pyve init --force (the pyve env namespace is selection-only for root), a broken named env to its own pyve env init <name> --force.

Resolution reasoning ([resolution])

For each managed command, one line names the winning PATH slot (project env, ~/.local/bin, version-manager shim, system PATH) and its probed version; a finding line appears only when something is wrong, carrying a bracketed machine class: [venv-pin-drift] (the env was created on a different interpreter than the current pin), [no-version-set] (a version-manager shim rejects the command under the active pin), [broken-winner], [not-found]. Run with --verbose for the full slot-by-slot PATH trace (every provider, winner marked, shadowed entries named).

Worked example — the incident this feature automates. A developer's .venv was created on Python 3.14.4; .tool-versions later moved to 3.12.13; project-guide wasn't installed under the pin. Untangling that by hand took a four-layer PATH/pin/shim trace. check now narrates it unprompted:

[resolution]
python → /Users/dev/proj/.venv/bin/python (project env, 3.14.4)
  ⚠ shadows the version-manager pin (3.12.13): the env was created on a
    different interpreter than the current pin [venv-pin-drift]
  → Rebuild toward the pin: pyve init --force
pip → /Users/dev/proj/.venv/bin/pip (project env)
project-guide → /Users/dev/.asdf/shims/project-guide (version-manager shim)
  ⚠ the version-manager shim rejects it under the active pin
    ("No version is set") [no-version-set]
  → Run: pyve self provision   (hosts project-guide outside the pin)

Resolution findings are diagnostic narrative: they contribute at most a warning and never flip the exit code to an error.

Reachability ([pyve])

Hosting health has three rungs, and passing the first two is not enough:

Rung Question Failure it catches
exists is the artifact there? never provisioned
runnable does executing it work? dead shebang, dangling symlink, deleted interpreter
reachable can your shell invoke it by name? hosted correctly, but ~/.local/bin is not on PATH

pyve hosts project-guide in its toolchain venv and shims it into ~/.local/bin. If that directory is not on your PATH, the tool is provisioned, runnable, and still uninvocablecheck used to report provisioned and be entirely correct while project-guide did not work at all. It now names it:

project-guide hosting: provisioned
  ⚠ hosted but NOT reachable from your shell — ~/.local/bin is not on PATH
  → Run: pyve check --fix   (adds it to /Users/dev/.zprofile)

A second reachability failure is a different copy winning on PATH:

project-guide hosting: provisioned
  ⚠ 'project-guide' resolves to /usr/local/bin/project-guide, not the pyve shim
  → A non-pyve copy earlier on PATH shadows the hosted one.

check --fix repairs the first case by appending the PATH entry to your login profile (~/.zprofile / ~/.bash_profile) — additive and reversible, and offered through the same plan-then-confirm gate as every other repair. The shadowing case is reported, never "fixed": which copy you want on PATH is your decision. Set PYVE_NO_PATH_WIRING=1 to stop pyve writing the entry at all.

The machine-readable form is pyve self provision --status --json, which carries project_guide.reachable as reachable / unreachable / shadowed. The 0 / 1 / 2 exit codes are unchanged — reachability is additive payload, because that contract is pinned by other tools (project-guide ≥ 2.15.0 gates its local-install warning on it). A consumer that wants reachability opts in by reading the field.

Update hints ([pyve])

The [pyve] section (toolchain + project-guide hosting state) also surfaces info-only update hints — pyve X.Y.Z is available (installed: …) with the upgrade command for your install source (brew upgrade pointmatic/tap/pyve vs. git pull && pyve self install; project-guide → pyve self provision). This is check's only network touch and it is strictly bounded: results cache for 24h, the probe runs only for interactive human runs (CI, piped output, and --offline / PYVE_NO_NETWORK=1 all suppress it), and a network failure degrades silently — the exit code can never depend on the network.

Exit codes (the composed contract, v3.0+):

  • 0 — all checks passed, or warnings only (environment works but is drifting; the advisory text is still printed)
  • 2 — one or more errors (environment is broken for pyve run / pyve test)

Safe for CI use.

check --fix — self-healing

Check detects; --fix repairs. The fault classes it consumes are exactly the runnability verdicts, resolution findings, and orphan contradictions documented above — pyve check --fix runs the same diagnostics, then detects broken Pyve-managed state and repairs it. It is plan-then-confirm: the detected faults and intended repairs are always printed first, and nothing is repaired without assent (--yes, or an interactive confirmation). Two tiers:

  • Non-destructive (hosting) — Pyve-owned, deterministically rebuildable state: a toolchain venv or hosted project-guide that exists but cannot run (dead interpreter symlink, dead shebang), a dangling ~/.local/bin/project-guide shim. Repaired via the self provision machinery / a shim re-link. Applied on batch assent.
  • Destructive (project envs) — repairs that destroy a materialized tree before recreating it: a broken root env (pyve init --force), a broken named env (pyve env init <name> --force), a root venv whose interpreter drifted off the declared pin (rebuild toward the pin), an orphaned tree the manifest doesn't declare (removed — the manifest is canonical). Each is individually confirmed in an interactive run; --yes assents to those prompts too.

Ground rules:

  • A non-interactive run never applies destructive repairs — even with --yes they are reported and skipped, so pyve check --fix --yes is safe in CI (you get the non-destructive tier only).
  • Without --yes, a non-interactive run is fully report-only; an interactive run prompts.
  • Declining one repair skips only that repair.
  • Idempotent: the plan is recomputed from live probes, so a re-run after success reports "Nothing to heal." and a re-run after a partial failure retries only what is still broken.
  • The exit code reflects the pre-repair diagnostics; a healed system goes green on the next run. What was never provisioned, healthy-but-stale, or project-managed is not a fault — heal repairs broken state, it never installs-from-nothing or upgrades.

Example output:

Pyve Environment Check
======================

✓ Configuration: pyve.toml
✓ Backend: venv
✗ Virtual environment: .venv (missing)
  → Run: pyve init --force
⚠ .env: missing
  → touch .env

1 error, 1 warning, 2 passed

Every failure points at exactly one command — no chains, no cross-references.

Legacy forms removed in v2.0. pyve doctor and pyve validate now error out with a migration message pointing at pyve check. Update CI scripts that grep for "Pyve Installation Validation" to match Pyve Environment Check or use pyve status for state snapshots.


status

Read-only project-state dashboard. Companion to pyve check: state here, diagnostics there.

Usage:

pyve status

Output sections:

  • Project — path, backend, declaration (pyve.toml), configured Python
  • Environment — path, Python, package count, backend-specific rows (environment.yml + lock status for micromamba)
  • Integrations — direnv, .env, project-guide, testenv

Exit code: always 0 unless pyve itself errors (e.g., unreadable config). Never signals problems via non-zero exit — for that contract use pyve check.

Example output:

Pyve project status
───────────────────

Project
  Path:           /Users/foo/Developer/bar
  Backend:        venv
  Declaration:    pyve.toml
  Python:         3.14.4 (.tool-versions via asdf)

Environment
  Path:           .venv
  Python:         3.14.4
  Packages:       127 installed

Integrations
  direnv:         .envrc present
  .env:           present
  testenv:        present, pytest installed

[project-guide]
  pyve-hosted (toolchain) v2.15.1

(project-guide reports in its own [project-guide] section — one readout naming how it is present: pyve-hosted (toolchain), local pip, or not installed, with the runnability-probed version.)

See also

  • Project Lifecycle — the remediation verbs check points at (init --force, update, upgrade)
  • Environments — the per-env rebuild verb (pyve env init <name> --force) and deliberately-isolated test envs
  • Testing — the silent-skip advisory and its declarative isolated = true opt-out
  • CI/CD Integration — using the exit-code contract in pipelines
  • Usage Guide — command overview, universal flags, and the Environment Variables table (PYVE_NO_NETWORK)