MOO-72 Commit 5: dependency and runtime health checks - #15
Conversation
Adds Node runtime, cache storage, Python runtime, GitHub reachability/
credential validity, and Python tree-sitter grammar checks to /readyz,
alongside the existing pyan3/CodeVisualizer checks -- normalized to one
{ok, detail, version, checkedAt} status shape and periodically
re-verified (folded into the existing 5-minute metrics interval, with a
reentrancy guard) where the underlying dependency actually supports it.
readiness-gating checks (buildOutput, workspaceRoot, cacheStorage,
nodeRuntime) determine the 200/503 status; feature-specific checks
(pyan3, pythonRuntime, codeVisualizer, pythonTreeSitter, githubReachable)
are reported but never take the whole service out of rotation.
Sensitive detail/version fields are only exposed to an authenticated
caller (the existing AUTH_TOKEN), never publicly.
The cache-storage check runs against a dedicated, isolated GraphCache
instance -- never the live, request-serving cache, which a naive
round-trip self-test would otherwise perturb (LRU eviction, capacity,
key-collision risk) on every /readyz hit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
OwenTanzer
left a comment
There was a problem hiding this comment.
One runtime-health mismatch to address.
| if (!match) { | ||
| return { ok: false, version: null, detail: `could not parse a version from output: ${JSON.stringify(stdout || stderr)}` }; | ||
| } | ||
| return { ok: true, version: match[1], detail: null }; |
There was a problem hiding this comment.
[P2] Reject non-Python-3 runtimes
This reports any parseable Python X.Y.Z output as healthy, so an operator-configured Python 2 executable returns pythonRuntime.ok: true. The adapter's existing contract requires a valid Python 3 interpreter (and the setup scripts prefer python3), so /readyz can give a misleading runtime-health signal while analysis cannot run. Please require major version 3 here and cover the Python 2 output case.
There was a problem hiding this comment.
Fixed. verifyPythonRuntime's parse/gate logic is extracted into a pure evaluatePythonVersionOutput({stdout,stderr}) helper (server/lib/pyan3Adapter.js) that now checks the parsed major version is exactly 3, rejecting Python 2 (and any other major) with an actionable detail ("found Python X.Y.Z, but a Python 3 interpreter is required") while still reporting the detected version so an operator can see what's actually installed. Verified with real Python 2 and Python 3 --version output shapes (stderr for 2.x, stdout for 3.x, matching the real interpreters' actual behavior) rather than trying to spawn a fake Python 2 binary -- a .bat-based stand-in turned out to be unreliable on Windows (execFile rejects it without shell:true, which this codebase deliberately never sets for security). Full suite: 658/658.
…check verifyPythonRuntime accepted any parseable "Python X.Y.Z" output, so an operator-configured Python 2 interpreter (which prints its version to stderr in the identical shape) was reported ok:true even though pyan3/the whole adapter require Python 3 -- a misleading /readyz signal (healthy runtime, broken analysis). Extracted the parse/gate logic into a pure evaluatePythonVersionOutput() helper, now checking the major version is 3, and covered it directly with real Python 2 and Python 3 --version output shapes -- avoids an unreliable fake-executable workaround (Windows' execFile rejects a .bat stand-in without shell:true, which this codebase deliberately never sets). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
/readyzgainscacheStorage/nodeRuntime(readiness-gating) andpythonRuntime/pythonTreeSitter/githubReachable(feature-specific, non-gating) checks, alongside the existingpyan3/codeVisualizerchecks — all normalized to one{ok, detail, version, checkedAt}status shape.cacheStorageround-trips against a dedicated, isolatedGraphCacheinstance, never the live request-serving cache — a naive self-test against production cache state would perturb real LRU order, consume capacity, and risk a key collision on every/readyzhit.nodeRuntimeenforces the actual declared range inpackage.json'sengines.node(^20.19.0 || >=22.12.0) via a small, pure, unit-testedisSupportedNodeVersion(version)helper — not just a bare major-version floor, which would wrongly accept 20.0.0/21.x/22.0.0.pyan3/pythonRuntime/githubReachableare periodically re-verified, folded into the existing 5-minute metrics-summary interval (one fewer new timer) via a new, independently-unit-testablerefreshDependencyStatuses()(server/lib/dependency-status.js), reentrancy-guarded so an overlapping tick is skipped rather than run concurrently.codeVisualizerstays startup-only — its parser-init memoization lives inside the vendored, commit-pinned@codevisualizer/corepackage, not something this codebase controls or should patch just to add live re-verification.pythonTreeSittersurfaces the existingPYTHON_TREE_SITTER_CAPABLEconstant (the repository layer's own, separate Python-grammar probe) as its own readiness entry — a second, independent tree-sitter dependency from CodeVisualizer's that a first plan draft missed.graphvizDotreports{applicable: false}(notok: true) — pyan3 emits DOT text directly, parsed via thets-graphvizJS library; no externalgraphviz/dotbinary is ever invoked.detail/version/checkedAtfields are only included when the request carries the sameAuthorization: Bearer <AUTH_TOKEN>used for/api/*— an unauthenticated/readyzonly ever seesok/gatesReadiness.verifyGithubReachable(github-analyzer-bridge.js) checks credential validity/reachability viaGET /rate_limit(doesn't consume the caller's own rate-limit quota), deliberately independent of the shared, per-request-mutableGitHub.tokensingleton to avoid a background-check-races-real-request hazard. InjectablefetchImpl/apiBasefor hermetic unit testing.verifyPythonRuntime/recheckPyan3Available(pyan3Adapter.js) — separate from pyan3's own pinned-version check per the checklist's two distinct bullets, and a production-facing (not test-only-named) re-check for the periodic refresh.Test plan
npm test— 654/654 passing (baseline 623 + 31 new)tests/dependency-status.test.mjs(new) — proves one failing check (bad Python binary, bad GitHub token) never affects the others, andrefreshDependencyStatusesnever rejects as a whole.tests/server-health.test.mjs— extended: cache-storage-never-touches-the-live-cache (asserts a real liveGraphCache's size/bytes/entry are completely unchanged after hitting/readyz), Node version boundary cases, auth-gated detail presence/absence, every new check's shape.tests/server-github-bridge.test.mjs—verifyGithubReachableagainst a mockedfetchImpl: 200, 401, non-401 non-2xx, network-error, abort, and header-shape assertions.tests/pyan3-adapter.test.mjs—recheckPyan3Availablegenuinely re-runs (forces a failure, then recovers against the real binary, not a stale memoized rejection);verifyPythonRuntimeagainst both a real interpreter and a missing one.tests/server-smoke.mjs) against the actual server process, the real pinned pyan3, and a real GitHub token — all 22 steps pass, including new assertions that the real/readyzresponse has every new checkok:true,graphvizDot.applicable:false, and thatdetail/versionare absent unauthenticated and present authenticated.🤖 Generated with Claude Code