✨ feature: live backend smoke for contributor CLIs + durable run telemetry - #5408
✨ feature: live backend smoke for contributor CLIs + durable run telemetry#5408Danathar wants to merge 5 commits into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Quality verification (live hive host): fetched
Skip behavior is loud-but-green as documented and the Filed by quality agent (hold-gated mode).🐝 Hive Agent: — hive: agent=quality backend=copilot model=claude-fable-5 |
Hive drives vendor coding-agent CLIs (claude, codex, ...) through the
contributor relay, and that seam drifts as vendors ship updates. Every
existing test pins the seam against captured fixtures, so vendor changes
ship green here and break in production. This adds the live complement:
bin/test_backend_smoke.sh drives the REAL relay against a fake ws hub and,
where credentials exist, the real CLI on a one-line task, asserting the
machine-checkable contract — the task wire shape, the HIVE_VERDICT
sentinel, and completion_signal=verdict rather than the chrome_idle
fallback.
Its first full run caught three live bugs, fixed here:
- detectHiveVerdict only tolerated codex's bullet (•, U+2022). Claude Code
renders assistant lines with ● (U+25CF), so every interactive claude
completion silently degraded to the chrome_idle fallback the sentinel
exists to replace.
- headless codex hung until the task timeout: codex exec blocks on
stdin-EOF even with the prompt in argv, and execFile's piped stdin was
never closed. runHeadlessTask now closes the child's stdin.
- codex exec refuses a non-git cwd, and the task workspace root is exactly
that; the headless argv now passes --skip-git-repo-check.
Wiring:
- .github/workflows/backend-smoke.yml: scheduled every 6h at :45 (the :00
and :30 slots are taken), matrix backend={claude,codex} x
lane={latest,pinned}. latest installs the vendor's current CLI on the
runner (drift early warning); pinned runs inside
ghcr.io/kubestellar/hive-contributor:latest (the versions contributors
actually get). Per-arm key scoping; deduplicated per-lane issues on
scheduled failures only. First and only workflow holding model
credentials — deliberately its own file, outside the arm64 lane's
no-secrets contract. Cheapest model tiers only (haiku / gpt-5.4-mini).
- v2-ci runs the keyless subset (drift checks + stub wire-contract
scenarios; live scenarios skip), so backend-list and Dockerfile-pin
drift are merge-gating with zero credentials.
- just backend-smoke runs it locally; HIVE_TEST_REQUIRE_BACKEND_SMOKE=1
reproduces the scheduled lane's skips-are-failures inversion.
- src/Dockerfile.contributor's inline codex pin becomes ARG CODEX_VERSION,
which the suite asserts equal to src/Dockerfile's — they had no guard
and CAN drift (found by the suite's first keyless run).
Verified: 33/33 with live claude + codex arms locally; relay suite
256/256; bin/test_bin_suites_wired.sh and check-release-lines.sh pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo76R9XXJ2iq6YcmVUiX2u
Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
… ratchet Every accepted task_complete/task_failed already carries a normalized closed vocabulary — completion_signal (kubestellar#5376), the failure kind (kubestellar#2547), the no_work_needed verdict (kubestellar#3987) — but none of it survived anywhere an operator could aggregate: the slog line rotates away, activity.json is capped at 50 entries and drops reason/kind, and nothing recorded task durations at all. "Which backend completes on its verdict vs the chrome-idle fallback" was unanswerable for real fleet traffic — the exact question the backend smoke asks synthetically. - src/pkg/dashboard/task_run_log.go: one JSONL record per terminal task report, appended to /data/contributors/task_runs.jsonl (0600, 10 MiB cap with a single .1 rotation). Each record carries the hub-normalized fields plus a derived `scenario` from a closed, stable vocabulary: verdict_complete, idle_complete (the ratchet metric — sentinel non-compliance), headless_complete, env_failure, task_failure, unspecified_failure. Best-effort by contract; DECLARE only — nothing routes, cooldowns, or gates on it, the same boundary contribute_protocol.go draws. - contribute_ws.go: a taskAssignedAt anchor on the connection (separate from lastLeaseRenew, which progress reports move) so records carry real wall-clock duration; appends in the accepted task_complete and task_failed branches, reusing the exact locals the structured log line already assembles. - GET /api/contribute/run-stats: per-backend scenario counts, chrome-idle share, and duration p50/p95 over a trailing window (?days=N, default 7). Public read-only like the sibling /api/contribute* GETs — aggregates only, no usernames, reasons, or tokens. Documented in dashboard/openapi.json (route-parity test enforced). - Session mirrors task_id, the correlation key github.InvocationMeta.Session reserves, so PR trailers can later join back to run records without a format change. Tests: scenario-derivation matrix, append/rotation/permissions, windowed aggregation with torn-line tolerance, and an end-to-end WebSocket task_complete drive asserting one record with positive duration plus the run-stats endpoint seeing it. Full pkg/dashboard suite green under -race. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo76R9XXJ2iq6YcmVUiX2u Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
Projects without metered API keys can fund the backend smoke from a Claude Pro/Max or ChatGPT account instead: the suite now accepts HIVE_SMOKE_CLAUDE_CREDENTIALS_B64 (base64 ~/.claude/.credentials.json) and HIVE_SMOKE_CODEX_AUTH_B64 (base64 ~/.codex/auth.json), tried after the API key and before the copy-the-operator's-login fallback. Decode is the careful kubestellar#5103 shape — temp file, discard on failure — so a corrupt secret degrades to a clean credential-missing skip. The workflow forwards the two new secrets with the same per-arm scoping as the keys, and its header documents the accepted trade-offs: OAuth refresh chains rotate so the stored blob goes stale and needs periodic re-capture, and the smoke shares the account's rate limits with its human. API keys remain the recommended form for an unattended schedule. Verified live: the full claude arm runs green with the credential delivered only via the B64 env (empty HOME, no API key), and a corrupt blob falls through to the skip path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo76R9XXJ2iq6YcmVUiX2u Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
… they found Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo76R9XXJ2iq6YcmVUiX2u Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
The smoke suite, its scheduled workflow, and the run telemetry were documented only in file headers and the changelog — nothing under src/docs/, which is what syncs to kubestellar.io and where an operator or maintainer actually looks. The new page covers the questions the code comments answer piecemeal: who runs what (the project's CI, centrally — hive operators run nothing and fund nothing), the latest/pinned lane distinction and what a red in each means, the per-lane deduplicated issue-filing flow with its evidence artifact, both credential forms with their trade-offs and the activation checklist, and the task_runs.jsonl scenario vocabulary with the idle_complete ratchet behind /api/contribute/run-stats. Indexed in src/docs/README.md beside the ClankeR relay page; check-docs-links.py passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo76R9XXJ2iq6YcmVUiX2u Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
49585a3 to
90e5ca6
Compare
Summary
Hive runs contributor tasks by driving vendor coding-agent CLIs ("clankers" — Claude Code, Codex, …) through the contributor relay, and that integration seam breaks whenever a vendor ships a CLI update: every existing test pins the seam against captured fixtures, so vendor drift lands green in CI and fails contributors in production. This PR adds a live smoke harness that runs the real relay against a fake hub — and, on a 6-hour schedule with real credentials, the real CLIs on a one-line task — plus durable per-run telemetry so the same health questions are answerable for real fleet traffic, not just synthetic runs.
--skip-git-repo-check).Implements the test-harness proposal from maintainer discussion (Discord, 2026-08-31); no tracking issue exists.
Detail
bin/test_backend_smoke.sh(alsojust backend-smoke) — three tiers:HEADLESS_BACKENDStable vsKNOWN_BACKENDSinconfig/backends.conf(previously sync-by-comment only), and codex/claude version-pin parity acrosssrc/Dockerfileandsrc/Dockerfile.contributor(the contributor codex pin was an inline hardcode with no guard; now anARG).task_failedshape) with zero API spend. This subset runs merge-gating in v2-ci.detect_clihealth probe, headless end-to-end (claude -p/codex exec, asserting theHIVE_VERDICT: no_work_neededreply on the wire), and interactive end-to-end (real CLI in tmux, relay scraping the pane, assertingcompletion_signal == "verdict"— chrome_idle is a failure naming the sentinel-contract regression class). Credentials: API key, base64 subscription-login blob (HIVE_SMOKE_*_B64, the 🐛 bug: contribute-k8s generates a workload with no agent-CLI credential — all five allow-listed headless backends launch unauthenticated #5103 decode shape), or the operator's own login copied into a throwaway HOME. Skips are loud-but-green locally and fatal underHIVE_TEST_REQUIRE_BACKEND_SMOKE=1(the test: run the entrypoint behavioural suites in the container lane, and fail on a skip (#5380) #5383 inversion)..github/workflows/backend-smoke.yml— cron45 2,8,14,20 * * *(off the taken :00/:30 slots), matrix {claude, codex} × {latest, pinned}, per-arm credential scoping, cheapest model tiers (haiku / gpt-5.4-mini ≈ 32 one-line calls/day),create-issue-on-failurecopied from v2-tests' schedule-gated dedupe shape but per lane. First and only workflow holding model credentials — deliberately its own file, outside the arm64 lane's no-secrets contract. Gated togithub.repository == 'kubestellar/hive'.Relay fixes (each found live by the harness, each with a regression test):
detectHiveVerdictaccepted only codex's•(U+2022) bullet; Claude Code renders●(U+25CF). The pane showed the sentinel while the relay logged "no HIVE_VERDICT yet". Both glyphs now match; same anchoring and echo guards.runHeadlessTasknever closed the child's stdin, andcodex execblocks on stdin-EOF even with the prompt in argv → zero-output hang until the 4h timeout. Stdin is now closed for every one-shot child.--skip-git-repo-check:execrefuses a non-git cwd, and the task workspace root is one until the agent's first clone.Telemetry (
src/pkg/dashboard/task_run_log.go): one JSONL record per acceptedtask_complete/task_failedin/data/contributors/task_runs.jsonl(0600, 10 MiB cap, single rotation) — the already-normalized fields (completion_signal, failure kind, verdict) plus wall-clock duration (newtaskAssignedAtanchor, separate fromlastLeaseRenewwhich progress reports move) and a derived scenario from a closed vocabulary (verdict_complete,idle_complete,headless_complete,env_failure,task_failure,unspecified_failure).GET /api/contribute/run-statsaggregates per backend (scenario counts, chrome-idle share, duration p50/p95); public read-only like the sibling contribute GETs, aggregates only. DECLARE-only per thecontribute_protocol.goboundary — nothing routes on any of it; writes are best-effort and never fail a task. Documented indashboard/openapi.json(route-parity test enforced).Deferred follow-ups: fleet-level aggregation of per-hive run-stats over the heartbeat (touches
hub.RepoActivityWire),backend=/model=extraction inactivity_collector.go, and populatingInvocationMeta.Sessionon contributor PRs (the correlation key the records already carry assession).Post-merge to activate the scheduled lane: provision per-vendor secrets (either
ANTHROPIC_API_KEY/OPENAI_API_KEY, orHIVE_SMOKE_CLAUDE_CREDENTIALS_B64/HIVE_SMOKE_CODEX_AUTH_B64) and run oneworkflow_dispatchper lane; the pinned in-container lane could not be exercised locally.Related issues
None — implements the maintainer-proposed clanker test harness (Discord discussion, 2026-08-31). Touches the surfaces of #5376 (completion signal), #5335 (CODEX_HOME), #5383 (skip inversion), #5103 (delivered credentials).
Testing
cd src && go build ./...cd src && go test -short -race ./pkg/dashboard/(green, including new scenario-matrix, rotation, aggregation, and end-to-end WebSocket run-record tests)bash bin/test_backend_smoke.sh— 33/33 with live claude (haiku) and codex (gpt-5.4-mini) arms, both headless and interactive; also green via the B64 subscription-credential path with an empty HOME.node bin/contributor-relay.test.js256/256.bin/test_bin_suites_wired.sh,src/scripts/check-release-lines.sh, shellcheck on the new suite: all pass. The pinned in-container workflow lane needs a post-mergeworkflow_dispatch(no image credentials locally).Contributor checklist
v2unless a maintainer requested another branch. (Targetsv4, the current default branch.)📖 docs: ...,🐛 fix: ..., or✨ feature: ....git commit -s).CHANGELOG.mdhas an entry for user-visible changes (features, fixes, new env vars, behavior changes), or the change is not user-facing.🤖 Generated with Claude Code
https://claude.ai/code/session_01Jo76R9XXJ2iq6YcmVUiX2u