fix(qwen): bump majordomo, and stop handing keys to the wrong vendor
Build & push image / build-and-push (pull_request) Successful in 9s
Build & push image / test (pull_request) Canceled after 59s

Round 6, and one finding exposed something no reviewer mentioned: the majordomo
bump this whole PR depends on was never made. Every test here builds the openai
client directly, so all of them passed against a majordomo release that had
never heard of qwen — a plain "qwen/<model>" in GADFLY_MODELS, the primary way
anyone will use this, would not have resolved at all. A compile error caught it,
which is luck. TestBuiltinCompatProvidersResolveViaRegistry now exercises that
path; the build is what guards the dep itself, since the old release cannot
compile the code below.

On the endpoint-override path, kimi and qwen fell through to openai.New's
OPENAI_API_KEY default whenever GADFLY_API_KEY was unset — sending an OpenAI
key to Moonshot or Alibaba. That is a credential handed to the wrong vendor,
and it is the exact failure majordomo's built-ins are written to prevent; I
reintroduced it one layer up. Both now pass the key unconditionally, so an
absent key is a 401 naming GADFLY_API_KEY rather than a foreign credential on
the wire.

The test job scrubbed the registry credential and left the checkout token in
.git/config, readable by the `go test` it then runs — fixing one credential
while its neighbour sat in the open. persist-credentials: false; nothing in
that job talks to git after checkout.

The cross-language wiring test now QUERIES preflight.sh via a new
gadfly_preflight_providers function instead of regexing its case statement.
Parsing made that file's formatting a contract no linter enforces, where a
harmless reformat breaks a test in another language. Two models flagged it.

Also: grep for the scrub check takes -e, so a password starting with a hyphen
is not read as options; and key_hint stopped repeating key_env in four of five
arms.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
2026-08-12 17:51:55 -04:00
co-authored by Claude Opus 5
parent 0abcd16e9e
commit a3d3a45e7e
6 changed files with 88 additions and 16 deletions
+15 -1
View File
@@ -107,8 +107,22 @@ func resolveModel() (llm.Model, error) {
// check and then 401s.
if isOpenAICompatProvider(provider) {
opts := []openai.Option{openai.WithBaseURL(baseURL)}
if apiKey != "" {
switch {
case provider == "kimi" || provider == "qwen":
// Pass the key UNCONDITIONALLY, even when empty. openai.New defaults
// its credential to OPENAI_API_KEY, so omitting the option sends an
// OpenAI key to Moonshot or Alibaba — a credential handed to the
// wrong vendor, which is exactly what majordomo's built-ins go out
// of their way to prevent. An empty key instead yields a synthetic
// 401 naming the knob that fixes it.
opts = append(opts,
openai.WithAPIKey(apiKey),
openai.WithAPIKeyName("GADFLY_API_KEY"),
)
case apiKey != "":
opts = append(opts, openai.WithAPIKey(apiKey))
// openai/openai-compatible with no explicit key keep the
// OPENAI_API_KEY default: for those names it IS the right key.
}
return openai.New(opts...).Model(model)
}
+40 -7
View File
@@ -1,9 +1,8 @@
package main
import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
)
@@ -169,15 +168,29 @@ func TestBuildSpec(t *testing.T) {
// - scripts/preflight.sh needs a credential arm, or a missing key for that
// provider skips the pre-flight and arrives as five unexplained per-lens
// failures — the exact thing the pre-flight exists to replace.
//
// The shell side is queried, not parsed: preflight.sh exports
// gadfly_preflight_providers precisely so this test asks it what it covers.
// Regexing the case statement would make that file's formatting a contract no
// linter enforces, and a harmless reformat would fail a test in another
// language.
func TestOpenAICompatProvidersAreFullyWired(t *testing.T) {
advertised := make(map[string]bool)
for _, n := range strings.Split(endpointProviderNames, "/") {
advertised[strings.TrimSpace(n)] = true
}
preflight, err := os.ReadFile(filepath.Join("..", "..", "scripts", "preflight.sh"))
script := filepath.Join("..", "..", "scripts", "preflight.sh")
out, err := exec.Command("bash", "-c", ". "+script+"; gadfly_preflight_providers").Output()
if err != nil {
t.Fatalf("read preflight.sh: %v", err)
t.Fatalf("query gadfly_preflight_providers from %s: %v", script, err)
}
preflighted := make(map[string]bool)
for _, line := range strings.Fields(string(out)) {
preflighted[line] = true
}
if len(preflighted) == 0 {
t.Fatal("gadfly_preflight_providers returned nothing — this test would pass vacuously")
}
for _, p := range openAICompatProviders {
@@ -185,11 +198,31 @@ func TestOpenAICompatProvidersAreFullyWired(t *testing.T) {
t.Errorf("openAICompatProviders has %q but endpointProviderNames does not list it — "+
"the error message operators read would omit a name that works", p)
}
// The arm may be shared ("openai|openai-compatible)"), so match the
// bare name as a case alternative rather than a whole line.
if !regexp.MustCompile(`(?m)^\s*(\w[\w-]*\|)*` + regexp.QuoteMeta(p) + `(\|[\w-]+)*\)`).Match(preflight) {
if !preflighted[p] {
t.Errorf("openAICompatProviders has %q but scripts/preflight.sh has no credential arm for it — "+
"a missing key for %s would skip the pre-flight and surface as unexplained lens failures", p, p)
}
}
}
// TestBuiltinCompatProvidersResolveViaRegistry exercises the PRIMARY path:
// a plain "qwen/<model>" in GADFLY_MODELS, with no GADFLY_BASE_URL, resolved
// through majordomo's registry rather than constructed here.
//
// Every other test in this file builds the client directly, so all of them
// passed against a majordomo release that had never heard of qwen — the
// dependency bump this feature depends on was missing and nothing said so. A
// compile error eventually caught it, which is luck, not cover.
func TestBuiltinCompatProvidersResolveViaRegistry(t *testing.T) {
for _, spec := range []string{"qwen/qwen3.8-max", "kimi/kimi-k2-0711-preview"} {
t.Run(spec, func(t *testing.T) {
t.Setenv("GADFLY_MODEL", spec)
t.Setenv("GADFLY_BASE_URL", "")
t.Setenv("GADFLY_PROVIDER", "")
if _, err := resolveModel(); err != nil {
t.Fatalf("resolveModel(%q): %v — the pinned majordomo may not "+
"provide this built-in; a bump is required, not just gadfly-side wiring", spec, err)
}
})
}
}