`cli-chat-proxy.grok.com` turns away requests that do not name a
supported client version — the failure mode #3952 was opened for. The
pin has sat at 0.2.93 since 2026-07-17, while https://x.ai/cli/stable
and `npm view @xai-official/grok version` both report 0.2.114.
The version was written out in three places, which is how they came to
drift:
- `repository/http_upstream.go` (OAuth traffic through the CLI proxy)
- `pkg/xai/billing.go` (billing and quota probes)
- `service/openai_gateway_grok.go` (gateway request headers)
`xai.CLIClientVersion` is now the single source, and the other two build
their own client identity from it, so the next bump is one line.
`internal/pkg/xai` is a leaf package both layers already import, so this
adds no cycle. `CLIUserAgent` derives from the same constant.
Checked against the real 0.2.114 binary rather than the version feed
alone: the `x-grok-client-version` header name, the `xai-grok-cli`
token-auth value, and the version string all match what the CLI sends.
The pinned value is also the floor an operator override must clear, so
some fixtures carried a meaning relative to it rather than a fixed
number. Those were moved, not search-and-replaced:
- the "valid override" case now uses 0.2.115-alpha.1; at 0.2.95-alpha.1
it would fall below the new floor, be dropped, and stop testing
acceptance at all
- the prerelease-at-the-minimum case tracks the pin to 0.2.114-beta.1,
or it becomes a copy of the below-minimum case
- three of the five malformed-semver entries sat below the new floor and
would have been turned away for being old, not for being malformed
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All four pass on a quiet box and fail on a loaded one, each for its own
reason. None of them is testing the clock, so none of them should be
failing on it.
- ollama_cloud_usage_test.go: the second caller was released with
`close(release)` right after its goroutine was started, not after it
had reached the singleflight group. When the first refresh won that
race, the second became a new singleflight execution, re-read the
account, saw the LastAttemptAt the first one had just written, and
came back with the 30-second manual-refresh 429 at line 685. It now
counts account loads and waits for the second caller's own load,
which happens right before it joins the group. Adds a counting
GetByID to the test repo.
- gateway_hotpath_optimization_test.go: a 20ms sleep was meant to let
all 12 callers reach the cache before the loader was released. A
caller that arrived after the load had finished got a hit, not a
miss, so the miss count came out 11 of 12. It now waits on the miss
counter itself, which is the value the test asserts on.
- token_refresh_pool_health_test.go: the floor was `configuredSpacing`
minus 10ms, i.e. 40ms out of 50ms. Each start timestamp is taken
after the rate gate releases the goroutine, so scheduler delay can
compress one observed gap with the gate behaving correctly — seen at
37ms and again at 13ms. The floor is now a tenth of the configured
spacing. Measured with providerQPS=20, 8 attempts, concurrency 2:
gate at 50ms gives a minimum gap of 49.97ms, gate at 0 gives 22µs.
So an unpaced gate sits three orders of magnitude under the 5ms floor
and is still caught, while jitter has room to move. A comment warns
against replacing this with an assertion on the total span of the
starts: the span is set by how long each attempt takes under the
concurrency limit, not by the gate — 471ms paced against 241ms
unpaced — so a span check passes with the gate disabled.
- prompt_guard_test.go: the bound only has to show the failover shared
the first endpoint's 70ms deadline and did not take the second
endpoint's own 500ms one. An unshared deadline lands near 535ms, so
350ms still fails loudly (seen: 224ms against a 180ms bound).
Tests only; no product code is touched. Each fix was checked in both
directions: it passes with the behaviour intact, and it still fails when
the behaviour is broken on purpose (for the QPS one, by swapping the
shared rate gate for a zero-interval one).
deploy/.env.example documents POSTGRES_MAX_CONNECTIONS,
POSTGRES_SHARED_BUFFERS, POSTGRES_EFFECTIVE_CACHE_SIZE and
POSTGRES_MAINTENANCE_WORK_MEM, with notes on how to size them — but no
compose file ever passes them to the postgres container. A user who sets
them in .env gets nothing, silently.
Wire them into the postgres command in deploy/docker-compose.yml. The
fallbacks are the postgres:18 stock defaults (100 / 128MB / 4GB / 64MB),
so a deploy that does not set the variables behaves exactly as before.
Checked with postgres:18-alpine: with the variables unset, SHOW gives
the stock values; with them set (1024 / 1GB / 6GB / 128MB), SHOW gives
the set values.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #4506 fixed this in deploy/docker-compose.yml, but the same broken
form is still in docker-compose.dev.yml and docker-compose.local.yml.
The redis command is one quoted script given to the inner sh -c, and
compose keeps the newlines inside the quoted string, so redis-server on
the first line runs as a complete command with no flags at all. The
--save / --appendonly / --appendfsync lines are silently never applied,
and ${REDIS_PASSWORD:+--requirepass ...} is dead too — redis takes no
password even when REDIS_PASSWORD is set.
The fix is the same trailing `\` line continuations as #4506, with the
same comment, so the three compose files read the same way.
Checked with both files on redis:8-alpine, REDIS_PASSWORD set. Before:
PING with no auth said PONG, appendonly was "no", save was the stock
"3600 1 300 100 60 10000". After: no-auth PING gets NOAUTH, appendonly
is "yes", save is "60 1". With REDIS_PASSWORD unset the server still
starts open, as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Building the image for linux/amd64 on an arm64 host (Apple Silicon)
kept failing at 'go mod download' with 'unexpected EOF'.
Two changes:
1. The builder stages had no --platform, so the Node and Go toolchains
ran under QEMU emulation of the target arch. Pin frontend-builder
and backend-builder to $BUILDPLATFORM and cross-compile: add
TARGETOS/TARGETARCH args and set them on 'go build'. The binary is
CGO_ENABLED=0, so this is a clean pure-Go cross-compile - much
faster, and the emulated networking that dropped module fetches
with EOF is gone. The frontend output is JS (arch-neutral), so it
is safe to build on the host arch too.
2. Add go module and build cache mounts, so a retry after a network
blip goes on from where it stopped instead of starting over.
The runtime image and app behavior are unchanged.
The redis command is one quoted script given to the inner `sh -c`.
Docker compose keeps the newlines inside the quoted string, so
`redis-server` on the first line ran as a complete command with no
flags at all, and --save / --appendonly / --appendfsync after it were
silently never applied (`redis-cli CONFIG GET appendonly` said "no").
Trailing `\` line continuations fold the script back into one command.
Checked with redis:7-alpine: appendonly is now "yes" and save is
"60 1".