Tale v0.5.25
0.5.25 is a fix release on the 0.5 line, and this one stays inside the patch promise: no schema migration, no configuration-file change, no new environment variable, and the API contract stays at 1.6.0. It closes the sixth external API evaluation's findings, every one a gap between what the surface does and what it said rather than a wire defect: the OpenAPI document and the reference now say what they were silent on, three refusals carry what a client can branch on, Strict-Transport-Security moves from 180 days to one year on every producer of the header, and the web site's changelog prerenders by size instead of by count. The proxy image changes for the one-year value on the edge's own refusals, and a plain tale deploy does not roll it — see Upgrading. The Known issues from 0.5.20 are unchanged; the proxy verification gap 0.5.24 listed is closed, and this release's own is listed with the rest.
Highlights
The sixth API evaluation pass (#3337)
The sixth external black-box evaluation of the REST and MCP API ran against 0.5.24 (about 700 requests across nine lanes, no backend 5xx; the proxy's access log for the evaluation hour agrees: 1,867 platform requests, none answered 5xx) and reported 0 S1, 0 S2, 4 S3 and about a dozen S4 findings — the first round with no blocker and no connectivity caveat. Every wire fix 0.5.24 promised held, so did all four S1 fixes from the earlier rounds, and the round-E verdict on the slow IPv4 handshake (the evaluating machine's tunnel, not the platform) was reconfirmed with a five-leg control bound to the physical interface. Every finding was second-confirmed from source before a fix was designed; every one that stood was a matter of contract precision, closed at the source:
- The REST send is text only, and now says so.
GET /modelslistscapabilities.visionand avisiontag as facts about the model, butPOST …/messagestakescontentalone: a data URI pasted there reaches the model as text and is answered as text, settledcompleteand billed, with nothing on the wire marking it. The field descriptions, the send body and the reference say exactly that, and an image input over REST is recorded as contract debt with its design (anattachmentsfield naming staged uploads, through the same gate the app's composer uses). No content-sniffing refusal was added: a data URI in a text field behaves the same on every text surface, the app included. POST /conversations/syncnames its precondition.externalContactIdmust be theexternalIdof a contact that already exists — the snapshot links, it never creates — and a snapshot never re-homes a mirrored conversation (409CONVERSATION_CONTACT_CONFLICT). The body schema describes the field, the operation namesCONTACT_NOT_FOUND,CONTACT_AMBIGUOUSand each attachment refusal (ATTACHMENT_NOT_OWNED, another service user's upload in the same organization, is a 403 where the description said 400 "otherwise"), andreplyConstraintsis described as what a person's Inbox reply may carry, enforced when that reply is written and never against a snapshot.- MCP refusals a client can branch on. The MCP page listed
INVALID_PARAMSandUNKNOWN_METHODas string codes; the transport answers those cases as JSON-RPC-32602and-32601before any tool runs, and the page now says so. The second confirmation found the hole: a blankname,runIdorquerypassed the advertised schema and reached the engine's ownINVALID_PARAMS, so the schemas carryminLength: 1(and\Sfor the trimmedquery) and a blank is the same-32602a missing field gets. The developer gate onsave_automation,deploy_automationandset_triggeranswered{error, hint}with nocode; it carriesFORBIDDEN_DEVELOPER_SETTINGS, the store's own role refusal. A call the request budget refuses inside a batch is documented:-32000withdata.retryAfterMsin its own slot, HTTP 200, noRetry-After. - One year of HSTS.
Strict-Transport-Securitywas 180 days; it ismax-age=31536000on every producer of the header — the platform app, the API doors, the web and docs sites, and the proxy's own edge refusals — still withoutincludeSubDomainsorpreload, because self-hosted deployments run on varied domains (an apex with plain-HTTP siblings included) and a preload listing is the operator's own submission. The hardening page names the lifetime. - Precision in the reference. The browser-session import 403 no longer names an environment variable to a caller who cannot act on it; it points at
GET /meandcapabilities.deploymentEditor.testsCheckedAtsays whatnullbeside atrueorfalseverdict means (a verdict recorded before 0.5.24 kept the time). The reply-cap text says which models carry the 2,048-token thinking floor (the thinking-budget dialect) and what a few-hundred-token cap does on an effort-level reasoning model (the GLM and DeepSeek families): an empty,complete,length, billed reply — so give a reasoning model a few thousand tokens or lowerreasoningEffort. Each keyset list's ownlimitmaximum (100 or 200, 500 for task comments) is stated with the loop; theX-Tale-Api-Versionsentence names/api/v1and the webhook doors, and the keyless doors that carry none; the delivery queue is a keyset page underdeliveries; the webhook tutorial says upfront that REST has no authoring door (POST /automationsanswers 405), so the prerequisite is met in the app or over MCP.
The web site's changelog prerenders by size, not by count (#3337)
The prerendered changelog on the web site carried a fixed twelve release bodies. Six consecutive 0.5.x fix releases with 17–24 KB of notes each pushed those twelve to 308 KB, over the 300 KB the prerender suite holds the page to, which turned the nightly E2E run on main red after the 0.5.24 release. The cut is now a byte budget — the newest bodies within 72,000 characters of Markdown, at most twelve, the newest always — computed by one function the page and the suite share, so the server and the first client render agree and hydration matches. At the time of the fix that is four bodies and about 217 KB; every release stays in the stream and the rest mount on hydration from the manifest the bundle already ships, so a visitor sees the same page.
Behaviour changes
Strict-Transport-Securityon every HTTPS response readsmax-age=31536000(wasmax-age=15552000): the platform app and the API doors with the platform image, the web and docs sites with theirs, the proxy's own refusals once the proxy is recreated (see Upgrading). Still noincludeSubDomains, nopreload.- An MCP
tools/callwith a blankname,runIdorquery(whitespace alone included) is refused at the transport as JSON-RPC-32602, exactly like a missing field, and no tool runs; it used to reach the engine and come back as anINVALID_PARAMSrefusal in the result. - The MCP developer-gate refusal on
save_automation,deploy_automationandset_triggercarriescode: "FORBIDDEN_DEVELOPER_SETTINGS"besideerrorandhint. - The browser-session import 403 for an account outside the deployment-editor allowlist reads "Your account is not on the deployment editor allowlist; GET /api/v1/me answers capabilities.deploymentEditor for this key" — the environment variable's name left the sentence (the OpenAPI document still names it where it documents the gate).
- The web site's changelog, in each language, prerenders only the newest release bodies within the byte budget; the rest render on hydration.
API contract changes
The OpenAPI document stays at 1.6.0: no operation, field, status or error code changes, and the contract fingerprint (operations and schema shapes; descriptions are outside it by design) is unchanged. What moved is what a client reads in the document and what three refusals carry.
Changed
Strict-Transport-Security—max-age=31536000on every HTTPS response of every door (wasmax-age=15552000).- MCP
tools/list—nameon the automation tools andrunIdonget_runandcancel_runcarryminLength: 1;queryonsearch_catalogcarriesminLength: 1andpattern: \S. A blank value answers JSON-RPC-32602at the transport and no tool runs. - MCP
save_automation,deploy_automation,set_trigger— the developer-capability refusal carriescode: "FORBIDDEN_DEVELOPER_SETTINGS"(waserrorandhintalone);isError: trueas before. POST /browser-sessions/import— the 403 for an account outside the deployment-editor allowlist no longer namesTALE_DEPLOYMENT_CONFIG_ADMINS; the code is unchanged.
Documented, unchanged on the wire (en, de, fr): POST /conversations/sync requires an existing contact (404 CONTACT_NOT_FOUND, 409 CONTACT_AMBIGUOUS), never re-homes a mirrored conversation (409 CONVERSATION_CONTACT_CONFLICT), answers 403 ATTACHMENT_NOT_OWNED for another service user's upload, and replyConstraints bounds a person's Inbox reply, never a snapshot; content on both chat sends is text only, and ChatModel.capabilities.vision and tags describe the model, not an input this surface offers; maxOutputTokens bounds a reasoning model's reasoning too, with the 2,048 floor on the thinking-budget dialect alone and no floor on an effort-level model; testsCheckedAt is null beside a verdict recorded before 0.5.24 kept the time; each keyset list's limit maximum is declared per list (100, 200, or 500 for task comments) and a larger value is clamped; X-Tale-Api-Version rides every response from /api/v1 and the webhook doors, and the keyless doors (/api/health, /status, /status.json, /openapi.json) carry none; the delivery queue is a keyset page under deliveries; on the MCP endpoint a schema miss or an unknown tool is -32602 or -32601 with no code, an in-batch budget refusal is -32000 with data.retryAfterMs, and FORBIDDEN_DEVELOPER_SETTINGS is named; the hardening page states the HSTS lifetime; the webhook tutorial states that REST has no authoring door.
Known issues
- Unchanged from v0.5.20, where each is described in full: the
es/co-ccColombian cédula detector still ships switched off and a locale-agnostic PII toggle still widens national-ID matching to every locale (the PII library is untouched in this range); thinking-block replay on the native Anthropic connector is not done and the live Max-plus-tool-call check is still owed;rag_searchembedding calls inside a harness turn are unmetered; the product edit dialog cannot clear a field; the app's skill editor still carries the retiredprivatevisibility. - The
x-tale-paginationextension is a declaration on the OpenAPI document; generated clients that do not read vendor extensions still branch on the two cursor names untilcursoris retired. - The proxy change was not exercised on a running edge. It is one line — the
max-agethe entrypoint writes into the edge's own JSON refusals — andservices/proxyhas no test of its own; the platform's headers, which every other response carries, are covered by the server suites. The 0.5.24 edge rules, which 0.5.24 listed as exercised only in a local container, have served the hosted platform since 2026-09-14 and the sixth evaluation drove them on the wire (BODY_LENGTH_MISMATCH, the uncompressedHEADlength, the dot-segment fold-in), which closes that item. - A reply-language directive is a directive: the sixth evaluation measured six German replies out of six where the fifth measured four, but a model may still answer in the prompt's language and nothing on the wire marks a slip.
- No image input on the REST chat send. A
visionmodel reads an image over REST only on a thread the app continued with an image attachment; the design of anattachmentsfield on the send is recorded as contract debt. - No REST door authors or deploys an automation —
POST /automationsanswers 405 by design. Build and deploy in the app, or over the MCP endpoint'ssave_automationanddeploy_automation; the REST key lists, reads, runs and wires triggers. - There is no single-file read on
/projects/{id}/files: a poller waiting for one file'sindexingafterretry-indexingwalks the folder listing. - The app's zip upload of a skill bundle rewrites the bundle and moves
updatedAteven when the zip is byte-identical, wherePUT /skills/{slug}writes nothing. - A tool call the reply cap cut keeps
input: {}on the storedtool-callpart; the raw text the model emitted is not on the transcript, so the timeline cannot show what was asked. - Folder names written before 0.5.24 keep their bytes; a sync engine's hub-path lookup can create an NFC twin beside a legacy NFD folder. No backfill ships.
- Behind a Docker-published port, every IPv6 client arrives as the bridge gateway's address and shares one per-address rate-limit bucket and one audit address until the daemon runs with
ip6tablesand the proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page.
Migration notes
- No platform migration and no knowledge-database migration in this range; the schema is 0.5.24's. The
task_labels_project_id_name_keyconstraint 0.5.22 kept for its rolling deploy is still in place; dropping it is a follow-up migration, not part of this release. - No configuration-file change and no new environment variable;
.env.exampleis untouched. - The proxy image changes (the one-year
Strict-Transport-Securityon the edge's own JSON refusals). The proxy is in the stop-gated tier: a plaintale deployleaves a running proxy untouched and names it in a hint, so pass--stopto take the new value (a brief downtime whiledb,object-storeandproxyrecreate); until then the edge's own refusals — the dot-segment 404,BODY_LENGTH_MISMATCH,UPSTREAM_UNAVAILABLE— keep the 180-day value while every response the platform answers carries one year. An own-Compose deployment pulls the newtale-proxytag. - A browser that sees the new header pins the origin to HTTPS for a year from that response (it was 180 days); moving a deployment's public origin back to plain HTTP inside that window is refused by browsers that saw it, as before, for longer.
- The platform, proxy, web (the changelog budget and the shared security headers) and docs (the shared headers and the updated pages in all three languages) images carry source changes. The db, sandbox, sandbox-runtime, sandbox-buildkitd, sandbox-egress and sandbox-llm-gateway images have no source change in this range. The CLI has no source change either; the release executables are rebuilt at this commit and report 0.5.25.
Upgrading
-
On the 0.5 line (0.5.0 – 0.5.24):
tale update tale deploy --stop
--stoprecreatesdb,object-storeandproxyso the edge's own refusals carry the one-year header (a brief downtime); a plaintale deployapplies everything else and leaves the running proxy on 0.5.24's value. Nothing migrates. -
Managed deployments move by pinning the CLI and the runtime to this release's commit, preparing a new bundle and applying it with the pinned CLI — see Managed deployments on the CLI install page. As since 0.5.24, the bundle's backend-local phases run under the interpreted CLI (
cli/tale.mjs) that thesetup-cliaction andbun run --filter @tale/cli buildproduce beside the executable; the executable from the release page has no interpreted bundle beside it and cannot prepare a managed bundle. On a Linux x64 host whose CPU lacks AVX2, passlinux-baseline: 'true'to thesetup-cliaction so the bundle embeds the baseline executable. -
New install:
curl -fsSL https://raw.githubusercontent.com/tale-project/tale/main/scripts/install-cli.sh | bash mkdir tale-05 && cd tale-05 tale init tale deploy
On a CPU without AVX2 the downloaded executable aborts with
Illegal instruction; build it from source withbun run build:linux-baselineintools/cliinstead.
What's Changed
Full Changelog: v0.5.24...v0.5.25