diff --git a/internal/tools/credential_context.go b/internal/tools/credential_context.go index 5fe54a33..0d5d3b48 100644 --- a/internal/tools/credential_context.go +++ b/internal/tools/credential_context.go @@ -42,9 +42,10 @@ func GenerateCredentialContext(creds []store.SecureCLIBinary) string { b.WriteString("\n") } - b.WriteString("### When a command is blocked:\n") - b.WriteString("Tell the user: \"This operation requires admin approval and cannot be performed automatically.\"\n") - b.WriteString("Do NOT attempt workarounds to bypass blocked commands.\n") + b.WriteString("### When a credentialed CLI command is blocked:\n") + b.WriteString("This section applies ONLY to commands that return a `[CREDENTIALED EXEC]` error.\n") + b.WriteString("Tell the user: \"This credentialed CLI operation is blocked by policy and may require admin approval.\"\n") + b.WriteString("Do NOT attempt workarounds to bypass blocked credentialed CLI commands.\n") return b.String() } diff --git a/internal/tools/credential_context_test.go b/internal/tools/credential_context_test.go new file mode 100644 index 00000000..4f52503d --- /dev/null +++ b/internal/tools/credential_context_test.go @@ -0,0 +1,40 @@ +package tools + +import ( + "strings" + "testing" + + "github.com/nextlevelbuilder/goclaw/internal/store" +) + +// TestGenerateCredentialContext_BlockedSectionScopedToMarker pins the wording +// that scopes the "blocked command" guidance to credentialed-CLI errors only. +// The LLM must see: (a) a header that says "credentialed CLI command", +// (b) the literal `[CREDENTIALED EXEC]` marker, and (c) the qualifier +// "credentialed CLI operation" — not the bare phrase from previous wording. +func TestGenerateCredentialContext_BlockedSectionScopedToMarker(t *testing.T) { + creds := []store.SecureCLIBinary{{ + BinaryName: "gh", + Description: "GitHub CLI", + }} + + out := GenerateCredentialContext(creds) + + wantContains := []string{ + "### When a credentialed CLI command is blocked:", + "[CREDENTIALED EXEC]", + "credentialed CLI operation", + } + for _, s := range wantContains { + if !strings.Contains(out, s) { + t.Errorf("expected output to contain %q, but it did not.\nOutput:\n%s", s, out) + } + } + + // Old unqualified wording must not survive — that wording over-generalized + // to plain shell exec failures and caused unjustified pre-refusals. + dontWant := "Tell the user: \"This operation requires admin approval" + if strings.Contains(out, dontWant) { + t.Errorf("output still contains unqualified wording %q.\nOutput:\n%s", dontWant, out) + } +}