Skip to content

bug: HAVE_* is a band-00 snapshot of a PATH that keeps changing — core-doctor's does not mean "Core wired this" #545

Description

@Gerrrt

Follow-up to #425, which fixed the population (the user bindirs now join PATH before detection) but not the structure.

The residual bug

zsh/00-tools.zsh computes all 43 HAVE_* flags at ~line 125, against the PATH as it stands at load-order band 00. PATH is not final there and never will be:

contributor when can it move before detection?
mise activate zsh (00-tools.zsh:378) registers a runtime chpwd/precmd hook no — it rewrites PATH per directory, forever
80-os.zsh band 80 no — bands ≥70 load after Core by contract
85-*.zsh role band 85 no
99-local.zsh band 99 no — that is the user's escape hatch

So a tool contributed by any of those still gets no flag, no alias and no shell init, while core-doctor — which probes live via _core_have (zsh/05-ui.zsh:53) against the finished PATH — reports it . The disagreement is not the bug; the bug is that it is silent, and that a reads as "Core wired this" when it only ever meant "this is on PATH right now".

zsh/00-tools.zsh already carries the standing note: "#425 is the reminder that a HAVE_* flag and the doctor disagreeing about the same box is itself a bug."

Options considered

Re-probe late and re-wire — rejected. There is no "after PATH is final" (mise's chpwd hook moves the target on every cd), and the wiring is not idempotent: re-sourcing atuin's init double-registers _atuin_precmd, 40-bindings.zsh has already bound Ctrl+E, and re-sourcing 20-aliases.zsh would silently clobber deliberate overrides from an 85-* role fragment. It also needs _have, which is unfunction'd at the end of 00-tools.zsh.

Snapshot-only warning — too coarse alone. PATH differs after band 00 on essentially every box (Homebrew, mise shims, 99-local.zsh), so a bare "PATH changed" warning fires on 100% of healthy shells and names no tool.

Doctor reports the divergence — recommended. Keep the live probe (deliberate: it is honest even if 00-tools.zsh never ran), and add a second axis: for each row, does the HAVE_* flag Core would have set actually hold a value? Present-but-unset → "installed, but Core did not wire it". The mirror case is also worth having: flag set but binary now gone → a live alias that will fail.

Sketch

  • 00-tools.zsh records typeset -g _CORE_PROBE_PATH=$PATH after the flag block — one assignment, no fork, and it doubles as the sentinel for "detection ran in this shell" so the unit harness and scripts are not told all 41 tools are unwired.
  • core-doctor derives the tool→flag map by parsing 00-tools.zsh's own probe lines (^_have <tool> && HAVE_<X>=1) rather than keeping a table. That is what gets the irregulars right without maintenance — ast-grepHAVE_ASTGREP but git-absorbHAVE_GIT_ABSORB, two opposite conventions three lines apart. 38 of the 41 inventory entries match; fd/bat are supplied where _core_doctor_bin already special-cases them, and op correctly maps to nothing (it self-gates in 50-op.zsh). scripts/test-core.sh already parses this file with the same ^-anchor, so the convention is a de-facto pinned interface.
  • Render as a marker on the row plus a not wired block — using , not /, keeps the render⇄JSON set-equality test blind to it by construction. The hint names the directory that joined late (set difference of live PATH against _CORE_PROBE_PATH), which is what makes it actionable.
  • --json gains "wiring":{"probed":bool,"unwired":[],"stale":[]}.

This also lets scripts/test-core.sh's probed ⊆ reported guard be closed in the reverse direction — reported ⊆ mapped ∪ {op} — which its comment currently has to decline.

Cost: one parameter assignment at startup. All the work lands in core-doctor, a one-off command that already forks for git --exec-path.

Split out of #425; the PATH half is fixed in #544.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions