1.17.1
v1.17.1
[!WARNING] Self-hosted deployments using the bundled Weaviate must complete a manual, staged upgrade before starting 1.17.1. The bundled Weaviate server moves from
1.27.0to1.39.2— 12 minor versions — and skipping minors is unsupported. Pulling and restarting can silently and permanently break vector search. See Upgrade Guide below. Fresh deployments, external Weaviate, and other vector stores are unaffected.
New Features
Dataset-Scoped Knowledge Base API Keys
- Knowledge base service-API keys were scoped to the whole workspace: one key could read and write every knowledge base in the tenant, so giving an integration access to a single knowledge base meant giving it access to all of them. Keys can now be bound to specific knowledge bases from the knowledge base's API access panel, with a workspace / this-knowledge-base scope selector at creation time. A bound key returns
403for any other dataset — both documents and retrieval — and for endpoints that carry no dataset ID, such as list-all. Existing keys are unbound and keep their workspace-wide behavior, so nothing changes when you upgrade. (#37569)
Keyboard Movement for Workflow Nodes and Comments
- The workflow canvas required a mouse to reposition anything. Nodes, multi-node selections, comment markers, and draft comments can now be moved with the arrow keys — hold Shift for larger steps — including Note, Iteration, and Loop nodes. Continuous movement is committed on key release or blur, so it produces one undo entry rather than one per keypress, and read-only mode, container boundaries, and collaboration updates are all still respected. (#41967)
Marketplace Creator Profiles and Homepage Redesign
- Plugin authors now have public profile pages at
/marketplace/creator/<handle>, listing their creations with a publisher sidebar, reachable from a new Creator Center entry in the sidebar help menu. The marketplace homepage was rebuilt around a hero, a banner carousel, sticky search, catalog tabs, and tag/language filters, with a stacked-and-swipeable mobile layout. Bundles are no longer mixed into plugin categories. (#41538)
Configurable Markdown Form Field Name Length
- The Markdown form field-name limit was hard-coded at 128 characters, with no way to accommodate longer generated field names. It is now configurable through
MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH(Docker) orNEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH(direct Web deployments), defaulting to the previous 128. Character validation and prototype-pollution protections are unchanged. (#41697)
Bug Fixes
Knowledge Base
This release fixes a cluster of extraction bugs that changed document content without reporting an error. Re-import affected documents after upgrading; indexed text is not retroactively repaired.
- CSV cells were passed through pandas type inference before indexing. Leading-zero codes became floats (
00123→123.0), empty cells became the literal stringnan, and cells containingNAorNULLbecame missing values. The extractor now reads everything as text and disables missing-value inference; an explicitcsv_argsstill overrides this for callers that want typed parsing. (#41922) - Notion table rows lost or gained columns. Each rich-text segment inside a cell was emitted as its own Markdown column, so any cell with mixed formatting — bold, links, mentions — produced extra columns and corrupted the table, while an empty cell was skipped entirely and shifted the rest of the row left. Cells are now emitted one-per-column with their segments concatenated. (#41993)
- Notion database properties were truncated at the first formatting change.
rich_textandtitleproperties read only their first segment, so a title ofHello worldwithworldin bold was indexed as justHello. All segments are now joined. (#42050) .xlscells containing double quotes corrupted the row. The extractor emits"column":"value"pairs; the.xlsxpath escaped embedded quotes but the legacy.xlspath interpolated them raw, sohe said "hi"became"note":"he said "hi"". Both engines now behave the same for the same content. (#42049)- Any PDF loaded from a URL crashed with a bare
AssertionError. After tenant context was added to the PDF extractor, the code path used byload_from_url— including the built-in web scraper whenever a target returnsContent-Type: application/pdf— asserted on an upload record that URL loads never have. Tenant context is now optional, image extraction is skipped when it is absent, and text extraction works again. (#41950) - The built-in web scraper returned Python data structures instead of page text.
readabilipyhands backplain_textas a list of dicts, which was interpolated straight into the output template, so every scraped page came back as[{'text': '<p>…</p>'}, …]— dict syntax, quotes, and HTML tags included. The items are now flattened into readable text. (#41954) - Partial segment updates wiped attachments. A segment update that omitted
attachment_idswas treated as "replace with nothing", deleting the segment's attachment bindings and multimodal vectors. Attachments are now left alone unlessattachment_idsis explicitly provided; passing[]still clears them. (#41777) - Documents with invalid Markdown image links could stall the indexing queue. Relative, protocol-relative,
data:,ftp:, and malformed image targets were all handed to the remote image fetcher and entered its SSRF retry loop, so a single document with many bad references could hold an indexing worker long enough to back up everything behind it. Non-fetchable targets are now validated and skipped. (#41343) - Deleting content left orphaned files in object storage. Segment deletion, multimodal re-indexing, and batch document deletion each removed the database rows but left the physical attachment objects behind. All three paths now delete the underlying objects after the database commit, with an orphan check so attachments shared by other segments survive. (#41400, #41999, #41256)
- Text file previews were misreported as binary. When the preview byte limit cut through a multi-byte UTF-8 character, strict decoding failed and the whole file was classified as binary — reproducible on a default preview with a single non-ASCII character at the 256 KiB boundary. Previews now keep the longest complete UTF-8 prefix; files with genuinely invalid encoding are still reported as binary. (#41761)
Model Providers
- Upgrades from pre-1.15 no longer leave models unusable, and no longer need a manual command. Installations older than 1.15 still carry legacy model type values (
text-generation,embeddings,reranking) in their provider tables, which makes model-related API requests fail and prevents affected models from loading. Both 1.16.1 and 1.17.0 shipped this as a separateflask data-migrate legacy-model-typesstep that had to be run by hand. It now runs as part offlask db upgrade: legacy values are canonicalized tollm/text-embedding/rerank, legacy-versus-canonical row collisions are resolved deterministically while preserving credential references, and the provider cache namespace is rotated so stale cached credentials cannot shadow the migrated rows. (#41326, fixes #41294) - The built-in file upload data source asked for authorization it does not need. When creating a RAG Pipeline knowledge base, local file upload showed "Please authorize first" and the creation checklist flagged it, because providers that declare no credentials schema never have a stored-credential row and were therefore always reported as unauthorized. Credential-free providers are now marked authorized directly. (#39772)
- Provider-scoped credential validation rules are now applied, and missing tool credentials are surfaced instead of failing opaquely. (#41659, #41726)
Workflow & Triggers
- Human Input forms could finish a workflow while leaving it looking stuck. Since the HITL callback migration in 1.17.0, submitting a form left its approval buttons on screen and the published Web App input blocked, because the engine emitted a generic node-success event without the form-filled or timeout events the response pipeline expects. Those events are now reconstructed, preserving submitted file values, original timeout deadlines, and execution metadata. (#42082)
- Human Input could not be placed inside Iteration or Loop nodes, even though 1.17.0 shipped the runtime support for it — two stale editor guards still blocked selecting, connecting, and pasting the node inside a container. (#40061)
- The Human Input timeout edge could fire the wrong branch. The editor writes the timeout edge as
__timeout, but the backend compared against__timeout__, so at runtime a timeout could take a user-action branch instead of the timeout branch the author drew. (#40746) - A second Human Input pause in the same run could become unresumable. If object storage rejected deleting the previous pause snapshot, the exception aborted the transaction — the run was already persisted as
PAUSED, but its only pause row was the previous, already-resumed one, so the next submission failed withCannot resume an already resumed pause. Snapshot cleanup is now best-effort, with the orphaned object key logged for later cleanup, so the run's durable state stays correct. (#41581) - Workflow-as-Tool only exposed the first End node's outputs. Output declarations are now collected from every End node, with the source node listed for each, one warning per output affected by a duplicate or reserved name, and a fallback to the published schema when live draft metadata is unavailable. (#41517)
- Loop Boolean break conditions never matched. New conditions stored
"true"/"false"as strings rather than booleans. Values are now stored and kept as real booleans, and legacy string values are normalized when rendered. (#41186) - Trigger-entry workflows could still be invoked manually from the Web App, Service API, OpenAPI, and MCP
tools/call— creating workflow runs that could never work. These are now rejected before a run or log is created, with a shared business code across HTTP and MCP JSON-RPC. Real trigger execution, read-only endpoints, and running a historical Start version by ID are unaffected. (#41519) - A single bad LLM node took down the whole editor. A workflow whose LLM node had
model_selectorpersisted asnullrather than omitted crashed Workflow Studio's per-node checklist on load and tripped the page-level error boundary, making the entire canvas unusable rather than flagging one node. (#41707) - Workflows containing legacy Agent nodes could not be published once Agent V2 was enabled, because publish validation could not resolve the legacy validator. They publish again, while legacy Agent nodes stay hidden from the node picker. (#41368)
- Creating a tool from a workflow used the previously published version. The current workflow is now published first — matching what the update path already did — and provider creation stops if that publish fails, instead of silently binding to a stale version. (#41528)
- Undoing the deletion of a webhook trigger node changed its URL. (#42069)
- Document create and update could report "One or more files not found." on MySQL even though the upload had committed. Under MySQL's default
REPEATABLE READ, the request session's read snapshot was pinned before the upload happened in a separate session, so the just-written rows were invisible. All four affected endpoints — create-by-text, create-by-file, update-by-text, update-by-file — now refresh the snapshot before the lookup. PostgreSQL was never affected. (#41840) - Large workflow graphs broke the collaboration socket.
sync_requestpayloads exceeded Engine.IO's 1 MiB default, so the frame was rejected and the editor reconnect-looped. The ceiling is now configurable throughWEBSOCKET_MAX_HTTP_BUFFER_SIZE, default 10 MiB. (#41347) - The Access Point page no longer shows "no published version" guidance when the published-workflow request simply failed, and no longer retries requests that returned
403. (#41572)
Agent & Chat
- Chatflow Agent V2 forgot everything between turns. Sessions are now keyed by
conversation_id, so memory persists across a conversation. A follow-up fixed the related case where publishing a new Agent roster version mid-conversation paired a new configuration with the previous participant and failed generation validation — existing conversations now stay pinned to the version they started on, and only new conversations pick up the current one. (#41871, #42105) - Replies from reasoning models rendered entirely as "thinking". Models such as GLM-5.3 wrap chain-of-thought in
<think>tags, and a tool call frequently interrupts generation before</think>is emitted, leaving the answer nested inside a still-open tag. The dangling tag is now closed when a tool log interrupts the stream, when a new<think>starts after real content, and at stream end — plus the same repair in the Markdown preprocessor so already-stored output renders correctly. (#41764) - Images uploaded to an Agent App were turned into shell download instructions rather than being shown to the model. Vision-capable models now receive them as native multimodal content, detected from the selected credential-bound model schema. The download fallback is preserved for non-image files and for models without vision support, and image URLs and Base64 payloads are redacted from agent backend logs. (#41685)
- Suggested questions could take minutes, or hang. With no dedicated suggested-question model configured, the endpoint fell back to the workspace default — inheriting expensive settings such as
reasoning_effort=high, a 2,560-token output allowance, and the global 600-second plugin timeout, so the browser gave up after 100 seconds while the backend kept working. Output is now capped at 256 tokens, thinking is disabled or set to the lowest supported effort, and a request-scoped 30-second timeout applies. One reported case went from 113–344 seconds to under 3 seconds; an unavailable default model now returns an empty list immediately instead of failing. (#41705) - E2B-backed agent runs failed with
502 binding_create_failed. A stale HTTP/2 connection in the pinned E2B SDK surfaced as an inline chat failure. The SDK is upgraded past the affected transport path, connect/pause/kill/delete-snapshot are retried once on bounded transport failures, and provider capacity exhaustion now returns a429instead of being reported as a create failure. Binding and snapshot creation stay one-shot, so ambiguous results are never silently adopted. (#41623) - Agent Preview reused a session that no longer matched the configuration. Because Preview edits the draft in place, a conversation could keep a session snapshot whose composition layers had since been added, removed, or reordered, and the agent backend rejected it with an internal mismatch error. This now returns
409 agent_session_configuration_changedwith an instruction to start a new conversation; changing configuration values without changing the layer topology still reuses the session. (#41447)
Service API & OpenAPI
- Pagination silently truncated results.
has_morewas computed against the client-requestedlimitwhile the query capped page size at 100, so requestinglimit=200against 150 rows returned 100 rows andhas_more=false— the remaining 50 were unreachable. Segment, dataset, and document list endpoints now computehas_morefrom the effective page size and report that size in the response. (#41776, #41784) - Deleting an MCP provider returned
422. Request parsing had started reading only query arguments onDELETE, while the web client sendsprovider_idin the JSON body; custom-model and credential deletion shared the same contract. The JSON body is now used as a fallback when the query string is empty, so query-parameter deletes keep working too. (#41288) - A standard OpenAPI schema was rejected as
invalid schema: 'env'whenever theX-Request-Envheader was present, because server matching indexed a non-standardenvkey unconditionally. Schemas without the extension now take the intended fallback to the first server URL. (#42025) - The generated Service API contract was realigned with runtime behavior: non-nullable fields no longer carry null defaults, Console-only permission metadata is excluded, error responses the endpoints never return are removed, historical 404/503 declarations are restored, and task-stop request schemas are split so chat/completion and workflow behavior are documented accurately. This changes the exported contract and generated clients, not request handling. (#41248, #41274, #41943)
- Service API rate limit descriptions were corrected. (#41975)
Concurrency & Deadlocks
- Concurrent knowledge retrieval could deadlock. The hit-count update incremented every matched segment in one
UPDATE … WHERE id IN (…), and PostgreSQL gives no guarantee that row locks are taken in the order the IDs were supplied — overlapping concurrent retrievals could acquire locks in different orders and fail withdeadlock_detected. Locks are now taken in a stableORDER BY idbefore incrementing, with up to three retries scoped strictly to deadlock errors. (#41979) - Automatic summary generation could hang a worker indefinitely. Tencent VectorDB rejects booleans in JSON metadata, so the
is_summary: truemarker failed at upsert after summary generation and embedding had already succeeded. The failure path then opened a second database session to record the error on the same row the caller's session still held locked, so each waited on the other and the task occupied a worker slot. The marker is now an integer for Tencent only, and errors are recorded in the caller-owned session. (#41916)
Skills, Plugins & Triggers
- Skill packages built on Windows failed with a misleading error. A
SKILL.mdusing CRLF line endings did not match the frontmatter parser, so the name field came back empty and the import reportedconfig asset name must not be blank. CRLF is now accepted and normalized on import, and__MACOSXmetadata folders in skill archives are tolerated. (#41657, #41757) - Triggers worked in Test Run but never fired in production. Plugin trigger relationships were synchronized from the draft workflow rather than the version being published, so published events found no subscriber and no workflow started. (#41756)
- Lease-based triggers stopped working after about a week. Subscription creation persisted the builder's placeholder
expires_atof-1instead of the real lease returned by the provider, and the refresh scanner skips-1— so providers like Gmailusers.watchsilently expired. The provider-supplied expiry is now stored. (#41773) - Swagger tool authorization survives a page refresh, and SSR now appends
/api/v1when readingMARKETPLACE_API_URL. (#41656, #41824)
Workspace, Accounts & Imports
- Gmail addresses that differ only by dots or a
+aliasregistered as separate accounts. Registration now stores and checks a normalized email — dots stripped,+aliasremoved,googlemail.comfolded togmail.com— across self-registration, OAuth registration, email-code account creation, CLI registration, and new workspace-invite accounts, with a dedicatednormalized_email_already_in_useerror. Existing login and account-update lookups are deliberately unchanged, and existing equivalent accounts are preserved. (#41249) - Transferring workspace ownership left the workspace inconsistent. The outgoing owner was demoted to admin rather than a restricted member, and the stored owner column was not always updated — so checks that read it, including the guard on the transfer endpoint itself, kept resolving the previous owner. Both now move correctly, and a workspace already left inconsistent by an earlier transfer is repaired on the next one, with no migration required. (#41267)
- A quota of zero was displayed as unlimited. Trigger Events and API rate limit now show zero as exhausted rather than unlimited, zero-count quotas appear in the exhausted-quota dialog, and
-1continues to mean unlimited. Annotation batch imports are allowed when the annotation limit is0(unlimited), while positive limits still reject imports that would exceed them. (#41927) - DSL imports showed the wrong warning. Backend-provided warning details — notably tool-authorization warnings raised while creating a recommended app — were discarded and replaced with a generic DSL-version warning. Real warning details are now shown, with the generic message kept as a fallback. (#41502)
- Pending DSL imports could be confirmed by the wrong tenant or account. Ownership is now mandatory and verified before any cached YAML is processed or database state is mutated, and validation errors no longer echo cached input values. (#40107)
- A broken app icon reference broke the page. Missing, malformed, and cross-tenant image icons now fall back to the default icon — silently during DSL import, and at Web App runtime with the invalid reference logged. (#41977)
- Repeated identical
GETfailures no longer stack up toasts: the first error shows immediately and identical errors for the same URL are suppressed for ten seconds, cleared on success. Every non-GETerror, including consecutive failed saves, is still shown. (#42035)
Correctness of Text and Value Parsing
- JSON extraction broke on backticks inside JSON.
parse_json_markdownpicked the first code-fence-like marker anywhere in the text, so a backtick inside a string value —{"code": "use `print` function"}— or in surrounding prose made extraction start mid-string and fail. Extraction is now anchored on the JSON brackets themselves. This parser backs LLM output parsing for the rule-config generator and dataset retrieval metadata, where backticked code inside JSON is common. (#41959) - Variable names with a trailing newline passed validation. Python's
$anchor also matches before a trailing newline, so"name\n"was accepted intouser_input_formdespite the newline being outside the allowed character set. (#42029) - Epoch-0 timestamps are accepted by the localtime conversion tool, and the weekday tool handles string and missing parameters. (#42027, #41952)
Accessibility
The console and web apps received a broad pass targeting the parts that made them unusable without a mouse or a screen reader: form controls got correct labels and programmatically associated validation feedback (#42096); sortable lists and panel/node resizing became keyboard-operable (#41974, #41963); focus is restored to its origin after overlays and comment threads close instead of being dropped to the document (#42068, #41978); page landmarks and heading structure were corrected so screen-reader users can navigate by region (#41938); button-styled navigation kept its link semantics so it still works with middle-click and modifier-click (#41312); decorative content and redundant list semantics were hidden from assistive technology (#42060); and clickable divs were replaced with native buttons in dataset lists (#41319). The apps list, home page, and login/signup flows were each covered as a whole (#41393, #41310, #41301).
Web App & UI
- The settings dialog is centered at all viewport sizes (#42120), invalid-input focus colors are preserved (#42108), Home and Studio route titles are stable (#41654), and the plugin detail drawer no longer traps interaction as a modal (#41473)
- Object editor values are stringified before Monaco creates a model, instead of failing on non-string input (#41719)
- Go to Anything resets its search when dismissed and its autocomplete behavior is consistent (#41976, #41258)
- Lao is available as a display language, and
pt-BRmaps topt_BRfor tool metadata (#41743, #41745) - Registration tracking is consent-safe and retryable, and Web App analytics are separated from console page tracking (#41365, #41251)
- The education plan is no longer advertised outside Dify Cloud (#41898), and Open in Explore is hidden for SSO-restricted apps (#41518)
Security Enhancements
- Agent skills bypassed the SSRF private-network policy. Agent traffic goes through a separate proxy that ignored
SSRF_PROXY_ALLOW_PRIVATE_IPSandSSRF_PROXY_ALLOW_PRIVATE_DOMAINS, so the allowlist you configured for workflows did not constrain the agent runtime. Both proxies now honor the same allowlist. (#41870) - A plugin could exhaust worker memory with one declared number. The blob chunk merger pre-allocated a buffer of the plugin-declared total length before checking it against the file size limit, so a plugin claiming an absurd size crashed the worker with
MemoryError— the 30 MB limit was never consulted. The declared size is now validated first and rejected with the documented size-limit error. (#42021) h2bumped from4.3.0to4.4.1in bothapianddify-agent(#40120, #40119)transformers,unstructured,pypdf,nltk,httpx2,gitpython, andgunicornrefreshed (#42053, #42054, #42011, #42012, #42017, #41607, #41602, #41505)
Performance
- Agent runs no longer fill up Redis. Per-run event streams were appended without any cap: production held 3.89 GB across 3,457 streams — 81% of the affected Redis node — with individual streams reaching 30 MB and 78,000 entries, because every stream event including token-scale text deltas was written unbounded and every write refreshed a three-day TTL. Streams are now capped, consecutive text deltas from the same part are coalesced within a 100 ms / 4,096-character window, the cancellation-intent stream holds one entry, and default retention drops from 3 days to 2 hours. The public Agent event schema and SSE cursor contract are unchanged. (#41566)
- The Web Apps sidebar was repainting every loaded app on every navigation. Moving the list to cursor pagination in 1.17.0 dropped its virtualizer, so every page fetched stayed mounted in the main nav and survived route changes. On a workspace with 427 installed apps, clicking through the top-level nav spent 1,068 ms of 3.06 s compositing across 12,112 paint records — roughly 7.5 fps — with the cost in paint rather than script. The list is virtualized again, now on a single always-virtualized render path. (#41397)
- The Console loaded 0.63 MB of support-widget JavaScript on every startup. The Zendesk SDK is now fetched on the first Contact Us action instead: Home and Apps load with zero Zendesk requests, and the 36 requests / 628 KB are deferred to users who actually open support. (#41653)
- Agent build-draft checks waited on an unrelated request. Two surfaces returned their loading UI before the component owning the build-draft query mounted, so the browser could not start that request — which needs only the agent ID — until the composer request had finished. Both waterfalls are flattened. (#41816)
- Account avatar uploads sent full-resolution images. Cropped static avatars are now downsampled to a 256 px maximum edge before upload, preserving aspect ratio and never upscaling; animated avatars keep the original-file path. (#41655)
Improvements
- Bundled Weaviate server upgraded from
1.27.0to1.39.2, moving the pull from Docker Hub tocr.weaviate.io, the registry Weaviate publishes to directly.1.39ships no breaking changes and is where Weaviate's bug and security backports land — but existing data volumes require a staged manual upgrade, covered in the Upgrade Guide. Dify also now sends anX-Weaviate-Client-Integration: dify/<version>header so Weaviate can attribute traffic to Dify in its server-side telemetry; registration is best-effort and never blocks client initialization. (#38214) - Explicit database sessions across the ORM layer. The legacy
db.sessionproperty wrappers were removed fromApp,Dataset,Document,DocumentSegment,Message,Conversation,Workflow,InstalledApp,ApiToolProvider, and related models, and sessions are now passed in explicitly at every call site. There is no API surface change, but this removes a long-standing class ofDetachedInstanceErrorand cross-request session bleed that produced intermittent, hard-to-reproduce failures under load. - Runtime and toolchain updates — React 19.3 with browser-only rendering (#42093), Next.js 16.3.3 (#41262), Node.js 24 LTS (#41573), pnpm 12.3.4 (#42005), Jotai v3 (#42036), base-ui 1.8.0 (#41820). Node 24 and pnpm 12.3.4 are now required to build the web frontend from source.
- A compact command map makes agents discoverable from Go to Anything (#41226), and app and agent card interactions were reworked (#41449)
- Embedded recommend-banner templates and plugins open in the detail dialog rather than navigating away, and banner logos and template authors are preserved (#42063, #41828, #41980)
- The secret environment variable description was updated (#41309), and tracing configuration boundaries are preserved across app operations (#41403)
Environment Variable Changes
New Variables
| Variable | Default | Description |
|---|---|---|
WEBSOCKET_MAX_HTTP_BUFFER_SIZE |
10485760 |
Socket.IO frame ceiling for the collaboration WebSocket. Raise it if very large workflow graphs still fail to sync. |
WEAVIATE_GRPC_ENDPOINT |
grpc://localhost:50051 |
Newly documented in api/.env.example for source deployments. gRPC has been required since Weaviate 1.27; the example files simply never listed it. The Docker value (grpc://weaviate:50051) is unchanged. |
EXPOSE_WEAVIATE_GRPC_PORT |
50051 |
Publishes Weaviate's gRPC port in the middleware-only stack. |
EXPOSE_LOCAL_SANDBOX_PORT |
5004 |
Publishes the local sandbox in the middleware-only stack, so a locally-run agent backend can reach it. |
DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN |
(empty) | shellctl auth token for the middleware-only local sandbox. |
MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH |
128 |
Markdown form field-name length limit, previously hard-coded. |
NEXT_PUBLIC_ENABLE_AGENT_V2_IN_CHATFLOW |
false |
Feature gate for the Agent V2 node in Chatflow apps. Off by default. |
DIFY_AGENT_RUN_EVENT_STREAM_MAX_LENGTH |
5000 |
Maximum entries retained per Agent run event stream. |
DIFY_AGENT_STREAM_TEXT_DELTA_COALESCING_ENABLED |
true |
Merge consecutive Agent text deltas before writing them to Redis. |
DIFY_AGENT_STREAM_TEXT_DELTA_FLUSH_INTERVAL_MS |
100 |
Coalescing flush window. |
DIFY_AGENT_STREAM_TEXT_DELTA_MAX_CHARS |
4096 |
Coalescing size cap. |
Modified (default values)
DIFY_AGENT_RUN_RETENTION_SECONDS:259200(3 days) →7200(2 hours)
[!IMPORTANT] Agent run records and event streams now expire after 2 hours instead of 3 days. This is part of the fix for unbounded Redis growth (#41566). If you rely on inspecting Agent run events for longer than that, set
DIFY_AGENT_RUN_RETENTION_SECONDSexplicitly in your env file before upgrading.
No environment variables were removed in this release.
Docker Compose Changes
- Weaviate image and registry:
semitechnologies/weaviate:1.27.0→cr.weaviate.io/semitechnologies/weaviate:1.39.2indocker-compose.yaml,docker-compose-template.yaml, anddocker-compose.middleware.yaml. This is not a drop-in change for existing data — see the Upgrade Guide. docker-compose.middleware.yaml: adds thelocal_sandboxservice (present in the main Compose file since 1.16.1) with thedify_agent_local_sandbox_homeanddify_agent_local_sandbox_workspacevolumes, and publishes Weaviate's gRPC port50051.- Image tags updated to
1.17.1.
Database Migrations
This release includes 3 new migrations, all applied by flask db upgrade:
2026_08_25_1200-9b7c6d5e4f3a_add_normalized_email_to_accounts.py— Adds an indexedaccounts.normalized_emailcolumn and backfills it, normalizing Gmail and Googlemail addresses. Deliberately not unique: existing installations may already hold equivalent accounts, and those records are preserved while new collisions are prevented.2026_08_27_1200-5578e028b2f2_migrate_legacy_model_types.py— Data migration canonicalizing legacy model types acrossprovider_models,provider_model_credentials,tenant_default_models,provider_model_settings, andload_balancing_model_configs, deduplicating colliding rows. This replaces the manualflask data-migrate legacy-model-typesstep from 1.16.1 and 1.17.0.2026_08_29_1200-c3f1a9b2e6d4_add_dataset_api_token_bindings.py— Creates thedataset_api_token_bindingsmany-to-many table backing dataset-scoped API keys. Both foreign keys cascade on delete.
No manual backfill command is required.
[!CAUTION] Migration
5578e028b2f2is not reversible — itsdowngrade()is intentionally a no-op, because rows written after the enum rename cannot be distinguished from rows it rewrote. Back up your database before upgrading.
Upgrade Guide
Bundled Weaviate Server Upgrade
[!WARNING]
Staged manual upgrade required for existing bundled-Weaviate deployments
This release moves the bundled Weaviate server from
1.27.0to1.39.2. Weaviate neither tests nor supports skipping minor versions, and any release may carry an on-disk migration that expects the previous version to have run at least once. An existing data volume cannot cross 12 minors in one step.Do not pull
1.17.1and restart. Step the volume through every minor release in order, landing on the latest patch of each, with a graceful stop at every rung.Follow the runbook: Weaviate Server Upgrade Path
This applies only if you run the bundled Weaviate with existing data. Check with:
grep -E '^VECTOR_STORE=' docker/.env
Fresh deployments start on 1.39.2 with an empty volume and need nothing. External Weaviate servers and other vector stores are unaffected.
The runbook is the source of truth for the image ladder, per-rung verification commands, backup, and rollback. In outline, the procedure is:
- Stop Dify's request and worker services, and keep them stopped until the final rung passes.
- Back up the Weaviate volume.
- Step through all 13 rungs —
1.27→1.28→ … →1.38→ finally exactly1.39.2, the version this release pins — verifying after each one. - Revert your temporary image edits, then continue with the normal upgrade steps below.
Two failure modes are worth kno
These notes run past the length kept in the archive. The rest is on the publisher’s page.