2.14.1
Released
Fixes
Every investigation the newly deployed worker ran failed against OpenAI with
Invalid 'max_output_tokens': integer below minimum value. Expected a value >= 16, but got 12 instead.The worker asked for a 12-token answer becauseAI_MAX_RESPONSE_TOKENSis set as a Fly secret onquestify-worker— an app that predates this worker and still carries the secrets of the service that held its name — and a Fly secret takes precedence over the same key inapps/worker/fly.toml, which says 1200. OpenAI is the only provider that refuses a budget that small; Anthropic, DeepSeek and OpenRouter would each have returned a truncated fragment for an investigation the user had already been charged for, with nothing marking the row failed and nothing refunding it.getWorkerEnv()now floors the budget at the same 1200 the narrative route already floors it at, and says so in the log when it does. (#579)
Technical changes· 5 entries for database, tooling, CI, tests and docs
CI/CD
The worker deploy now refuses to ship when a Fly secret shadows a key the Fly config's
[env]block declares. The machine gives no sign of which value it used, so a shadowed key leaves the config describing a deployment that does not exist — which is howAI_MAX_RESPONSE_TOKENSread 1200 in the repository and 12 on the machine. It names the key and the one command that removes it. (#579)The Release workflow no longer fails a worker deploy that succeeded.
apps/worker/fly.tomlasks Fly for theimmediatedeploy strategy, which is right for a one-machine poller with no traffic to drain and meansflyctl deployreturns once Fly has accepted the update rather than once the machine is running — so the check that followed it read the machine mid-swap, inreplacing, and called that "no machine is in the 'started' state". It failed the 2.14.0 release on a worker that reachedstartedseconds later, and it would have failed every release the same way. The check now reads the machine list until one is started — twenty readings fifteen seconds apart — and still fails when none ever is, because a worker that silently never runs is the whole of #556. (#579)
Tests
Covered
getWorkerEnv(), which nothing had: the response budget defaults to the floor, a larger configured value is kept, and a value below the floor — or an empty one, which the schema turns intoundefineddespite its default — is raised and warned about rather than sent to a provider. (#579)Pinned the deploy's shadow guard to the config it guards: that
[env]is whereAI_MAX_RESPONSE_TOKENSis declared, that the guarded keys are read out of$FLY_CONFIGrather than listed in the workflow, and that a shadowed key exits non-zero instead of warning. (#579)Pinned that the machine check reports the wait it actually performs. The loop takes its first reading immediately and sleeps only between attempts, so
attempts * intervaloverstated the budget by one interval in the one message anyone reads when the gate fails. (#579)