Non-Anthropic providers are broken end-to-end: launcher exports SOUL_LLM_BASE_URL, engine reads NEURON_LLM_0_URL — so use_openai is always false and a Groq key gets POSTed to api.anthropic.com (and that path has no tools anyway) #112

Open
opened 2026-08-06 03:26:41 +00:00 by tim.lingo · 0 comments
Member

Filed by Neuron (Tim's instance) 2026-08-05. All line numbers from neuron main (HEAD 4171aad) and the round-7 launcher; the byte-level check is against the shipped brain.

I started from a narrower finding — tools are not passed on the OpenAI-compatible path — and it is true, but it is the second problem. The first is that the OpenAI-compatible path can never execute in a shipped install. Both are below, in the order they need fixing.


Problem 1: the launcher and the engine use different env var names, so use_openai is always false

The engine reads (neuron/chat.el:1295, 1299):

fn llm_base_url() -> String {
    return env("NEURON_LLM_0_URL")
}
fn llm_wire_format() -> String {
    let f: String = env("NEURON_LLM_0_FORMAT")
    if str_eq(f, "") { return "anthropic" }
    return f
}

The launcher exports (_wt-beta-round7/resources/macos-arm64/neuron-daemons.sh:287-299):

287: exec env \
294:     SOUL_LLM_PROVIDER="$PROVIDER" \
295:     SOUL_LLM_BASE_URL="${BASE_URL:-}" \
298:     ANTHROPIC_API_KEY="${API_KEY:-}" \
299:     SOUL_API_KEY="${API_KEY:-}" \

It exports SOUL_LLM_BASE_URL. It never exports NEURON_LLM_0_URL or NEURON_LLM_0_FORMAT at all.

Nothing reads SOUL_LLM_BASE_URL. An env("...") search across the engine sources returns zero reads for SOUL_LLM_BASE_URL, SOUL_LLM_PROVIDER and SOUL_API_KEY. Confirmed at the byte level in the shipped brain (_wt-beta-round7/resources/macos-arm64/neuron, md5 85a19bd6e83a9f96f60a7b7a17e5745d), strings -a | grep -cx:

name in shipped brain who sets it
NEURON_LLM_0_URL 1 nobody
NEURON_LLM_0_FORMAT 1 nobody
SOUL_LLM_BASE_URL 0 the launcher

So at chat.el:2103 the fork evaluates with llm_base_url() == "" and llm_wire_format() == "anthropic":

2103:    let use_openai: Bool = !str_eq(llm_base_url(), "") && str_eq(llm_wire_format(), "openai")

false && falseuse_openai is unconditionally false in every shipped configuration. The OpenAI-compatible path added by #65 is unreachable dead code as packaged.

What that means for a Groq user

Every provider falls into the Anthropic branch. That branch has the endpoint hardcoded (chat.el:2085 and chat.el:2167):

let api_url: String = "https://api.anthropic.com/v1/messages"

and takes its key from agentic_api_key() (chat.el:1436-1442), which reads ANTHROPIC_API_KEY first — into which the launcher has put whatever provider key the user saved (line 298). A user who picks Groq, pastes a gsk_… key, and sends a message causes that key to be POSTed to api.anthropic.com in Anthropic wire format.

Two consequences, both bad:

  1. It cannot work. The turn fails authentication. This is a candidate root cause for user-visible "couldn't get a reply" reports on non-Anthropic providers.
  2. A third-party API key is transmitted to a vendor the user never chose. The launcher does resolve the right URL per provider — neuron-daemons.sh:165-171, including groq) BASE_URL="https://api.groq.com/openai/v1" — it just hands it to the engine under a name the engine does not read.

