Env-sourced endpoints are not trimmed: a trailing CR in ANTHROPIC_BASE_URL fails every file with an opaque URL-parse error
OpenCodeReview Version
open-code-review dev windows/amd64 — built from source at commit 089b68b (v1.12.4-14-g089b68b, 2026-09-17). Re-checked against origin/main 189be5b (2026-09-17 20:59): both functions below still read the raw environment values there.
Operating System
Windows (Windows 11, 10.0.22631), Git Bash
Installation Method
Built from source (go build -o dist/ocr.exe ./cmd/opencodereview)
LLM Provider
Anthropic-compatible gateway, reached through the Claude Code environment resolution path (ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN / ANTHROPIC_MODEL)
Bug Description
Endpoint resolution normalizes its inputs on some paths but not others:
| resolution path | trims the value? |
|---|---|
parseShellRC (shell rc file) |
yes — line and value (internal/llm/resolver.go ~767, ~780) |
parseTimeoutEnv |
yes (~204) |
NormalizeAuthHeader |
yes (~827) |
provider api_key |
yes (~426) |
tryOCREnv — OCR_LLM_URL/TOKEN/MODEL |
no (~251-254) |
tryCCEnv — ANTHROPIC_BASE_URL/AUTH_TOKEN/MODEL |
no (~702-705) |
So a value that the shell-rc path tolerates is fatal on the environment path. OCR_LLM_PROTOCOL, read a few lines below tryOCREnv's untrimmed reads, is wrapped in strings.TrimSpace, which makes this look like an oversight rather than intent.
Two consequences:
- Opaque failure. The Go SDK rejects the value before any request is sent:
requestoption: WithBaseURL failed to parse url parse "...\r": net/url: invalid control character in URL. Nothing in the message points at the environment, or at which variable was dirty. In my case this failed all 36 files of a review run (36llm_errorrecords with an identical message in the session JSONL), and the value only had a trailing\r— exactly what a CRLF-generated environment produces on Windows. - The session log can't be triaged.
llm_errorrecords carry only the raw error string, not the resolvedSource, so a reader cannot tell whether the bad endpoint came from the environment, the config file, or the rc file.
Steps to Reproduce
A dummy token is enough: the failure happens while parsing the URL, before any request, so nothing is spent.
export ANTHROPIC_BASE_URL=$'https://<host>/api/anthropic\r' # trailing CR, e.g. CRLF-generated env
export ANTHROPIC_AUTH_TOKEN=dummy
export ANTHROPIC_MODEL=<model>
ocr llm test
export ANTHROPIC_BASE_URL="https://<host>/api/anthropic" # same value, trimmed
ocr llm testThe same applies to OCR_LLM_URL / OCR_LLM_TOKEN / OCR_LLM_MODEL.
Expected Behavior
OCR_LLM_*andANTHROPIC_*values are trimmed before use — or normalized once infinalizeResolvedEndpoint, which already normalizes the model suffix — so every resolution path behaves identically.- When a value can't be used, the error names the source and the variable, e.g.
Claude Code environment: invalid ANTHROPIC_BASE_URL (control character in URL), and the session record preserves which source was resolved.
Logs / Error Output
$ ANTHROPIC_BASE_URL=$'https://zenmux.ai/api/anthropic\r' ANTHROPIC_AUTH_TOKEN=dummy ANTHROPIC_MODEL=<model> ocr llm test
Error: llm request failed: requestoption: WithBaseURL failed to parse url parse "https://zenmux.ai/api/anthropic\r": net/url: invalid control character in URL
$ ANTHROPIC_BASE_URL="https://zenmux.ai/api/anthropic" ANTHROPIC_AUTH_TOKEN=dummy ANTHROPIC_MODEL=<model> ocr llm test
Error: llm request failed: POST "https://zenmux.ai/api/anthropic/v1/messages": 403 Forbidden {"error":{"code":"403","type":"access_denied",...}}The second command reaches the request, i.e. URL construction succeeded once the CR was gone (the token is a dummy, so the gateway rejects the auth — nothing was consumed).
Session JSONL from the failing review run, 36 records of this shape:
{"duration_ms":0,"error":"requestoption: WithBaseURL failed to parse url parse \"https://<host>/api/anthropic\\r\": net/url: invalid control character in URL","filePath":"<redacted>","request_no":1,"taskType":"main_task","type":"llm_error"}AI/LLM Disclosure
The reproduction and verification above were run by me. This write-up was drafted with an AI coding agent (OMP, model deepseek/deepseek-v4.1-flash, via a gateway), and I checked every command and log line myself.
Additional Context
Related, on the same fallback path — disclosing the resolved endpoint:
The Claude Code environment fallback is documented and covered by tests (resolver_test.go:55), and honoring ambient ANTHROPIC_* is reasonable for zero-config use. What's missing is disclosure: the resolved source is printed only by ocr llm test (cmd/opencodereview/llm_cmd.go:110), while ocr review prints nothing about it and --preview lists files only. A review launched by a wrapper script, another agent, or CI therefore silently inherits whatever endpoint and model the ambient environment carries — and the price spread between tiers is large (roughly 8x between the cheap and expensive tier on my gateway). One 36-file review inherited an expensive tier model and consumed ~1.42M tokens, about 90% of that day's gateway spend, which I only noticed on the bill.
Suggestions, in descending order of how much they would have helped:
- Print one line at review start, on stderr so
--format json|sarifstdout stays clean:[ocr] llm: <model> via <source>. - When the only resolved source is an ambient one (
Claude Code environment/Shell rc file), say so explicitly, and consider requiring confirmation (--yes) or offering an escape hatch such as--no-ambient-endpoint. - Let
ocr llm testaccept--modelthe wayocr reviewdoes, so a run with a specific model can be preflighted. My preflight failed withunknown flag: --modeland had to be rewritten to setANTHROPIC_MODELinstead.
Minor note from the same run: the review exited 0 with 22 of 36 items covered after the gateway answered 402 quote_exceeded for the rest. That matches the documented contract (cmd/opencodereview/review_cmd.go:330-339 — non-zero only when every selected item failed) and is the right behavior for a self-imposed budget cap, but for a provider-side auth/quota failure CI would read a half-finished review as success.
Source: alibaba/open-code-review