Conversation
…ng-import violations
Two instances of the same class: a check that silently never runs.
1. tools/toolchain_health.py imported `StateReadAPI` from state_store.read_api,
but the class there is `ReadAPI`. The import raised on every single run and
fell into the `= None` fallback, so BOTH heartbeat checks reported
"unavailable" instead of being evaluated -- dead since the day it was
written. It also never put the repo root on sys.path, so `state_store` was
invisible when run by file path, and it called the facade with no args when
ReadAPI requires a state_dir. Fixed all three; the facade is now built with
get_state_dir() (AESOP_STATE_ROOT, else ./state).
2. `python tools/sibling_import_check.py --check` was RED on main with 3
violations, all in tools/merge_queue.py. Its guard used the conditional
`if str(d) not in sys.path:` form, which guards identically at runtime but
is invisible to the checker (it only recognizes a top-level
sys.path.insert statement). Switched to the repo's sanctioned unconditional
idiom -- same form as auto_merge.py and tracker_autoclose.py. The checker
is unchanged: not weakened, not exempted.
Tests (TDD, both proven RED against the pre-fix code):
- TestStateAPIImportIsLive: every symbol imported from state_store.read_api
must exist in it, the module global must not be the None fallback, and
run_checks must hand check_heartbeat a non-None state_api (the check is
live, not skipped). 4 tests, all 4 fail on the old import.
- TestRepoToolsTreeIsClean: the real tools/ tree scans clean, and
merge_queue.py's transport imports read as guarded. 2 tests, both fail
against origin/main's merge_queue.py.
- setUpModule pins AESOP_STATE_ROOT to a temp dir so building a real ReadAPI
cannot mkdir ./state in the caller's cwd.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two findings from the #746 lane, both in the same class: a check that silently never runs.
Finding 1 -- the toolchain health check was dead from the day it was written
tools/toolchain_health.pyimportedStateReadAPIfromstate_store.read_api. Thatsymbol has never existed -- the class is
ReadAPI. So the import raised on everyinvocation and fell into the
except ImportError: ... = Nonefallback, andcheck_heartbeat()returned early with"Heartbeat <name> check unavailable (StateAPI not loaded)". Both heartbeat checks have been permanently reported-unavailable rather thanevaluated for the entire life of the file.
Fixing the symbol exposed two more defects behind it:
state_storewas not importable. Run by file path (python tools/toolchain_health.py),sys.path[0]istools/, sostate_storeis invisible regardless of the symbol name.The tool now puts the repo root on
sys.path(the same idiomtracker_autoclose.pyand
gen_state_md.pyuse).ReadAPI.__init__(self, state_dir)requires the state directory; the call site was
StateReadAPI(). It now passesget_state_dir()(AESOP_STATE_ROOT, else./state) -- the repo-wide convention.With the import actually working, what does it report?
It reports real problems that were hidden. Not a regression -- a finding.
All 5 binary checks (
bash,git,python,node,curl) pass. Both heartbeat checksfail, and the failures are genuine:
aesop/state/.watchdog-heartbeatcontains epoch1784681587-- 2026-07-21, roughly12 days stale against a 300s threshold.
aesop/state/.monitor-heartbeatdoes not exist at all.The live heartbeat is in a different state root (
conductor3/state/.watchdog-heartbeat,fresh), which is exactly the kind of split the tool exists to notice. Relative to aesop's
own
AESOP_STATE_ROOT, the daemons look dead -- and until this PR nothing could say so.These findings are deliberately NOT silenced.
toolchain_health.pyis not wired intoany CI workflow, the pre-push hook,
package.json, ordaemons/(verified), so itsnon-zero exit blocks nothing. Deciding whether the fix is "point aesop's state root at the
live heartbeats" or "the daemons should also beat into the repo state dir" is a follow-up
that belongs to whoever owns the daemon topology, not to this lane.
Finding 2 --
sibling_import_checkwas red on mainmerge_queue.pywas already guarded -- but with the conditional form:That guards identically at runtime, but
SiblingImportAnalyzer.visit_Moduleonly scanstop-level statements for a bare
ast.Exprsys.path.insert(...)call, so a guard nestedinside an
ifis invisible to it. Switched to the repo's sanctioned unconditional idiom --the same form as
auto_merge.py:29andtracker_autoclose.py:53. A module body executesonce, so an unconditional insert cannot accumulate duplicate entries.
The checker itself is untouched: not weakened, not exempted, no suppression comment.
The diff is one hunk in the import block only -- no
REGENERATORSor logic changes, so itdoes not collide with the concurrent
guard/register-index-generatedlane.Tests (TDD -- both sets proven RED against the pre-fix code)
tests/test_toolchain_health.py::TestStateAPIImportIsLive(4 new tests):state_store.read_apimust actually exist in that module(AST-parsed from the source, so any future rename is caught the same way)
Nonefallbackrun_checksmust handcheck_heartbeata non-Nonestate_api-- i.e. the check islive, not skipped
check_heartbeat_freshmethod the check callsAll 4 fail against the original file:
tests/test_sibling_import_check.py::TestRepoToolsTreeIsClean(2 new tests) -- the realtools/tree scans clean, andmerge_queue.py's transport imports read as guarded. Bothfail against
origin/main'smerge_queue.py:A
setUpModulepinsAESOP_STATE_ROOTto a temp dir so building a realReadAPI(whichmkdirs its state directory) can never create
./statein the caller's cwd -- test hygiene.Verification
tools/sibling_import_check.py --check-- exit 0 (was 1)tools/toolchain_health.py-- exit 1 with the two real heartbeat findings above (was avacuous "unavailable" on a dead import)
OK (skipped=3)npm run test:node): greentest_toolchain_health+test_sibling_import_check+test_merge_queue=172 passed (was 166 -- 6 new)
secret_scan --staged,sibling_import_check,claudemd_lint,claudemd_sync_gate,encoding_lint,watcher_linter,spec_contract_validator,workflow_model_linter,import_cycle_check,verify_test_suite_count,claudemd_contract,metrics_gate,agent_prompt_hygienedispatch_lint,subprocess_guard,commit_lint,file_size_lint,docstring_checkare non-zero on
origin/maintoo (verified against a clean detached checkout) --pre-existing, untouched by this diff
A third instance of the same class, reported not fixed
tools/import_resolution_check.pyreportsmerge_queue.py's three bare sibling imports asunresolvable -- identically before and after this PR (reproduced on a clean
origin/maincheckout with
merge_queue.pystaged), since the diff neither adds nor removes an import.It is wired into
hooks/pre-push-policy.shas fail-closed, but it readsgit diff --cached --name-only-- the staging index, which is empty at push time:So a fail-closed pre-push gate is a no-op on every normal push. That is the same failure
mode as findings 1 and 2 and deserves its own lane; fixing it here would mean changing a
gate's rules, which is outside this lane's ownership.
Generated with Claude Code