Tale v0.5.26
0.5.26 is a patch tag on the 0.5 line that carries more than its fixes — read it as a minor. It ships two forward-only schema migrations, three optional environment variables, an API contract that moves from 1.6.0 to 1.9.0 in three additive steps, a new tale-ui-docs image and CLI changes for managed deployments; nothing in it is breaking, no configuration file changes shape, and the upgrade is still tale update followed by tale deploy --stop (the proxy image changes — see Upgrading). Fourteen changes since 0.5.25: the seventh external API evaluation's findings closed at their root, the platform's reusable components folded into @tale/ui with a design-system site of their own, a navigation memory and tabbed automation pages in the app, a notification export for connected applications, a hostname migration and a container-name prefix for managed deployments, and aggregate analytics that stays off unless an operator switches it on.
Highlights
The seventh API evaluation pass (#3353)
The seventh external black-box evaluation of the REST and MCP API ran against 0.5.25 and reported 3 S2, 24 S3, 34 S4 and 9 lead-lane findings. Every finding was second-confirmed against the source; all three S2 are real defects, fixed at the root with regression tests and integration lanes, and the rest split three ways: code defects fixed, contract-precision items closed in the OpenAPI document and the en/de/fr reference, and thirteen product gaps recorded in the contract debt ledger with their pay-down (listed under Known issues).
- A mirrored conversation could re-home onto a recreated contact.
DELETE /api/v1/contacts/{id}frees the contact'sexternalId, and the nextPOST /api/v1/conversations/syncre-resolved the conversation'sexternalContactIdonto whichever contact now carried it, so an unrelated contact inherited the history. A content snapshot for a conversation whose contact is in the trash answers 409CONVERSATION_CONTACT_TRASHEDand applies nothing; adeleted: trueteardown still closes the mirror; the receipt reportscontactStatus(active,trashed,missing) and the boundexternalContactId; an existing conversation's contact is never re-resolved. - Documents released from a deleted project stayed unfindable. A project delete in
detachmode (and the app's delete) released the documents without re-stamping the corpus rows retrieval pre-filters on (project_id,team_ids,folder_path), and aretry-indexingreported success while changing nothing. Both delete doors re-stamp the released documents after the commit, and the indexer's skip-unchanged branch re-stamps off the current scope, so a retry heals a drifted stamp. - Six concurrent task starts began five runs. The start guard was a check-then-act inside a SERIALIZABLE transaction whose snapshot froze before the advisory lock, so every racer read "no live run". The rule now lives in the schema: migration 0102 collapses existing live duplicates onto the earliest and adds the partial unique index
automation_runs_one_live_per_task; the REST door starts under READ COMMITTED, and a loser's unique violation is reconciled by re-reading the winner (already_running, never a second billable run). The integration lane starts six at once and counts one run. - Runs say why they failed.
Run.failureCodeandRunSummary.failureCodecarry one enum over the engine's, the provider's and the agent turn's causes —node_error,connector_error,credit_exhausted,rate_limited,context_length,turn_crashed,deadline,ask_expiredand twenty more — written when a run lands onfailed(migration 0103); a cancel clears the stale parkdetailand answers the run'sstatusbesidecancelled;GET /api/v1/meanswerscapabilities.developer. - Indexing names its cause and knows what is terminal.
indexing.errorCodeis a closed vocabulary of thirteen values:unsupported_type,image_no_vision,empty,not_textandmalformedare terminal (unsupported— a retry reproduces the answer and is refused as terminal), the rest readfailedwith the job retrying (embedding_upstream,indexer_error,index_rebuilding, …). NUL bytes are stripped before the PII policy and the chunker (a.txtthat was really binary used to fail after the embedding was paid for); the storedmimeTypeis resolved from the extension first (json,yaml,ymlmapped);GET /api/v1/projects/{id}/files/{documentId}reads one file row with itsindexingstate — the single-file read 0.5.24 and 0.5.25 listed as missing. - The crawler explains an empty page. A page the crawler looked at and stored nothing for is
unsupported_contentinstead of a row that reads unfetched;X-Robots-Tag: noindexis honoured (robots_noindex); a TLS handshake failure istls_error; a same-host link naming a port is never dialed; a scan that stored nothing ends onerrorwith its reason instead ofactive;Website.statusis an enum and?status=refuses a value outside it. - Contract doors hold their line. A blank or over-long
Idempotency-Keyanswers 400INVALID_HEADER(a blank header no longer reads as "no key");POST /api/v1/contacts/bulkjudges rows one by one;contacts?source=is the closed set;products.imageUrlis held to the crawl-target host rule as a string, never fetched; a contact, product or projectPATCHthat changes nothing writes nothing;If-Matchon the document update answers 412PRECONDITION_FAILEDwithdata.etag; the REST 429 carries a sentence and arequestIdlike every other refusal; a body string with a NUL or a lone surrogate is refused with its path. - Chat, MCP and the edge. A reply that settles with no text carries no empty text part (a reasoning-only cut keeps its
reasoningpart); a tool call the reply cap withheld says whether its arguments were cut or complete; every MCP string argument refuses a blank — whitespace alone included — at the transport (JSON-RPC-32602); a malformed HTTP/1.1 chunked request body answers 400BODY_CHUNK_MALFORMEDat the edge instead of the 502 outage envelope with retry advice;//api/v1/…answers the JSON 404;robots.txtno longer pairsAllow: /withDisallow: /and carves out the developer pages;bluetoothleft the Permissions-Policy, and with it the one console warning every page load carried. - A cloud-drive folder sync files its documents. A OneDrive or Google Drive sync import whose files the hub already knew — an earlier one-time import at the root, a directly picked file — left them at the root with no folder row and nothing to stop the sync from. The sync root now exists from the moment the sync configuration does, and an adopted, unchanged document is moved into the folder its selection names. A live organization heals on its next scheduled sync.
One design system, two packages, and a site to read them (#3351)
@tale/uiholds every reusable platform component. 324 files moved out of the platform app intopackages/ui(152 new export subpaths), their message keys into the package catalog, tests and stories alongside; the platform keeps only the wrappers that carry business logic — org-branded logos, the ability-gated tab strip, the app sidebar, error-scope and accent-colour providers — and imports everything else through@tale/ui/<subpath>.@tale/marketing-uiis the marketing language, split out of@tale/uiand the web site — site chrome, primitives, feature frames, product-demo frames, entrance motion, marketing tokens — layered on@tale/ui; routing stays the host's.- Both packages install from GitHub with Bun. Bun cannot install a package from a subdirectory of a git repository, so every push to
mainrepublishes each package as a root-level snapshot on thedist/uianddist/marketing-uibranches, and every release pins the same snapshot with aui-v<version>and amarketing-ui-v<version>tag:"@tale/ui": "github:tale-project/tale#ui-v0.5.26"is reproducible, the branch moves. The consumer contract is inpackages/ui/README.md. - docs.tale.dev wears the app language: a navigation rail, one sticky strip with the breadcrumb trail and the page actions, a mobile header with a drawer, neighbour cards and a compact footer — no marketing chrome. Three defects went with it: ancestor rows claiming
aria-current, a drawer that needed two Escapes to close, an invisible scrim after a viewport change. - ui.tale.dev is a new service,
services/ui-docs(port 3003, imagetale-ui-docs, its own compose file): the front page in the marketing language, every/docs/*page in the app language, markdown content with live examples backed by the real components, search, prerender and the SEO artifact set, and a container test in the release gate. Nineteen pages seed it; the contributor-setup page points at it.
The app remembers where you were (#3347, #3345, #3338, #3346, #3340)
- Each rail section reopens the place you last had open there — the task board you were on, not the projects list — and clicking the section you are already in goes to its default entry, the one-click way out of a deep page. The memory is per tab (two tabs on different projects do not overwrite each other), seeded across restarts, and expires eight hours after your last navigation; a deleted project or automation forgets its place. Section roots do not redirect, so a shared
/projectslink still means what it says. The Knowledge rail gained its missing knowledge-entries item, and a deleted project's page carries a link back to the list. - Automations are built like projects. The list ends its title row in a divider above the toolbar and its button reads Create automation; an automation's page has three tabs — Editor, Versions, Runs — with the version picker, Deploy this version, the run verbs and Save/Discard at the right end of the tab strip; the bare URL forwards to
/editor, the canvas version lives in?version=, an unknown slug renders the not-found state instead of loading forever, and belowmdthe verbs sit in the floating dock. The switcher in the breadcrumb lists every automation in the organization even from inside a project — organization-level entries above a divider, project-specific ones below — keeps the tab you were on, and resets the editor's version, draft and inspector when the automation changes. - A notification link survives login. Opening a protected task, document or conversation link in a signed-out browser used to land on a bare login page; both dashboard guards now carry the full destination through the login return, and expired-session recovery keeps query values and fragments too.
- Loading placeholders match what they replace. Skeletons mask the real controls and reuse the loaded layouts for forms, tables, cards, chat, tasks, project tabs and document previews — no jump when the data lands — and a quick route transition no longer flashes the top progress bar; the masks follow the theme and the reduced-motion preference.
A notification export for connected applications
GET /api/v1/notifications/sync lets an organization owner or administrator mirror every notification visible to a verified member: the personal and the organization stream, walked independently (recipientEmail and stream are required; cursor, limit 1..100 and locale optional) with signed cursors scoped to the recipient, the stream and the organization. Each row carries a stable, org-prefixed id, a version that moves with content or read state, title, body, read, createdAt and the path the bell itself would open — agent-question task and run links included — relative to the deployment's browser origin, in the requested locale or the organization's default. The export never marks a notification read and never deletes one; a missing, disabled, unverified or ambiguous recipient answers an empty completed page, and a 304 answers an unchanged ETag. Contract 1.8.0.
REST task intake binds the Setup folder by name (#3339)
Folder-driven automations — the VAT return desk among them — read a task's externalUrl as the id of the project's Setup folder, and the app's intake resolves that folder by name; the REST door refines externalUrl to an absolute URL and had no way to bind a folder, so an external desk on an API key could not use them at all. POST /api/v1/projects/{id}/tasks takes an optional setupFolderName: the project's root folder of that name (matched without regard to case) is resolved inside the intake transaction — on the create and again on every repeat — and stored as the task's externalUrl; a name no root folder carries answers 400 SETUP_FOLDER_MISSING and creates nothing, and sent beside externalUrl it is refused as INVALID_BODY. Contract 1.7.0.
A managed deployment can change its hostname, and name its containers (#3343, #3344, f4ebf4d)
Changing a managed Tale deployment's origin used to fail against the retained bootstrap, email-attestation and OAuth client journals. identity.migrateOriginFrom: "https://old.example.org" in the deployment declaration binds the exact previous HTTPS origin: the CLI re-authenticates the retained account, verifies the existing client secret and updates only the journals' origin bindings — identity ids and secrets stay — and the native configuration receipt migrates with it, keeping the organization id and slug and verifying every resource through the normal plan and readback flow. Missing, pending or unrelated journals refuse; an interrupted migration resumes with a mixture of completed old- and new-origin journals; a configuration write interrupted at the new origin resumes its exact pending plan, and a pending receipt at the old origin blocks the migration. After the ready receipt, drop the declaration and export consumer configuration for the new issuer; reversing is the same flow with the origins swapped. The CLI install page and the CLI README carry the walk-through.
A managed deployment can also name its containers so an environment is recognisable in a container listing: optional runtime.containerPrefix — a lowercase hyphenated slug of at most 40 characters, north-desk-prod say — gives every managed service the container name <prefix>-<service> (north-desk-prod-db, north-desk-prod-backend-api) while the deployment's identity, Compose project, state paths and named volumes stay what they were; service DNS names do not change. Adding, changing or removing the prefix recreates the managed containers, which can briefly interrupt service, and readiness requires the observed container names; a rename that is interrupted replays, an unchanged one is a no-op, and a naming conflict refuses. It stays one complete managed runtime per Docker daemon — the prefix allocates no separate ports, sandbox networks or host workspaces.
Aggregate analytics, off unless you switch it on
The platform, the web site and the docs site can report aggregate traffic to a self-hosted Umami collector. It is an opt-in per deployment: set UMAMI_URL, UMAMI_WEBSITE_ID and UMAMI_PROXY_TOKEN together — all three, read at runtime, so clearing the website id switches it off without a rebuild; unset, nothing is loaded and nothing is sent. A first-party proxy serves the tracker and forwards only pageviews — and, on the marketing site, completed contact and demo submissions without their contents — with the token held server-side; a private platform route is reported as its template with placeholders for organization and resource ids; no cookies, no persistent identifiers, no page titles, query strings, form contents or session replay; Do Not Track and Global Privacy Control disable it. The edge sets X-Analytics-Client-IP from the client address it trusts on the platform and docs upstreams, so a browser-supplied value never counts. The observability page has the rollout check; the privacy policy on tale.dev and the docs' legal page describe what is collected.
Self-hosted providers on private addresses reach the sandbox gateway
A provider on a private address could not start a sandbox agent turn even with TALE_ALLOW_PRIVATE_PROVIDER_HOSTS=1 set: the LLM gateway rejected the upstream URL. The platform now sends the gateway's allow_private_network setting for a private host that opt-in admits; a custom provider URL passes the host policy before any gateway I/O and before a cached provision is reused; cloud metadata endpoints stay refused (the AWS IPv6 endpoint included), and a public provider keeps the gateway's default safeguards.
Behaviour changes
- Clicking a rail section opens the place you last had open there (an eight-hour memory, per tab); clicking the section you are in goes to its default entry.
/projectsand the other section roots still render their lists when opened by URL. - Automations: the list's button reads Create automation (was New automation); an automation opens on
…/editorunder the Editor | Versions | Runs tab strip; a Versions row opens the editor at that version (?version=); a run page opens under the same strip with Runs lit; an unknown automation slug renders Automation not found. The breadcrumb switcher lists every organization automation from any project, in two groups. - A notification link opened signed-out lands on its task, document or conversation after login instead of on the dashboard; the destination keeps its query and fragment.
- Loading skeletons take the shape of the loaded page; a quick navigation no longer flashes the progress bar.
- A OneDrive or Google Drive sync creates its root folder with the sync configuration and moves adopted, unchanged documents into the selected folder on the next scheduled sync.
- A run that fails on this build answers
failureCodebeside its sentence; cancelling a parked run clears the park detail and the cancel answers the run'sstatus. - Six simultaneous starts of the same task's automation produce one run; any live duplicates the old race left behind are cancelled by migration 0102 at boot (the earliest run of each group is kept).
- Deleting a contact and recreating it with the same
externalIdno longer hands the old contact's mirrored conversations to the new one: a snapshot for the trashed contact's conversation is refused with 409 until the contact is restored or the mirror is torn down. - Documents a deleted project released (
detachmode) are findable in the hub again;retry-indexingon such a document re-stamps its scope. - Knowledge indexing: a document that cannot be indexed reads
unsupportedwith a terminalerrorCode, and a retry on it is refused as terminal; a text file that is really binary no longer costs an embedding before it fails;.json,.yamland.ymlare typed from the extension. - Website crawling: a page that stored nothing reads
unsupported_content, a page answeringX-Robots-Tag: noindexreadsrobots_noindex, a certificate failuretls_error; a same-host link naming a port is not dialed; a scan that stored nothing ends the site on Error with the reason (was Active). - A REST
PATCHon a contact, product or project that changes nothing writes nothing:updatedAtstays, no audit row, no event — a retry is free. - The REST 429 body carries a sentence in
errorand arequestId(errorused to repeat the code); the in-app doors are unchanged. - An
Idempotency-Keyheader sent blank or over 255 characters is refused with 400INVALID_HEADERand nothing starts (a blank used to read as no key). - MCP: a blank string argument — whitespace alone included — on any tool is refused at the transport as JSON-RPC
-32602and no tool runs (0.5.25 refused the empty string on three arguments). - A chat reply that settles with no text carries no empty text part; a tool call the reply cap withheld says whether its arguments were cut or complete.
- The edge answers 400
BODY_CHUNK_MALFORMEDto a malformed HTTP/1.1 chunked request body (was 502UPSTREAM_UNAVAILABLEwith retry advice) once the proxy is recreated (see Upgrading);//api/v1/…answers the JSON 404. robots.txtcarves out the developer pages instead of pairingAllow: /withDisallow: /; the/docspage links the developer guides and/openapi.json; the user menu's Documentation item opens the docs site; the app shell's<noscript>names both.Permissions-Policyno longer listsbluetoothon the platform, web and docs responses; the "Unrecognized feature: 'bluetooth'" console warning on every page load is gone.- docs.tale.dev reads in the app language (rail, sticky strip, a drawer on phones); the ancestor-row
aria-current, the two-Escape drawer and the invisible scrim are fixed. - With the three
UMAMI_*values set, the platform, web and docs sites load a first-party tracker and report pageviews; without them nothing changes. - A self-hosted provider on a private host, admitted by
TALE_ALLOW_PRIVATE_PROVIDER_HOSTS=1, starts sandbox agent turns. tale deployon a managed deployment honoursidentity.migrateOriginFromandruntime.containerPrefix; adding, changing or removing the prefix recreates the managed containers under their new names.
API contract changes
The OpenAPI document moves from 1.6.0 to 1.9.0 in three additive steps, each dated 2026-09-14; the Error.code enum grows from 145 to 149 values (BODY_CHUNK_MALFORMED, CONVERSATION_CONTACT_TRASHED, PRECONDITION_FAILED, SETUP_FOLDER_MISSING). Nothing was removed or renamed.
Added
- 1.7.0 —
POST /projects/{id}/taskstakessetupFolderName(optional; sent besideexternalUrlit answers 400INVALID_BODY); 400SETUP_FOLDER_MISSINGwhen no root folder of the project carries the name. - 1.8.0 —
GET /notifications/sync(recipientEmailandstreamrequired;cursor,limit1..100,locale): a keyset page (page,isDone,continueCursor,recipientId) of one member's personal or organization notifications, each with a stableid, aversion,title,body,path,readandcreatedAt; owner or administrator only; 200, 304 on an unchangedETag. - 1.9.0 —
GET /projects/{id}/files/{documentId}: one project file row with itsindexingstate, anETagand 304;Run.failureCodeandRunSummary.failureCode(present withstatus: "failed"on a run that failed on this build or later);Me.capabilities.developer;externalContactIdandcontactStatuson the conversation-sync receipt; 409CONVERSATION_CONTACT_TRASHEDon a content snapshot for a trashed contact;If-MatchonPATCH /documents/{id}→ 412PRECONDITION_FAILEDwithdata.etag;WebsitePage.lastErrorKindgainsunsupported_content,robots_noindexandtls_error; the edge's own 400BODY_CHUNK_MALFORMED.
Changed
- 429 —
erroris a sentence naming the wait and the envelope carriesrequestId(waserror: "RATE_LIMITED");code,Retry-Afteranddata.retryAfterMsare unchanged. Idempotency-Key— blank, or over 255 characters, answers 400INVALID_HEADERand nothing starts (a blank was read as no key).POST /contacts/bulk— rows are created independently: a row the schema refuses fails alone, undererrors[]asINVALID_BODYwith its field-namedissues, while every valid row lands (201); only the batch's own shape refuses the whole call.GET /contacts?source=— the closed set; a value outside it answers 400INVALID_QUERY.Website.status— the enumidle,scanning,active,error,deleting;GET /websites?status=refuses a value outside it with 400INVALID_QUERY.Product.imageUrl— held to the crawl-target host rule as a string, never fetched: a loopback, link-local, private-network or cloud-metadata host answers 400INVALID_BODY(private hosts admitted byTALE_ALLOW_PRIVATE_CRAWL_HOSTS, metadata hosts never).PATCH /contacts/{id},PATCH /products/{id},PATCH /projects/{id}— a body that changes nothing writes nothing and answers the current representation;updatedAtdoes not move.Automation.document.version— the schema declares the integer the wire always carried.indexing.errorCode— the closed vocabulary of thirteen values, present withfailedandunsupported;retry-indexingon anunsupporteddocument answers{"status": "skipped", "reason": "unsupported"}.POST /runs/{runId}/cancelandPOST /projects/{id}/runs/{runId}/cancel— answer the run'sstatusbesidecancelled, and a cancel clears a parked run'sdetail.- The MCP endpoint — every string argument on every tool refuses a blank, whitespace alone included, as JSON-RPC
-32602at the transport (0.5.25 heldname,runIdandquerytominLength: 1). - Chat
Messageparts — a reply that settled with no text carries no emptytextpart; atool-callpart the reply cap withheld says whether its arguments were cut or complete.
Documented, unchanged on the wire (en, de, fr): webhook delivery dedupe is by the delivery id, never the body, with the header precedence list on the parameter and the 202; automation authoring lives on the MCP endpoint, in the app and in tale deploy (the Automations tag and the reference row say so); accepted thread sends form one oldest-first queue shared by the whole deployment, worked in batches of up to five turns (the operator's WORKER_CONCURRENCY), so a burst completes in waves; /health on a deployment's origin is the proxy's own liveness and a path no route owns answers the app shell with 200 — monitor /status.json or /api/health; the crawler's ceilings (10,000 tracked URLs per site, a scan of at most 200 five-minute links, 25 MB and 30 seconds per page, a discovered page dropped after five failed scans) and that it honours robots.txt Disallow for the * agent while discovering, never for a URL you listed; website search is BM25 with score semantics; what startedAt means on a run; Contact.email and Product.imageUrl carry their formats; the compression floor sits in the document's description; the keyless jq recipe over the Error.code enum; the tutorial's key placeholder reads <api-key>.
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 changes were not exercised on a running deployment. The chunked-body rule (
BODY_CHUNK_MALFORMED) and theX-Analytics-Client-IPheader this release adds to the edge went into atale-proxyimage no deployment has run yet, andservices/proxyhas no test of its own; the manual register carries theprintfrecipe that provokes the refusal. The 0.5.25 gap (the one-year HSTS value on the edge's own refusals) is closed: the hosted platform's edge has answeredmax-age=31536000on its own 404 since 2026-09-14. - The rail's navigation memory awaits its manual round: restore, re-entry, expiry, a deleted target and two-tab isolation are manual boxes (
NAV-F16–NAV-F19,NAV-B6–NAV-B9); the automated suites cover the store and the rail's resolution, not the browser choreography. - The docs screenshots
automations-catalogandautomation-editor-canvasstill show the previous automations layout; the text beside them is current. - ui.tale.dev is not live at this tag.
tale-ui-docs:0.5.26exists from this release, but the site goes live with the operator's deployment of it; until then the link on the contributor-setup page does not resolve. Nineteen pages seed the site — one page per remaining component family is follow-up work — and the docs chrome is duplicated between the docs site and the design-system site rather than shared. - A reply-language directive is a directive: 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. - 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 part now says its arguments were cut, but the raw text the model emitted is still not on the transcript. - 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. - Recorded as contract debt by the seventh evaluation, each with its design in the ledger: a run carries no
usageor cost; approvals and asks have no REST twins (a run parked onwaitingFor: approvaloraskis decided in the app); a task cannot be archived or deleted over REST; a webhook bind does not say whether the deployedinputsschema admits a delivery; an exhaustedrepeatUntilis only a trace note;Websitecarries noscanStartedAt,<meta name="robots" content="noindex">is not honoured (the header is) and the crawler has no page cap, path filter or stop verb of the caller's; website search has no dense leg and its substring fallback (a knowledge database without ParadeDB) stampsscore: 0silently; noIdempotency-Keyon the task start, so a retry after the run finished starts another; no queue position on a queued send; a corrupt Office document (docx,pptx,xlsx,odt) still fails asindexer_errorand is retried five times where a PDF landsmalformed; no/.well-known/security.txt; no changelog feed on tale.dev; no SDK, collection or per-code table beyond theError.codeenum.
Migration notes
- Two platform migrations, both forward-only and rolling-safe, applied at boot in filename order inside the advisory lock while the previous image keeps serving:
- 0102
automation_runs_one_live_per_taskfirst collapses any duplicate live runs (queued,running,waiting) that share an organization, an automation, a project and a task subject onto the earliest-started run, marking the restcancelled— the race's own residue; a correct platform never produced them — then adds a partial unique index over that key. The previous image cannot violate it: it only ever starts the runs it already started. - 0103
automation_runs.failure_codeadds a nullable text column with no backfill: a run that failed before this release keeps its sentence and answers nofailureCode(read the absence as "unknown"). The previous image neither reads nor writes it. - No knowledge-database migration. 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.
- 0102
- Three new optional environment variables —
UMAMI_URL,UMAMI_WEBSITE_ID,UMAMI_PROXY_TOKEN— in the root.env.example(the platform) and in the web and docs sites' own. All three unset means analytics off, which is the default and the previous behaviour; no configuration file changes shape. A managed deployment carries them through its runtime environment when they are declared. - The proxy image changes — the chunked-body 400 and the
X-Analytics-Client-IPheader it sets on the platform and docs upstreams. 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 rules (a brief downtime whiledb,object-storeandproxyrecreate); until then a malformed chunked body still gets 0.5.25's 502 envelope. An own-Compose deployment pulls the newtale-proxytag. - A new image,
tale-ui-docs, joins the release matrix — eleven images, amd64 and arm64 — with its own compose file (compose.ui-docs.yml) and container test. It is not part of the platform stack; nothing pulls it unless you deploy the design-system site. - The platform (the backend, the app, and the message catalogs the notification export reads), proxy, web (the marketing-language package, the analytics opt-in, the privacy policy) and docs (the app-language chrome, the updated pages in en, de and fr) images carry source changes; the db, sandbox, sandbox-runtime, sandbox-buildkitd, sandbox-egress and sandbox-llm-gateway images have none. The CLI has source changes —
identity.migrateOriginFrom,runtime.containerPrefixand the analytics pass-through — and the release executables report 0.5.26. @tale/uiand@tale/marketing-uiare pinned by this release as theui-v0.5.26andmarketing-ui-v0.5.26tags on their snapshot branches; a consumer outside the monorepo installs"@tale/ui": "github:tale-project/tale#ui-v0.5.26".
Upgrading
-
On the 0.5 line (0.5.0 – 0.5.25):
tale update tale deploy --stop
The two migrations run at boot.
--stoprecreatesdb,object-storeandproxyso the edge carries the new rules (a brief downtime); a plaintale deployapplies everything else and leaves the running proxy on 0.5.25's. To switch analytics on, set the threeUMAMI_*values in.envbefore deploying; leave them unset to keep the previous behaviour. -
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. 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. A hostname change is a deployment withidentity.migrateOriginFromdeclared, as above. 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
- fix(platform): keep all automations reachable in the switcher by @yannickmonney in #3338
- fix(platform): let REST task intake bind the setup folder by name by @yannickmonney in #3339
- fix(ui): align loading skeletons with content layouts by @yannickmonney in #3340
- feat(platform): export recipient-scoped notifications by @yannickmonney in d1edf52
- feat(cli): migrate managed deployment hostnames by @yannickmonney in #3343
- fix(cli): migrate retained native configuration origins by @yannickmonney in #3344
- feat(platform): give automations the projects page structure by @yannickmonney in #3345
- fix(platform): retain notification destinations through login by @yannickmonney in #3346
- feat(ui): connect optional deployment analytics by @yannickmonney in 2d62456
- fix(platform): admit self-hosted providers to the sandbox gateway by @yannickmonney in eb50e2d
- feat(platform): return to the last place in each nav section by @Israeltheminer in #3347
- fix(platform): close the 2026-09-14 API evaluation's seventh-pass findings by @larryro in #3353
- feat(ui): fold the platform UI into @tale/ui and add ui.tale.dev by @yannickmonney in #3351
- feat(cli): name managed containers independently of storage by @yannickmonney in f4ebf4d
Full Changelog: v0.5.25...v0.5.26