Catalog / Dify

1.17.1

v1.17.1

todaybreakingsecurityaddedOriginal notes

[!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.0 to 1.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 403 for 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) or NEXT_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 (00123123.0), empty cells became the literal string nan, and cells containing NA or NULL became missing values. The extractor now reads everything as text and disables missing-value inference; an explicit csv_args still 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_text and title properties read only their first segment, so a title of Hello world with world in bold was indexed as just Hello . All segments are now joined. (#42050)
  • .xls cells containing double quotes corrupted the row. The extractor emits "column":"value" pairs; the .xlsx path escaped embedded quotes but the legacy .xls path interpolated them raw, so he 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 by load_from_url — including the built-in web scraper whenever a target returns Content-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. readabilipy hands back plain_text as 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_ids was treated as "replace with nothing", deleting the segment's attachment bindings and multimodal vectors. Attachments are now left alone unless attachment_ids is 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 separate flask data-migrate legacy-model-types step that had to be run by hand. It now runs as part of flask db upgrade: legacy values are canonicalized to llm / 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 with Cannot 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_selector persisted as null rather 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_request payloads exceeded Engine.IO's 1 MiB default, so the frame was rejected and the editor reconnect-looped. The ceiling is now configurable through WEBSOCKET_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 a 429 instead 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_changed with 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_more was computed against the client-requested limit while the query capped page size at 100, so requesting limit=200 against 150 rows returned 100 rows and has_more=false — the remaining 50 were unreachable. Segment, dataset, and document list endpoints now compute has_more from 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 on DELETE, while the web client sends provider_id in 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 the X-Request-Env header was present, because server matching indexed a non-standard env key 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 with deadlock_detected. Locks are now taken in a stable ORDER BY id before 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: true marker 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.md using CRLF line endings did not match the frontmatter parser, so the name field came back empty and the import reported config asset name must not be blank. CRLF is now accepted and normalized on import, and __MACOSX metadata 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_at of -1 instead of the real lease returned by the provider, and the refresh scanner skips -1 — so providers like Gmail users.watch silently expired. The provider-supplied expiry is now stored. (#41773)
  • Swagger tool authorization survives a page refresh, and SSR now appends /api/v1 when reading MARKETPLACE_API_URL. (#41656, #41824)

Workspace, Accounts & Imports

  • Gmail addresses that differ only by dots or a +alias registered as separate accounts. Registration now stores and checks a normalized email — dots stripped, +alias removed, googlemail.com folded to gmail.com — across self-registration, OAuth registration, email-code account creation, CLI registration, and new workspace-invite accounts, with a dedicated normalized_email_already_in_use error. 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 -1 continues to mean unlimited. Annotation batch imports are allowed when the annotation limit is 0 (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 GET failures 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-GET error, including consecutive failed saves, is still shown. (#42035)

Correctness of Text and Value Parsing

  • JSON extraction broke on backticks inside JSON. parse_json_markdown picked 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 into user_input_form despite 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-BR maps to pt_BR for 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_IPS and SSRF_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)
  • h2 bumped from 4.3.0 to 4.4.1 in both api and dify-agent (#40120, #40119)
  • transformers, unstructured, pypdf, nltk, httpx2, gitpython, and gunicorn refreshed (#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.0 to 1.39.2, moving the pull from Docker Hub to cr.weaviate.io, the registry Weaviate publishes to directly. 1.39 ships 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 an X-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.session property wrappers were removed from App, 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 of DetachedInstanceError and 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_SECONDS explicitly 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.0cr.weaviate.io/semitechnologies/weaviate:1.39.2 in docker-compose.yaml, docker-compose-template.yaml, and docker-compose.middleware.yaml. This is not a drop-in change for existing data — see the Upgrade Guide.
  • docker-compose.middleware.yaml: adds the local_sandbox service (present in the main Compose file since 1.16.1) with the dify_agent_local_sandbox_home and dify_agent_local_sandbox_workspace volumes, and publishes Weaviate's gRPC port 50051.
  • Image tags updated to 1.17.1.

Database Migrations

This release includes 3 new migrations, all applied by flask db upgrade:

  1. 2026_08_25_1200-9b7c6d5e4f3a_add_normalized_email_to_accounts.py — Adds an indexed accounts.normalized_email column 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.
  2. 2026_08_27_1200-5578e028b2f2_migrate_legacy_model_types.py — Data migration canonicalizing legacy model types across provider_models, provider_model_credentials, tenant_default_models, provider_model_settings, and load_balancing_model_configs, deduplicating colliding rows. This replaces the manual flask data-migrate legacy-model-types step from 1.16.1 and 1.17.0.
  3. 2026_08_29_1200-c3f1a9b2e6d4_add_dataset_api_token_bindings.py — Creates the dataset_api_token_bindings many-to-many table backing dataset-scoped API keys. Both foreign keys cascade on delete.

No manual backfill command is required.

[!CAUTION] Migration 5578e028b2f2 is not reversible — its downgrade() 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.0 to 1.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.1 and 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:

  1. Stop Dify's request and worker services, and keep them stopped until the final rung passes.
  2. Back up the Weaviate volume.
  3. Step through all 13 rungs — 1.271.28 → … → 1.38 → finally exactly 1.39.2, the version this release pins — verifying after each one.
  4. 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.