Commercial weight: Groq is the free, zero-cost lane onboarding recommends to new users (OnboardingView.kt:57-58, added in ui#194). It is the path a free-tier beta user is most likely to take.

The fix

Align the names — one side or the other, your call. Cheapest is for the engine to accept the names the launcher already exports (SOUL_LLM_BASE_URL, plus a wire-format derived from SOUL_LLM_PROVIDER) while keeping NEURON_LLM_0_* as the override. Worth a boot-time log line stating the resolved provider, base URL and wire format, so a mismatch like this announces itself instead of silently degrading.


Problem 2: once the path is reachable, it still has no tools

chat.el:2103-2108 — the fork happens before the tool list is used, and only one branch receives it:

    let result: String = if use_openai {
        openai_chat_complete(model, llm_base_url(), agentic_api_key(), safe_sys, messages)
    } else {
        agentic_loop(session_id, model, safe_sys, tools_json, messages, h, "")
    }

The Anthropic branch is handed tools_json. The OpenAI branch is not — and cannot be: openai_chat_complete has no tools parameter in its signature (chat.el:1318):

fn openai_chat_complete(model: String, base_url: String, api_key: String, safe_sys: String, messages_json: String) -> String {

There is no tool loop on that path either — it is a single completion call. So on OpenAI, Grok, Gemini, Groq and Ollama an "agentic" turn has no tools whatsoever. The code is honest about it (chat.el:1290, "v1 SCOPE: plain chat completion only — NO tools / agentic loop yet (that is a follow-up port)") — this issue is to make that follow-up port a tracked item, because the app has been shipping the promise.

Ours, already fixed: our app-side prompt was telling those users they had file and command tools. That text is now provider-aware in neuron-ui#223. The engine-side port is yours.


Order of operations

  1. Fix the env-name mismatch (Problem 1). Small, and it converts a class of silent total failure into a working plain-chat turn.
  2. Port the tool loop onto the OpenAI-compatible path (Problem 2), or keep the app's provider-aware messaging as the honest interim.

Cross-refs: neuron#65 (added the OpenAI-compatible path), neuron#62 (configurable inference endpoint + auth token — same surface), neuron-ui#223 (app-side prompt honesty, ours), neuron-ui#193 (the two-bill / free-lane story), neuron-ui#213 (2026-08-03 bug harvest).

**Filed by Neuron (Tim's instance) 2026-08-05. All line numbers from `neuron` `main` (HEAD `4171aad`) and the round-7 launcher; the byte-level check is against the shipped brain.** I started from a narrower finding — *tools are not passed on the OpenAI-compatible path* — and it is true, but it is the **second** problem. The first is that the OpenAI-compatible path can never execute in a shipped install. Both are below, in the order they need fixing. --- ## Problem 1: the launcher and the engine use different env var names, so `use_openai` is always false **The engine reads** (`neuron/chat.el:1295, 1299`): ``` fn llm_base_url() -> String { return env("NEURON_LLM_0_URL") } fn llm_wire_format() -> String { let f: String = env("NEURON_LLM_0_FORMAT") if str_eq(f, "") { return "anthropic" } return f } ``` **The launcher exports** (`_wt-beta-round7/resources/macos-arm64/neuron-daemons.sh:287-299`): ``` 287: exec env \ 294: SOUL_LLM_PROVIDER="$PROVIDER" \ 295: SOUL_LLM_BASE_URL="${BASE_URL:-}" \ 298: ANTHROPIC_API_KEY="${API_KEY:-}" \ 299: SOUL_API_KEY="${API_KEY:-}" \ ``` It exports `SOUL_LLM_BASE_URL`. It never exports `NEURON_LLM_0_URL` or `NEURON_LLM_0_FORMAT` at all. **Nothing reads `SOUL_LLM_BASE_URL`.** An `env("...")` search across the engine sources returns zero reads for `SOUL_LLM_BASE_URL`, `SOUL_LLM_PROVIDER` and `SOUL_API_KEY`. Confirmed at the byte level in the shipped brain (`_wt-beta-round7/resources/macos-arm64/neuron`, md5 `85a19bd6e83a9f96f60a7b7a17e5745d`), `strings -a | grep -cx`: | name | in shipped brain | who sets it | |---|---|---| | `NEURON_LLM_0_URL` | 1 | nobody | | `NEURON_LLM_0_FORMAT` | 1 | nobody | | `SOUL_LLM_BASE_URL` | **0** | the launcher | So at `chat.el:2103` the fork evaluates with `llm_base_url() == ""` and `llm_wire_format() == "anthropic"`: ``` 2103: let use_openai: Bool = !str_eq(llm_base_url(), "") && str_eq(llm_wire_format(), "openai") ``` `false && false` — **`use_openai` is unconditionally false in every shipped configuration.** The OpenAI-compatible path added by #65 is unreachable dead code as packaged. ### What that means for a Groq user Every provider falls into the Anthropic branch. That branch has the endpoint hardcoded (`chat.el:2085` and `chat.el:2167`): ``` let api_url: String = "https://api.anthropic.com/v1/messages" ``` and takes its key from `agentic_api_key()` (`chat.el:1436-1442`), which reads `ANTHROPIC_API_KEY` first — into which the launcher has put **whatever provider key the user saved** (line 298). A user who picks Groq, pastes a `gsk_…` key, and sends a message causes that key to be POSTed to `api.anthropic.com` in Anthropic wire format. Two consequences, both bad: 1. **It cannot work.** The turn fails authentication. This is a candidate root cause for user-visible "couldn't get a reply" reports on non-Anthropic providers. 2. **A third-party API key is transmitted to a vendor the user never chose.** The launcher does resolve the right URL per provider — `neuron-daemons.sh:165-171`, including `groq) BASE_URL="https://api.groq.com/openai/v1"` — it just hands it to the engine under a name the engine does not read. **Commercial weight:** Groq is the free, zero-cost lane onboarding recommends to new users (`OnboardingView.kt:57-58`, added in ui#194). It is the path a free-tier beta user is most likely to take. ### The fix Align the names — one side or the other, your call. Cheapest is for the engine to accept the names the launcher already exports (`SOUL_LLM_BASE_URL`, plus a wire-format derived from `SOUL_LLM_PROVIDER`) while keeping `NEURON_LLM_0_*` as the override. Worth a boot-time log line stating the resolved provider, base URL and wire format, so a mismatch like this announces itself instead of silently degrading. --- ## Problem 2: once the path is reachable, it still has no tools `chat.el:2103-2108` — the fork happens *before* the tool list is used, and only one branch receives it: ``` let result: String = if use_openai { openai_chat_complete(model, llm_base_url(), agentic_api_key(), safe_sys, messages) } else { agentic_loop(session_id, model, safe_sys, tools_json, messages, h, "") } ``` The Anthropic branch is handed `tools_json`. The OpenAI branch is not — and cannot be: `openai_chat_complete` has no tools parameter in its signature (`chat.el:1318`): ``` fn openai_chat_complete(model: String, base_url: String, api_key: String, safe_sys: String, messages_json: String) -> String { ``` There is no tool loop on that path either — it is a single completion call. So on OpenAI, Grok, Gemini, Groq and Ollama an "agentic" turn has **no tools whatsoever**. The code is honest about it (`chat.el:1290`, *"v1 SCOPE: plain chat completion only — NO tools / agentic loop yet (that is a follow-up port)"*) — this issue is to make that follow-up port a tracked item, because the app has been shipping the promise. **Ours, already fixed:** our app-side prompt was telling those users they had file and command tools. That text is now provider-aware in **neuron-ui#223**. The engine-side port is yours. --- ## Order of operations 1. Fix the env-name mismatch (Problem 1). Small, and it converts a class of silent total failure into a working plain-chat turn. 2. Port the tool loop onto the OpenAI-compatible path (Problem 2), or keep the app's provider-aware messaging as the honest interim. Cross-refs: neuron#65 (added the OpenAI-compatible path), neuron#62 (configurable inference endpoint + auth token — same surface), neuron-ui#223 (app-side prompt honesty, ours), neuron-ui#193 (the two-bill / free-lane story), neuron-ui#213 (2026-08-03 bug harvest).
tim.lingo added the securityP0BETA-CRITICAL labels 2026-08-06 03:26:42 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: neuron-technologies/neuron#112