Tale v0.5.33
0.5.33 carries 5 merged pull requests. Three of them are about who may hold an account on a deployment and what that account is worth: the account-creation route now closes the moment a deployment holds an account, an account an administrator provisions counts as a verified one everywhere at once, and a managed deployment can rename its deploy operator in place and declare a break-glass administrator whose password never reaches the deployment. The fourth closes the 2026-09-18 external API evaluation — the backend no longer stamps Sentry trace headers onto outbound requests, the crawler honours the robots group written for it by name, a webhook re-posted across scopes is refused instead of run twice, and a third concurrent site scan waits for a render slot instead of failing — and moves the contract to 1.15.0. The fifth reconnects two governance model pickers that had been empty since the AI-backend rewrite. One migration (application database 0107, nullable column and index, rolling-safe), one new environment variable for throwaway test stacks only, and nothing in the stop-gated tier: the upgrade is tale update followed by a plain tale deploy.
Highlights
Account creation closes once a deployment has an account (#3407)
POST /api/auth/sign-up/email answered anyone who could reach the backend. The proxy in front of a deployment refuses that route, but backend-api is also attached to each box's sandbox network so the in-sandbox connectors bridge can call its host door — and the proxy never sees a request that arrives that way. Measured on a test deployment, from a container on that network: 200, with a live session token for a brand-new account. Code running inside an agent session — a prompt-injected agent, or an automation body — could create accounts on a deployment whose edge refuses exactly that.
The auth before-hook now refuses the route with 403 SIGN_UP_CLOSED once the deployment holds an account, and keeps the two paths that exist: the first account over HTTP (the setup flow in the browser, or the managed CLI's bootstrap), and every later one server-side — an administrator's Settings > Members door and the deploy's own provisioning reach Better Auth as calls that carry no request and bring their own authorization. The refusal is logged ([sign-up] refused: this deployment has an account), because nothing else records the attempt. The login page already offered setup only while the deployment was empty; the backend now agrees with it, through one shared hasAnyUsers query so the page can never offer a screen the backend refuses.
A throwaway test stack that mints its own accounts over HTTP reopens the route with TALE_ALLOW_OPEN_SIGN_UP=true. The local dev orchestrator, the dev compose overlay and the backend integration check set it for themselves; a real deployment leaves it unset. The managed CLI learned the refusal too: a deploy whose bootstrap sign-up is refused takes back the journal marker it wrote a moment earlier — nothing was created — and says what to do instead of reporting an authentication failure: sign in as the break-glass administrator and set the operator password back to the one the deploy carries.
A provisioned account is a verified account (#3408)
Tale sends no mail and has no self-service sign-up, so an address could never become verified by anything a person or an administrator could do. The setup wizard's owner and every colleague added under Settings > Members started unverified and stayed that way; only enterprise SSO, SCIM, trusted headers or the CLI's operator-attested declaration ever set the flag. Everything that requires a vouched-for address therefore refused those people: identity issuance answered 403 IDENTITY_NOT_ELIGIBLE — after Tale's own login page had already sent them back to the application, so the relying party showed a generic failure — and conversation synchronization and the notification mirror skipped them.
Whoever provisions an account names its address, and on this platform that assertion is the only verification there can be — the same one the CLI records as operator-attested. Every account Better Auth creates here now arrives verified: the setup wizard's owner, a member added under Settings > Members, the deploy's own accounts. A directory-provisioned account (SSO, SCIM, trusted headers) is written straight to the table and keeps its provider's verdict. Connected applications read this as the email_verified claim on the identity Tale issues, so a colleague an administrator just added can sign in to them immediately.
Existing deployments heal themselves. After the numbered migrations and Better Auth's own, boot catches up the accounts this instance provisioned — a credential row is the proof that it issued the password — and logs how many it verified. It is not a numbered migration, because the application's .sql files run before Better Auth's tables exist and do not own them; it runs on every boot, so a rolling deploy cannot leave behind an account the previous image created while the new one was already up. A directory account has no credential row and is left alone. Both halves are proved against real Postgres in the backend integration check.
Five minutes to sign in. The identity provider signs the login and consent continuation with the same lifetime it gives an authorization code, so codeExpiresIn: 60 was also the window a person had to find a password and answer a second factor before the request expired and the provider's error page took over. It is now 300 seconds — still a single-use, PKCE-bound code, within the conventional ceiling. The CLI pins the backend's provider policy before it provisions a managed client, so its expectation moves with the platform: a deploy needs a CLI at least as new as the platform it deploys, which the managed bundle already requires.
A machine address for the deploy operator, and a break-glass administrator (#3406)
A managed deployment (tale deploy --bundle) signs in as its identity operator on every run, with the password alone. That had two consequences. An organization could not enforce two_factor_policy without breaking its own deploys: a password sign-in of a TOTP-enabled operator is answered with a challenge, and an operator with neither factor is held at the enrolment wall once its grace period ends — and the CLI stopped at both with one generic message. And the operator's user id anchors the retained state — the bootstrap and email-attestation journals, managed native client intents, operator-owned skills (which have no ownership transfer) and API keys — so a new account could not take over an existing deployment's deploys.
The way out keeps the account and gives it a machine address, so that people sign in with accounts of their own and choose either factor.
identity.migrateEmailFrom— the retained operator's previous sign-in address. Requiresbootstrap: "fresh"and must differ fromidentity.email. The CLI reads the retained account's current address backend-locally (it must be the declared or the previous one, and no other account may hold the declared one), signs in with it and proves the retained user id, journalsmigratingEmailFrom, renames the account through Better Auth's internal adapter in a write scoped so that only this exact update passes — guarded byemail = previous, so a concurrent change is refused rather than overwritten — ends every session of the account, signs in again with the new address and proves the same user id. A declared operator attestation re-verifies the new address, admitting the previous address's completed journal exactly once. A replay after an interruption finds the address already moved and finishes without a second rename; leavingmigrateEmailFromdeclared afterwards is harmless. Renaming an operator that holds a single sign-on link is refused: the link belongs to the person who signed in with it.identity.breakGlass: { email, passwordHash }— one administrator for when the operator is unavailable.emailis a literal or a required environment reference, distinct from the operator's current and previous address;passwordHashis a required environment reference to a Better Auth hash (<32 hex>:<128 hex>). The host resolves both into private provisioning stdin only; the bundle carries the variable names, and the password never reaches the deployment. After the managed organization is selected, every deployment converges on the declaration: an absent account is created with a verified address, andbreak-glass.jsonbinds its id as soon as the user row exists, before its singlecredentialaccount is linked — the platform's adapter runs without transactions, so this binding is what makes a partial creation resumable. A credential write (first link or replacement) is journaled as pending beforehand and cleared only after the account's sessions are proven ended, so a run that dies between the write and the sweep still ends the old sessions on replay. An unbound account at the address is adopted only when it is this deployment's own interrupted creation; any other account is refused, and so are the operator and a different account holding a bound address. Membership converges through the organization-scoped member doors — add asadmin, or setadmin; anowneris never touched. The deployment never signs in as this account, and the ready proof must name it.tale auth hash-passwordprints that hash from stdin, or from a hidden prompt with confirmation in an interactive terminal. It refuses a password that fails the platform's default password policy and prints nothing but the hash. It is a local helper that manages no instance, so it is excluded from the CLI's version alignment — a re-exec would hand its stdin password to another binary.- Two named refusals replace the generic MFA message: The deploy operator has TOTP enabled; a managed deploy signs in with its password alone, so the operator must hold a passkey and no TOTP, and The deploy operator has no second factor and its two-factor enrolment grace period has ended; register a passkey for it. Operator-address and break-glass refusals name the conflict — another holder, a missing account, a retained binding — and never echo credentials.
The new suites drive real Better Auth 1.6.30 — its internal adapter, adapter scoping and transactions — over its memory adapter, and an independent adversarial read before merge found four defects that are fixed in the same change: the role door needs orgId, an interrupted rotation could leave old sessions, a foreign account could be adopted as administrator, and account creation is not transactional on the platform adapter. All of it is documented under Rename the deploy operator's address, Declare a break-glass administrator and Enforced two-factor sign-in on the CLI install page, in English, German and French, and in the CLI readme.
The 2026-09-18 API evaluation findings are closed (#3410)
The tenth external black-box evaluation of the platform API ran against 0.5.32 — about 2,700 requests, zero 5xx, no data loss, no cross-tenant leak, no auth bypass, verdict mature — and rated the website crawler its weakest surface. Every finding was re-confirmed against the code before it was fixed. The four serious ones:
- Sentry trace headers leaked from the backend — and not only from the crawler. The empty
tracePropagationTargetslist from the previous round is correct and present, but the deployment's shared environment file carriesSENTRY_TRACES_SAMPLE_RATEinto the backend containers (the CLI writes it,'0'by default). Any value switches span recording on; the http and fetch integrations then register OpenTelemetry's request instrumentation beside their own, and its propagator derives the target URL from the active span — an unsampled span records no URL, so the target list is never consulted andsentry-traceandbaggage(release, public key, environment) went out on every outbound request. The two integrations are now registered withspans: false, which keeps the request lanes on the Sentry-native hooks that honour the target list. Proved on the wire with the sample-rate variable set, with a positive control. - The
User-agent: TaleBotrobots group was ignored. Only the*group was ever read, so a site that followed the documentation and addressed TaleBot by name to refuse or throttle it was crawled under the rules it wrote for everyone else. robots.txt is now parsed for the product token per RFC 9309 — the named group binds, else the*group — withAllow, longest-match precedence,$end-anchors (aDisallow: /*.pdf$used to fail open) andCrawl-delay(capped at 60 seconds, paced on both fetch legs and on sitemap reads), honoured by discovery, every continuation link, the rendered-page admission, the retirement pass and the registration-time homepage probe — which also identifies as TaleBot now instead of a barenodeuser agent. ADisallow: /site no longer has its/sitemap.xmlguessed or its homepage walked for links, and a scan that stored no page because robots refused everything lands on the documentederrorstate with that reason instead of readingactivewith zero pages. - A cross-scope webhook redelivery ran twice, silently. The delivery key folded the requested project into its hash, so the same delivery id at the organization door and at a project door produced two keys, two ledger rows and two runs — an operator who moved an automation between organization and project scope had the sender's redelivery run again, and the documented
409 AUTOMATION_DELIVERY_SCOPE_MISMATCHnever fired. Migration 0107 adds a scope-freeidentity_hash; the accept path reads the live rows that share it and answers the 409 when one belongs to the organization door and the other to a project door. Per-project fan-out — the same id starting one run in each installed project — stays legitimate. - A third concurrent scan failed the whole scan. An organization's render sessions are a shared budget (two by default), and a third scan that found it spent ended in
errorwith everything it had fetched discarded, and no retry until the next scan interval. The refusal is now a wait: the link polls for a slot every 15 seconds while its window allows, and when none comes it leaves the batch unmarked for the next link, which follows after 60 seconds instead of the usual five. Only infrastructure failures other than capacity still fail the scan.
And the rest of the round, each a small honest thing: Run.failureCode listed budget_exceeded twice (the chat and agent families both name it), which made openapi.json fail OAS 3.0.3's uniqueItems and aborted openapi-python-client by default — deduplicated, with a guard; the MCP cancel_run tool answered already finished for an id that did not exist, which a cancel-until-refusal loop read as success — now RUN_NOT_FOUND, like get_run and REST; a task create's 201 carries Location; a redirect off the registered site is reported as its own host_not_allowed kind instead of wearing the private_ip label (with a Websites-dialog label in English, German and French); a registration with a non-default port is refused instead of silently crawling the wrong origin on 443; website search's total is a real match count rather than the page size; Product.currency accepts any case, as the door always did; /openapi.json carries an ETag and answers 304; and the chat send documents that its 100,000-character cap counts UTF-16 code units.
The governance model pickers are filled from the live catalog (#3411)
On Settings → Governance → Default models, the Add default model rule dialog's Provider dropdown showed No providers found even with provider credentials configured, and the Model access editor's pickers were empty for the same reason. Both read their options through two hooks that the 2026-08 AI-backend rewrite had replaced with stubs returning nothing while the catalog was offline; the catalog came back and already powers the Providers page, but these editors were never reconnected.
Both hooks now read the live provider catalog. The provider list is scoped to the providers the organization holds an active credential for, so a default rule can never point at a provider that could not serve it and the set matches what the Providers page shows; the model-info popover gets the catalog's context window, cost, reasoning, tools and vision.
Behaviour changes
POST /api/auth/sign-up/emailanswers 403SIGN_UP_CLOSEDonce the deployment holds any account, unlessTALE_ALLOW_OPEN_SIGN_UP=true. The setup flow and the managed CLI's bootstrap still create the first account; Settings > Members and the deploy's provisioning are server-side and unaffected. A refused attempt is logged.- Every account Better Auth creates on this platform arrives with
emailVerified: true; directory-provisioned accounts keep their provider's verdict. At boot, accounts this deployment provisioned earlier (those with acredentialrow) are marked verified, every boot, with a log line naming the count. Theemail_verifiedclaim on an issued identity, conversation synchronization and the notification mirror follow. - The identity provider's authorization code — and with it the signed login-and-consent continuation — lives 300 seconds instead of 60.
- A managed deployment stopped by an enforced two-factor policy names whether it met a TOTP challenge or the enrolment wall.
- The backend sends no
sentry-traceorbaggageheader on any outbound request, whateverSENTRY_TRACES_SAMPLE_RATEsays. - The crawler obeys the robots.txt group that names
TaleBotover*, honoursAllowwith longest-match precedence,$end-anchors andCrawl-delayup to 60 seconds, never fetches a guessed/sitemap.xmlor walks the homepage when robots covers them, and fails a scan that robots left with no page to store. The rules persist on the site row as an object rather than a bare list; rows written by earlier scans are still read. - A webhook delivery re-posted across the organization/project boundary within its identity window answers
409 AUTOMATION_DELIVERY_SCOPE_MISMATCH; the same id across two installed projects still starts one run in each. - A site scan that finds the organization's render sessions spent waits for one (15-second polls within its window) and otherwise defers the batch to its next link 60 seconds later; it no longer ends in
error. - MCP
cancel_runon an unknown run id answersRUN_NOT_FOUND.POST …/projects/{id}/tasksanswers 201 with aLocationheader on a create (an upsert that matched an existing task is a 200 without one)./openapi.jsoncarries anETagand answers 304 to a matchingIf-None-Match. - A crawl redirect that leaves the registered site is recorded as
host_not_allowed, with a message that says so; registering a site with a non-default port answers400 WEBSITE_DOMAIN_INVALID. POST /api/v1/websites/{id}/searchanswers atotalthat counts every matching chunk on the site — it can exceedresults.length— instead of the page size.- The governance Default models and Model access editors list the providers the organization has an active credential for, with their models and capabilities.
API contract changes
- 1.14.0 → 1.15.0. The surface is unchanged at 80 paths, 127 operations, 58 schemas and 150
Error.codevalues; the version moves because a generated client sees the difference. Regenerating the document from this commit reproduces the committed file and its contract fingerprint. Run.failureCode,RunProjection.failureCodeandRunSummary.failureCodelistbudget_exceededonce. The document now validates as OAS 3.0.3, andopenapi-python-clientgenerates from it with its default settings. A spec test refuses a repeated enum value from here on.WebsitePage.lastErrorKindgainshost_not_allowed— a redirect off the registered site, which the crawler does not follow; it used to be reported asprivate_ip, whose description now says what it means.GET /api/v1/skills/{slug}/files/{path}declares the conditional GET the door already answered:If-None-MatchandIf-Modified-Sincerequest headers,ETag,Last-ModifiedandCache-Controlon the 200, and a 304 response.Product.currencyon create and update is^[A-Za-z]{3}$— any case in, stored uppercase, as the door always behaved; the old^[A-Z]{3}$made a generated client refuse"eur"the door accepts.WebsiteSearchResults.totalis documented as the count of all matches across the site, not the size of the answer; this door has no pagination.Automation.createdByandAutomationVersion.createdBydocumentsystem:provisioningfor the built-in connector automations (split on the first:; a value without one is a user id).- The chat send's
contentdocuments that its 100,000 cap counts UTF-16 code units, so an astral character costs two. - Not in the schema but on the wire:
Locationon a task create's 201,ETag/304 on/openapi.json,RUN_NOT_FOUNDfrom MCPcancel_run, andAUTOMATION_DELIVERY_SCOPE_MISMATCH— already in the enum — actually answered. SIGN_UP_CLOSED(403) is a code of the Better Auth door, not of/api/v1, whose surface mounts no sign-up route; the REST registry records it as app-only.
Security
- Unauthenticated account creation from inside the deployment's own network is closed (#3407). The sign-up route answered whoever could reach the backend, and the sandbox network could. The measured result was a new account with a live session token; that account belonged to no organization, so it read no tenant data, but it was a foothold on the deployment, and an address created that way is one an operator can no longer declare for a machine account. The route now refuses once an account exists, and the refusal is logged. One limit is stated in the code rather than hidden: on an empty deployment the probe and the insert are separate statements, so simultaneous first sign-ups are all admitted.
- The backend no longer leaks its Sentry release, public key and environment to third parties (#3410). The previous round's fix held on the render leg only; with
SENTRY_TRACES_SAMPLE_RATEin the environment — which every CLI-written deployment has — the plain fetch leg, and every other outbound request the backend makes, carriedsentry-traceandbaggage. Registering the http and fetch integrations withspans: falsecloses it for the whole backend, and the wire test now runs with the variable set. - What
email_verifiedmeans on this platform (#3408). A relying party reading the claim from a Tale-issued identity should know it now says an administrator or the operator named this address, not the person clicked a link — Tale has no link to click. Accounts from a directory (SSO, SCIM, trusted headers) carry their directory's verdict, unchanged. - The break-glass administrator's password never reaches the deployment (#3406). Only its Better Auth hash travels, resolved on the host into private provisioning stdin; the deployment never signs in as that account; any credential change ends the account's sessions, also when a run finishes an interrupted one; a foreign account at the declared address is refused, never adopted.
- The authorization-code lifetime is 300 seconds (#3408), up from 60, for a single-use, PKCE-bound code that also gates the login and consent continuation — within the conventional ceiling.
- A crawl redirect that leaves the registered site is refused under its own name (#3410), and a non-default port is refused at registration; neither is a new refusal, but a public off-site redirect is no longer reported as a private-network attack, and a site can no longer be registered at
host:8443and crawled on 443. - A cross-scope webhook replay is refused (#3410): the sender's redelivery no longer runs an event a second time after an automation moved between organization and project scope.
- No dependency advisory is fixed in this release, and no dependency changes in this range. One advisory is open against a runtime dependency:
@better-auth/oauth-provider1.6.30, the identity provider behind Continue with Tale, is affected by CVE-2026-67332 / GHSA-p2fr-6hmx-4528 (CVSS 6.4, medium) — a client could request an access token for a different allow-listed audience than its authorization covered. The platform already applies the advisory's own workaround and has since the provider shipped:validAudiencesis empty, so no resource outside the deployment's own can be minted, and only theauthorization_codegrant is enabled, so the refresh-grant path does not exist. The upgrade to 1.7.0 is a breaking change with a schema migration and is tracked as a separate dependency pull request, not carried here.
Known issues
- The sign-up gate has a first-boot race. On a deployment with no account at all, simultaneous first sign-ups are all admitted; the gate closes once one exists. The setup flow is the only intended caller of that window.
- A managed deploy that cannot authenticate its operator can no longer mint a replacement administrator — that was the hole. The recovery is the break-glass administrator: sign in as it and set the operator password back to the one the deploy carries, as the CLI's refusal says.
- The boot catch-up asks nobody. Every account with a
credentialrow that this deployment holds becomes verified at the first boot of 0.5.33 (and any it missed at every later boot). There was no state on this platform in which an unverified local account was intended, but an operator who relied on the flag being false for such accounts should read the Security note above. - Break-glass limits, each documented on the CLI install page: the deployment records no password-change time for the break-glass account, so an organization password rotation policy may ask it for a new password, which the next deployment sets back; renaming an operator that holds a single sign-on link is refused until the link is removed; the backend-local suites drive real Better Auth over its memory adapter, not the platform's Postgres adapter.
- The cross-scope webhook guard governs deliveries from here on. Rows written before the upgrade carry no
identity_hashand never match, so a delivery first taken on 0.5.32 and re-posted across scopes on 0.5.33 still runs; the guard applies within the delivery's identity window — 24 hours for a header-named id, two minutes for a body-hashed one. - A site's robots policy upgrades at its next scan. Until then the row holds the bare
Disallowlist an earlier scan stored, read as noAllowrules and no delay.Crawl-delayis honoured up to 60 seconds; a site that asks for more gets 60. - A scan waiting on render capacity takes longer, by design, and can still end in
errorif its continuation budget runs out with pages waiting — the reason then says so (N page(s) were still waiting when the scan's continuation budget ran out). The organization's render-session budget itself is unchanged. - The Sentry fix is proved by a reproduction and the wire test, not yet on a fleet host — no deployment runs this release yet. The check is the same as the evaluation's: a logging origin behind the crawler should see no
sentry-traceorbaggageheader on either fetch leg. - The governance pickers list only providers with an active credential. A provider whose credential is missing, expired or disabled does not appear in Add default model rule or Model access, even though the catalog ships it. That is the intended reading; the alternative (every shipped provider) is a one-line filter change if it turns out wrong.
- One dependency advisory is open (
@better-auth/oauth-provider, above); the workaround is in place, the upgrade is not. - A page is still fetched three to four times per scan — the plain probe, the render pass and, for the homepage, the create-time metadata probe. The robots
$-anchor andAllowhalf of that ledger item is paid down by this release. - Unchanged from v0.5.32, where each is described in full: the embedding pacing is proved against a controlled server, its bound is per Tale process, and
minTokensPerSecondis a statement nothing verifies; the Kubernetes page's verified scope is one kind cluster,config-dataneeds RWX or a single node, and Tale ships no Helm chart. - Unchanged from v0.5.31, where each is described in full: a managed deployment picks up that release's proxy policy only when a newly prepared bundle is applied; the transcription setting is only as good as the organization's credentials; the six agent-turn fixes are bounded by the pinned Claude Code build they were read from; the 0.5.29 proxy change has been exercised live in
TLS_MODE=letsencryptonly; the web tier's backend-URL default lives in the image, not in the generated compose; the scheduled-pack fix does not reach an automation an organization already has; a budget hold covers a turn's first round only; a run still carries no usage or cost; nothing backfills a task timeline. - 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; 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. - Cloud sync, left for later: there is still no Sync now action — the cadence is the fifteen-minute scan, so a reconnected account waits for the next run. A config whose owner leaves the organization is still deactivated silently by a different door, and a source-deleted item is still a status stamp with no bell.
- Documents indexed before 0.5.27 keep one vector per repeated passage until they are re-indexed; the content hash is unchanged, so only an explicit
retry-indexing(or a content change) re-embeds them. - The rail's navigation memory has had part of its manual round: the R5 round drove six EN/DE/FR desktop and phone cases covering parts of
NAV-F16–NAV-F19; the remaining section, the second-account cases andNAV-B6–NAV-B9are still unrun. - 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
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 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 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.
- Two bounded document readers still filter after their cut; both report an honest
truncated, so a caller can tell the answer was cut. - 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 reverse proxy's network is IPv6-enabled — an operator item, documented on the Own Compose page. - Recorded as contract debt, each with its design in the ledger: a queued send is invisible on the message list until a worker opens it; a webhook delivery the deployed
inputsschema refuses moves no trigger stamp; the MCPrun_deployedtool keys its idempotency apart fromstart_runand REST; a cancelled run answerstrace: nullandeffects: nullwhere a failed run answers both; approvals and asks have no REST twins; 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 noscanStartedAtand 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 stampsscore: 0(itstotalis now honest); noIdempotency-Keyon the task start; no queue position on a queued send; a corrupt Office document 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;GET /notificationsrows carrytypeas a free string and nothing pushes them to a machine caller; a skill keeps no version history on the machine door; the per-task circuit breaker is not built; the messages a conversation snapshot applied are readable only in the app.
Migration notes
- One migration, forward-only and rolling-safe. 0107
automation_webhook_delivery_identityon the application database, applied by the backend at boot inside the advisory lock while the previous image keeps serving:ALTER TABLE app.automation_webhook_deliveries ADD COLUMN IF NOT EXISTS identity_hash textand an index on(trigger_id, identity_hash, expires_at_ms). The column is nullable, the previous image neither writes nor reads it (its rows simply do not take part in the new guard), and no backfill is possible or needed. The application database moves from 0106 to 0107; the knowledge database is unchanged. There is no down migration by design. - One boot-time catch-up that is not a numbered migration. After the migrations, the backend marks verified every account that holds a
credentialrow and is not yet verified, and logsverified N provisioned account(s)when it changed any. It runs on every boot and matches nothing once they are all verified. - One new environment variable:
TALE_ALLOW_OPEN_SIGN_UP. Unset on a real deployment. Set to exactlytrueonly on a throwaway test stack that mints its own accounts over HTTP; the local dev orchestrator, the dev compose overlay and the backend integration check set it for themselves. Documented in.env.exampleand in the environment reference in English, German and French. - Two optional additions to the managed deployment specification,
identity.migrateEmailFromandidentity.breakGlass, and one new CLI command,tale auth hash-password. A specification that declares neither behaves as before. - One new error code,
SIGN_UP_CLOSED(403), on the Better Auth door only. - No image in the stop-gated tier changes. The
proxyanddbimages carry no source change, and neither does the managed proxy policy the CLI renders. A plaintale deployis the whole upgrade: no--stop, no downtime window. - The platform image (the sign-up gate, the verified-account rule and its boot catch-up, the provider lifetime, the evaluation fixes, the governance pickers) and the docs image (five pages in each of English, German and French: the CLI install page, first administrator, authentication, the environment reference and members and roles) carry source changes. web and ui-docs rebuild with no change of their own. The
db,proxy,sandbox,sandbox-runtime,sandbox-buildkitd,sandbox-egressandsandbox-llm-gatewayimages carry no source change. - The CLI has source changes in this range — the operator rename, the break-glass administrator,
auth hash-password, the sign-up refusal and the provider-policy pin — so a managed deployment must move its pinned CLI reference as well as its platform reference. A CLI from before this release refuses to provision a managed client against this platform: its expectation of the provider's code lifetime moved with the platform. The release executables report 0.5.33. @tale/uiand@tale/marketing-uiare pinned by this release as theui-v0.5.33andmarketing-ui-v0.5.33tags on their snapshot branches; a consumer outside the monorepo installs"@tale/ui": "github:tale-project/tale#ui-v0.5.33". Neither package has a source change in this range, so both tags are content-identical to their 0.5.32 predecessors.
Upgrading
-
On the 0.5 line (0.5.0 – 0.5.32):
tale update tale deploy
The migration and the account catch-up run at boot. Nothing in this release needs
--stop. A deployment crossing from a version older than 0.5.29 should read that release's notes, which do: itsproxyimage change is only applied by a--stopdeploy. -
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. Pin the CLI first: a CLI older than this release refuses to provision a managed client against a 0.5.33 platform. 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. -
Enforcing two-factor sign-in on a managed organization? Give the deploy operator a passkey and no authenticator app before you enforce the policy, or the next deploy stops at the challenge — the refusal names which wall it met. Then consider giving the operator a machine address with
identity.migrateEmailFrom, and declaringidentity.breakGlasswith a hash fromtale auth hash-password, so people sign in with their own accounts and a person can still recover the deployment. All three are under Managed deployments on the CLI install page. -
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/cli. -
Running a test stack that creates its own accounts over HTTP? Set
TALE_ALLOW_OPEN_SIGN_UP=trueon it, or every account after the first is refused. Never set it on a real deployment.
What's Changed
- feat(cli): rename the deploy operator and declare a break-glass administrator by @yannickmonney in #3406
- fix(platform): close sign-up once a deployment has an account by @yannickmonney in #3407
- fix(platform): treat a provisioned account as a verified account by @yannickmonney in #3408
- fix(platform): close the 2026-09-18 round-J API evaluation findings by @larryro in #3410
- fix(platform): fill the governance model pickers from the live catalog by @larryro in #3411
Full Changelog: v0.5.32...v0.5.33