Skip to content

fix(health): STALE verdict mis-reported as UNKNOWN (exit 4) #198

Description

@Jammy2211

Overview

agents/conductors/health/health.sh has no STALE branch. When PyAutoHeart returns
its freshness tier the card correctly prints adopted verdict = stale with a score,
then the recommendation prints "UNKNOWN — could not obtain a verdict from the
vitals faculty"
, the triage counts read 0 blocker(s) · 0 real warning(s) · 0 expected first-run gap(s), and the script exits 4 (unknown).

PyAutoHeart/AGENTS.md and heart/readiness.py make STALE a first-class tier
(nothing known-bad; some evidence missing or expired; remedy = re-run the check),
and AUTONOMY.md leg 4 treats STALE as passing the dev-ship gate. So a machine
caller today cannot distinguish "Heart says STALE" from "Heart unreachable" — the
two conditions collapse onto exit 4 and the same UNKNOWN headline.

Reproduced on a live health run 2026-08-05.

Plan

  • Teach the triage item classifier to walk stale_reasons alongside red_reasons
    and yellow_reasons, so stale evidence becomes triage items instead of vanishing.
  • Give stale items their own kind, evidence-gap — actionable ("re-run the named
    check") and therefore distinct from the existing baseline-gap bucket, which is
    rendered as "accept, not action items".
  • Add a STALE branch to the recommendation chain that names the top evidence gap and
    cites a real refresh entry point, never an invented one.
  • Give STALE its own exit code so a caller can branch on it, and update the header's
    documented exit-code table to match.
  • Add contract tests under tests/ covering all three sites.
Detailed implementation plan

Affected Repositories

  • PyAutoBrain (primary, only code change)
  • PyAutoMind (registry/prompt lifecycle only)

PyAutoHeart is referenced (its readiness.py defines the tier and emits
stale_reasons) but needs no change — Heart already computes and exports the
tier correctly; this is purely a Brain-side consumer bug.

Branch Survey

Repository Current Branch Dirty?
./PyAutoBrain claude/health-conductor-stale-verdict-6ve1sl (== origin/main) clean
./PyAutoMind claude/health-conductor-stale-verdict-6ve1sl prompt file only

Branch: claude/health-conductor-stale-verdict-6ve1sl (mandated by the session;
supersedes the feature/<task-name> convention). No worktree claim conflicts —
active.md has no in-flight task touching PyAutoBrain.

Implementation Steps

  1. agents/conductors/health/health.sh_triage, item classifier (~line 187).
    Read stale = list(r.get("stale_reasons") or []) and iterate
    (("red", red), ("yellow", yellow), ("stale", stale)). Keep the existing
    red-severity override and add its stale counterpart: when severity == "stale",
    force kind = "evidence-gap" — severity wins over the keyword class, exactly as
    it already does for red. Add evidence-gap to KIND_RANK (rank 1: actionable,
    below real-problem, above advisory/baseline-gap) and "stale": 2 to
    SEV_RANK. blocks_green is true for an evidence gap: re-running it is the path
    to GREEN. Add an evidence_gaps count to the emitted counts object (additive —
    existing keys keep their meaning).

  2. agents/conductors/health/health.sh — recommendation chain (~line 234).
    Insert a STALE branch after warn_real and before the
    gaps or verdict == "yellow" branch, keyed on verdict == "stale" or evidence.
    Action refresh-evidence; headline names the top evidence gap; detail states the
    tier's semantics (re-run the check, do not fix code) and that the dev-ship gate
    treats STALE as passing while a release still requires GREEN. Command comes from a
    capability → refresh map of real entry points only:
    validatepyauto-brain release validate (delegated — the release conductor
    owns the MCP GitHub boundary), verify_installpyauto-heart verify_install,
    continuous-check capabilities (repo_state, version_skew, worktree_drift,
    script_timing, ci_status, open_prs, unknown) → pyauto-heart tick.
    Capabilities with no known refresh entry point (test_run, url_check) get
    command: None and are named in the detail — never an invented command, matching
    the file's existing "cite a fix only for a named failure class" discipline.
    Prefer the validate gap when present (it is the hard gate that makes GREEN
    reachable), else the top-ranked evidence gap.

  3. agents/conductors/health/health.sh_exit_code_for (~line 304).
    Add stale) return 6 ;;. Decision + rationale: the free slot 1 is rejected
    because the shell's own generic failure is 1 (a missing _common.sh, a failed
    readlink) and STALE is the tier AUTONOMY.md leg 4 treats as passing — a
    crash must never be readable as a passing verdict. This is the same reasoning the
    header already applies to usage errors ("exits 5 — kept distinct from the verdict
    codes so misuse is never read as a real YELLOW"). 6 sits outside the existing
    0–5 block, is never produced by bash or python in this script, and is unambiguous
    to a machine caller.

  4. agents/conductors/health/health.sh — header exit-code table (~line 52).
    Document 6 stale, with the one-line rationale for why it is not 1.

  5. _render_triage_human. Mark stale items ? (Heart's own tier glyph) rather
    than !; add an "Evidence gaps (re-run the named check — nothing is known-bad)"
    section and the evidence_gaps count to the counts line.

  6. tests/test_health_conductor.py (new). Hermetic contract tests that pipe a
    fabricated readiness JSON through the conductor's triage engine and assert:
    stale reasons become items; the recommendation is refresh-evidence (not
    refresh/UNKNOWN); counts are non-zero; the exit code is 6 and distinct from
    unknown's 4, green's 0, yellow's 2, red's 3, usage's 5; the header's documented
    table matches _exit_code_for; red/yellow/green behaviour is unchanged
    (regression guard); and a Heart with no stale_reasons key behaves exactly as
    before (the tier is additive).

Key Files

  • agents/conductors/health/health.sh — the conductor; all four change sites.
  • tests/test_health_conductor.py — new coverage.
  • PyAutoHeart/heart/readiness.py — reference for the tier's semantics (unchanged).
  • AUTONOMY.md leg 4 — why STALE must be machine-distinguishable (unchanged).

Testing

python3 -m pytest tests/test_health_conductor.py plus the existing suite, and a
manual conductor run against a stubbed stale-only readiness payload to confirm the
rendered card, the recommendation, and the exit code.

Original Prompt

Click to expand starting prompt
# Bug: the health conductor mis-reports a STALE verdict as UNKNOWN.

Type: bug
Target: PyAutoBrain
Repos:
- PyAutoBrain
- PyAutoHeart
Difficulty: small
Autonomy: supervised
Priority: high
Status: formalised

Bug: the health conductor mis-reports a STALE verdict as UNKNOWN. In PyAutoBrain
agents/conductors/health/health.sh the recommendation chain branches on green /
blockers / real-warnings / (gaps or yellow) and otherwise falls through to an UNKNOWN
branch, and _exit_code_for maps only green/yellow/red with a catch-all 4. When
PyAutoHeart returns STALE the card correctly prints 'adopted verdict = stale' with a
score, then the recommendation incorrectly prints 'UNKNOWN - could not obtain a verdict
from the vitals faculty' and the script exits 4. The triage item classifier only walks
the red and yellow reason lists, so stale reasons are dropped and the counts wrongly
read 0 blockers / 0 warnings / 0 gaps. PyAutoHeart AGENTS.md makes STALE a first-class
freshness tier that the dev-ship gate treats as passing, so a caller cannot distinguish
'Heart says STALE' from 'Heart unreachable'. Reproduced on a live health run 2026-08-05.

<!-- formalised by the Intake (Conception) Agent on 2026-08-05 from user-intake -->

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions