From 8405e32efcbe3e3f6211d95a529cb1fd8f3d229e Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 4 Oct 2026 11:31:42 +0200 Subject: [PATCH] The console says an account was refused only when the bus refused it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "authorization" alone matched a tool's own answer mentioning the word — the runtime refusing a state value with an Authorization header read as "this account may not call". --- node-tools/internal/console/client.go | 7 +++++-- node-tools/internal/console/refusal_test.go | 22 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 node-tools/internal/console/refusal_test.go diff --git a/node-tools/internal/console/client.go b/node-tools/internal/console/client.go index f47a805..c350495 100644 --- a/node-tools/internal/console/client.go +++ b/node-tools/internal/console/client.go @@ -110,8 +110,11 @@ func subjectListed(name, node string, l *Listing) string { var ( noResponders = regexp.MustCompile(`(?i)no responders|503`) - refused = regexp.MustCompile(`(?i)permissions violation|authorization`) - timedOut = regexp.MustCompile(`(?i)timeout`) + // The bus's own two refusals, by their whole phrase. "authorization" alone also matched a tool's own + // answer that merely mentions the word — the runtime refusing a state value with an Authorization + // header (novox/hq ADR 0201) read as "this account may not call", which sent the reader the wrong way. + refused = regexp.MustCompile(`(?i)permissions violation|authorization violation`) + timedOut = regexp.MustCompile(`(?i)timeout`) ) // whyItFailed says why a call failed, so the remedy is in the words. diff --git a/node-tools/internal/console/refusal_test.go b/node-tools/internal/console/refusal_test.go new file mode 100644 index 0000000..4078c51 --- /dev/null +++ b/node-tools/internal/console/refusal_test.go @@ -0,0 +1,22 @@ +package console + +import ( + "errors" + "strings" + "testing" +) + +// A refusal is said only for the bus's own refusals; a tool's answer that mentions authorization is the +// tool's answer, not the account's. +func TestARefusalIsSaidOnlyForTheBussOwn(t *testing.T) { + for msg, refusal := range map[string]bool{ + `nats: Permissions Violation for Publish to "mesh.mod.x.tool.y"`: true, + "nats: Authorization Violation": true, + `claude-code's servers.all.x carries a field "Authorization", which names a credential`: false, + } { + got := strings.HasPrefix(whyItFailed("x.y", errors.New(msg)), "this account may not call") + if got != refusal { + t.Errorf("%q read as a refusal: %v, want %v", msg, got, refusal) + } + } +}