From 1ef1b1356044e0ed8e792ec2c6ad65bb7b510958 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 8 Nov 2026 23:55:32 +0100 Subject: [PATCH] The keeper's health reads the consumers it looks for: the shared monitoring read asks for them nats_keeper_health found the keeper's consumer novox_nats_keeper in /jsz, but the read it shares with the broker verbs asked /jsz?accounts=1&streams=1&config=1, without consumers=1, so keeperPending always read -1 and the keeper was "unhealthy: what waits on its consumer cannot be read from the bus's monitoring" for ever (2026-11-08, issue 1966). Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5 --- modules/nats/cmd/nats-tools/main.go | 7 ++++++- modules/nats/cmd/nats-tools/tools_test.go | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/nats/cmd/nats-tools/main.go b/modules/nats/cmd/nats-tools/main.go index 420d106a7..f16222c37 100644 --- a/modules/nats/cmd/nats-tools/main.go +++ b/modules/nats/cmd/nats-tools/main.go @@ -172,13 +172,18 @@ var ReadingVerbs = []string{"list", "show", "check", "orphans"} // and kept in full on the bus module's own stream broker-events (BROKER_EVENTS), which `history` reads (keep.go). var Events = []string{"created", "refused", "drifted", "drift-cleared", "full", "full-cleared", "orphaned", "recreated", "dropped"} +// heldJsz is the monitoring read the broker's verbs and the keeper's health share: with the consumers, since the +// keeper's health reads what waits on its own consumer from it (keeperPending); without them it read -1 and the keeper +// was never healthy (2026-11-08, issue 1966). +const heldJsz = "/jsz?accounts=1&streams=1&consumers=1&config=1" + // allTools is every tool and seat verb this bundle serves. func allTools() []stdio.Tool { monitor := LiveMonitor() jsz := func() ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() - return monitor(ctx, "/jsz?accounts=1&streams=1&config=1") + return monitor(ctx, heldJsz) } out := append(tools(monitor, ReadUsers, UsersWritten), removalTools(LiveBus(), SaidByTheRuntime, HeldInState)...) out = append(out, expiryTools(LiveBus(), SaidByTheRuntime, RecordedBySeat)...) diff --git a/modules/nats/cmd/nats-tools/tools_test.go b/modules/nats/cmd/nats-tools/tools_test.go index e2493a151..ce2b827d1 100644 --- a/modules/nats/cmd/nats-tools/tools_test.go +++ b/modules/nats/cmd/nats-tools/tools_test.go @@ -277,3 +277,15 @@ func TestTheRemovalToolsRefuseBeforeTheyReachTheBus(t *testing.T) { } } } + +// The keeper's health reads what waits on its own consumer from the shared monitoring read: that read asks for the +// consumers, or the health answers "cannot be read" for ever (2026-11-08, issue 1966). +func TestTheHeldReadAsksForTheConsumers(t *testing.T) { + if !strings.Contains(heldJsz, "consumers=1") { + t.Fatalf("the monitoring read %q asks for no consumers, so keeperPending never finds the keeper's", heldJsz) + } + doc := []byte(`{"account_details":[{"stream_detail":[{"name":"EVENTS","consumer_detail":[{"name":"novox_nats_keeper","num_pending":2}]}]}]}`) + if got := keeperPending(doc); got != 2 { + t.Fatalf("keeperPending = %d on a read with the keeper's consumer, want 2", got) + } +} -- 2.54.0