Does Gemini plugin support Veo2 or Veo3 video models? #913

Closed
opened 2026-02-16 10:20:56 -05:00 by yindo · 56 comments
Owner

Originally created by @WilliamChen-luckbob on GitHub (Jan 5, 2026).

Self Checks

  • I have read the Contributing Guide and Language Policy.
  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report, otherwise it will be closed.
  • Please do not modify this template :) and fill in all the required fields.

1. Is this request related to a challenge you're experiencing? Tell me about your story.

it seems the official plugin of Gemini not support the video models? that's quite a problem...

2. Additional context or comments

No response

3. Can you help us with this feature?

  • I am interested in contributing to this feature.
Originally created by @WilliamChen-luckbob on GitHub (Jan 5, 2026). ### Self Checks - [x] I have read the [Contributing Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) and [Language Policy](https://github.com/langgenius/dify/issues/1542). - [x] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify-official-plugins/issues), including closed ones. - [x] I confirm that I am using English to submit this report, otherwise it will be closed. - [x] Please do not modify this template :) and fill in all the required fields. ### 1. Is this request related to a challenge you're experiencing? Tell me about your story. it seems the official plugin of Gemini not support the video models? that's quite a problem... ### 2. Additional context or comments _No response_ ### 3. Can you help us with this feature? - [x] I am interested in contributing to this feature.
yindo added the enhancementquestion labels 2026-02-16 10:20:56 -05:00
yindo closed this issue 2026-02-16 10:20:56 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jan 5, 2026):

The Gemini plugin does not currently support Veo2 or Veo3 video models. While the plugin offers some video capabilities for certain Gemini models (like Gemini 2.5 Flash), there are no references to Veo2 or Veo3 in the code, documentation, or issues. The plugin's multimodal support is limited to the Gemini models exposed by Google's GenAI API, and Veo2/Veo3 integration is not present or planned at this time reference reference. If you want to contribute support for Veo2 or Veo3, you would need to extend the model config files and provider logic to recognize and handle these models.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 5, 2026): <!-- Answer --> The Gemini plugin does not currently support Veo2 or Veo3 video models. While the plugin offers some video capabilities for certain Gemini models (like Gemini 2.5 Flash), there are no references to Veo2 or Veo3 in the code, documentation, or issues. The plugin's multimodal support is limited to the Gemini models exposed by Google's GenAI API, and Veo2/Veo3 integration is not present or planned at this time [reference](https://github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/README.md#L1-L14) [reference](https://github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/llm/gemini-2.5-flash.yaml#L10-L14). If you want to contribute support for Veo2 or Veo3, you would need to extend the model config files and provider logic to recognize and handle these models. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/843dce69-e2d4-4e0a-98b0-f6fbae797956?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/843dce69-e2d4-4e0a-98b0-f6fbae797956?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/843dce69-e2d4-4e0a-98b0-f6fbae797956?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/843dce69-e2d4-4e0a-98b0-f6fbae797956?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/843dce69-e2d4-4e0a-98b0-f6fbae797956?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/843dce69-e2d4-4e0a-98b0-f6fbae797956?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/843dce69-e2d4-4e0a-98b0-f6fbae797956?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20Gemini%20plugin%20does%20not%20currently%20support%20Veo2%20or%20Veo3%20video%20models.%20While%20the%20plugin%20offers%20some%20video%20capabilities%20for%20certain%20Gemini%20models%20%28like%20Gemini%202.5%20Flash%29%2C%20there%20are%20no%20references%20to%20Veo2%20or%20Veo3%20in%20the%20code%2C%20documentation%2C%20or%20issues.%20The%20plugin%27s%20multimodal%20support%20is%20limited%20to%20the%20Gemini%20models%20exposed%20by%20Google%27s%20GenAI%20API%2C%20and%20Veo2/Veo3%20integration%20is%20not%20present%20or%20planned%20at%20this%20time%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/README.md%23L1-L14%29%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/llm/gemini-2.5-flash.yaml%23L10-L14%29.%20If%20you%20want%20to%20contribute%20support%20for%20Veo2%20or%20Veo3%2C%20you%20would%20need%20to%20extend%20the%20model%20config%20files%20and%20provider%20logic%20to%20recognize%20and%20handle%20these%20models.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 5, 2026):

@dosu
I can help integrate support for Veo2 and Veo3, but I need you to provide me with some detailed development guidelines—such as the location of the configuration files for the Gemini model plugin, configuration requirements, which package(s) the code logic should be placed in, what code logic is required, and the requirements for final unit testing, among others. And also how to deploy the latest plugin and inplement it in my project.

@WilliamChen-luckbob commented on GitHub (Jan 5, 2026): @dosu I can help integrate support for Veo2 and Veo3, but I need you to provide me with some detailed development guidelines—such as the location of the configuration files for the Gemini model plugin, configuration requirements, which package(s) the code logic should be placed in, what code logic is required, and the requirements for final unit testing, among others. And also how to deploy the latest plugin and inplement it in my project.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 5, 2026):

Here’s a step-by-step guide for integrating Veo2 and Veo3 support into the Gemini plugin:

  1. Model Configuration Files:

    • Create a new YAML file for each model (e.g., veo2.yaml, veo3.yaml) in models/gemini/models/llm/.
    • Use existing files like gemini-2.5-flash.yaml as templates. Define the model name, label, model_type, features (include video), model_properties, parameter_rules, and pricing.
    • Add the new model names to models/gemini/models/llm/_position.yaml so they appear in the model list.
    • Example features section:
      features:
        - tool-call
        - multi-tool-call
        - agent-thought
        - vision
        - stream-tool-call
        - document
        - video
        - audio
      
    • Set parameter rules for temperature, top_p, max_output_tokens, etc., as needed for Veo2/Veo3.
  2. Provider Logic:

    • The main logic is in models/gemini/models/llm/llm.py (GoogleLargeLanguageModel).
    • This file already handles multimodal content, including video uploads and MIME types. Unless Veo2/Veo3 have unique API requirements, you likely won’t need to change this logic—just ensure your YAML config matches the expected parameters and features.
    • If Veo2/Veo3 require special handling, extend the relevant methods (e.g., _set_chat_parameters, _set_response_modalities) to support any new parameters or modalities.
  3. Provider Configuration:

    • The provider config is in models/gemini/provider/google.yaml.
    • This references the model sources and credential schema. No changes are needed here unless you add new credential requirements.
  4. Manifest and Dependencies:

    • Update models/gemini/manifest.yaml to bump the plugin version after your changes.
    • If Veo2/Veo3 require a newer Google GenAI SDK, update models/gemini/requirements.txt and pyproject.toml accordingly.
  5. Unit and Integration Testing:

    • Tests are in models/gemini/models/tests/.
    • Follow the structure in TEST_INSTRUCTIONS.md for running and extending tests.
    • Add or update tests to cover Veo2/Veo3, especially for video input/output.
    • Use pytest for both unit (no API key) and integration (API key required) tests.
    • Example:
      uv run pytest models/tests/
      
  6. Deployment and Usage:

    • After making changes, update the manifest version and ensure dependencies are installed.
    • Install the plugin in your Dify instance, configure the API key in Model Provider settings, and verify that Veo2/Veo3 appear in the model list.
    • For best performance with video, set the Files URL if using url mode for MULTIMODAL_SEND_FORMAT in the provider settings.

If Veo2/Veo3 have unique API endpoints or parameters not covered by the current Gemini logic, you’ll need to adjust the provider code accordingly. Otherwise, the process above matches how new Gemini models are typically added.
References:

Let me know if you need a sample YAML or have questions about any step!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 5, 2026): <!-- Answer --> Here’s a step-by-step guide for integrating Veo2 and Veo3 support into the Gemini plugin: 1. **Model Configuration Files**: - Create a new YAML file for each model (e.g., `veo2.yaml`, `veo3.yaml`) in `models/gemini/models/llm/`. - Use existing files like `gemini-2.5-flash.yaml` as templates. Define the model name, label, model_type, features (include `video`), model_properties, parameter_rules, and pricing. - Add the new model names to `models/gemini/models/llm/_position.yaml` so they appear in the model list. - Example features section: ```yaml features: - tool-call - multi-tool-call - agent-thought - vision - stream-tool-call - document - video - audio ``` - Set parameter rules for temperature, top_p, max_output_tokens, etc., as needed for Veo2/Veo3. 2. **Provider Logic**: - The main logic is in `models/gemini/models/llm/llm.py` ([GoogleLargeLanguageModel](https://github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/llm/llm.py#L1-L1018)). - This file already handles multimodal content, including video uploads and MIME types. Unless Veo2/Veo3 have unique API requirements, you likely won’t need to change this logic—just ensure your YAML config matches the expected parameters and features. - If Veo2/Veo3 require special handling, extend the relevant methods (e.g., `_set_chat_parameters`, `_set_response_modalities`) to support any new parameters or modalities. 3. **Provider Configuration**: - The provider config is in `models/gemini/provider/google.yaml`. - This references the model sources and credential schema. No changes are needed here unless you add new credential requirements. 4. **Manifest and Dependencies**: - Update `models/gemini/manifest.yaml` to bump the plugin version after your changes. - If Veo2/Veo3 require a newer Google GenAI SDK, update `models/gemini/requirements.txt` and `pyproject.toml` accordingly. 5. **Unit and Integration Testing**: - Tests are in `models/gemini/models/tests/`. - Follow the structure in `TEST_INSTRUCTIONS.md` for running and extending tests. - Add or update tests to cover Veo2/Veo3, especially for video input/output. - Use pytest for both unit (no API key) and integration (API key required) tests. - Example: ```bash uv run pytest models/tests/ ``` 6. **Deployment and Usage**: - After making changes, update the manifest version and ensure dependencies are installed. - Install the plugin in your Dify instance, configure the API key in Model Provider settings, and verify that Veo2/Veo3 appear in the model list. - For best performance with video, set the `Files URL` if using `url` mode for `MULTIMODAL_SEND_FORMAT` in the provider settings. If Veo2/Veo3 have unique API endpoints or parameters not covered by the current Gemini logic, you’ll need to adjust the provider code accordingly. Otherwise, the process above matches how new Gemini models are typically added. References: - [Gemini model YAML example](https://github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/llm/gemini-2.5-flash.yaml#L1-L116) - [Provider logic](https://github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/llm/llm.py#L1-L1018) - [Provider config](https://github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/provider/google.yaml#L1-L64) - [Testing instructions](https://github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/tests/TEST_INSTRUCTIONS.md) Let me know if you need a sample YAML or have questions about any step! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/b3e254af-1918-4fa1-8255-4d4aef42a78c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/b3e254af-1918-4fa1-8255-4d4aef42a78c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/b3e254af-1918-4fa1-8255-4d4aef42a78c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/b3e254af-1918-4fa1-8255-4d4aef42a78c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/b3e254af-1918-4fa1-8255-4d4aef42a78c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/b3e254af-1918-4fa1-8255-4d4aef42a78c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/b3e254af-1918-4fa1-8255-4d4aef42a78c?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Here%E2%80%99s%20a%20step-by-step%20guide%20for%20integrating%20Veo2%20and%20Veo3%20support%20into%20the%20Gemini%20plugin%3A%0A%0A1.%20%2A%2AModel%20Configuration%20Files%2A%2A%3A%20%20%0A%20%20%20-%20Create%20a%20new%20YAML%20file%20for%20each%20model%20%28e.g.%2C%20%60veo2.yaml%60%2C%20%60veo3.yaml%60%29%20in%20%60models/gemini/models/llm/%60.%20%20%0A%20%20%20-%20Use%20existing%20files%20like%20%60gemini-2.5-flash.yaml%60%20as%20templates.%20Define%20the%20model%20name%2C%20label%2C%20model_type%2C%20features%20%28include%20%60video%60%29%2C%20model_properties%2C%20parameter_rules%2C%20and%20pricing.%20%20%0A%20%20%20-%20Add%20the%20new%20model%20names%20to%20%60models/gemini/models/llm/_position.yaml%60%20so%20they%20appear%20in%20the%20model%20list.%20%20%0A%20%20%20-%20Example%20features%20section%3A%20%20%0A%20%20%20%20%20%60%60%60yaml%0A%20%20%20%20%20features%3A%0A%20%20%20%20%20%20%20-%20tool-call%0A%20%20%20%20%20%20%20-%20multi-tool-call%0A%20%20%20%20%20%20%20-%20agent-thought%0A%20%20%20%20%20%20%20-%20vision%0A%20%20%20%20%20%20%20-%20stream-tool-call%0A%20%20%20%20%20%20%20-%20document%0A%20%20%20%20%20%20%20-%20video%0A%20%20%20%20%20%20%20-%20audio%0A%20%20%20%20%20%60%60%60%0A%20%20%20-%20Set%20parameter%20rules%20for%20temperature%2C%20top_p%2C%20max_output_tokens%2C%20etc.%2C%20as%20needed%20for%20Veo2/Veo3.%0A%0A2.%20%2A%2AProvider%20Logic%2A%2A%3A%20%20%0A%20%20%20-%20The%20main%20logic%20is%20in%20%60models/gemini/models/llm/llm.py%60%20%28%5BGoogleLargeLanguageModel%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/llm/llm.py%23L1-L1018%29%29.%20%20%0A%20%20%20-%20This%20file%20already%20handles%20multimodal%20content%2C%20including%20video%20uploads%20and%20MIME%20types.%20Unless%20Veo2/Veo3%20have%20unique%20API%20requirements%2C%20you%20likely%20won%E2%80%99t%20need%20to%20change%20this%20logic%E2%80%94just%20ensure%20your%20YAML%20config%20matches%20the%20expected%20parameters%20and%20features.%20%20%0A%20%20%20-%20If%20Veo2/Veo3%20require%20special%20handling%2C%20extend%20the%20relevant%20methods%20%28e.g.%2C%20%60_set_chat_parameters%60%2C%20%60_set_response_modalities%60%29%20to%20support%20any%20new%20parameters%20or%20modalities.%0A%0A3.%20%2A%2AProvider%20Configuration%2A%2A%3A%20%20%0A%20%20%20-%20The%20provider%20config%20is%20in%20%60models/gemini/provider/google.yaml%60.%20%20%0A%20%20%20-%20This%20references%20the%20model%20sources%20and%20credential%20schema.%20No%20changes%20are%20needed%20here%20unless%20you%20add%20new%20credential%20requirements.%0A%0A4.%20%2A%2AManifest%20and%20Dependencies%2A%2A%3A%20%20%0A%20%20%20-%20Update%20%60models/gemini/manifest.yaml%60%20to%20bump%20the%20plugin%20version%20after%20your%20changes.%20%20%0A%20%20%20-%20If%20Veo2/Veo3%20require%20a%20newer%20Google%20GenAI%20SDK%2C%20update%20%60models/gemini/requirements.txt%60%20and%20%60pyproject.toml%60%20accordingly.%0A%0A5.%20%2A%2AUnit%20and%20Integration%20Testing%2A%2A%3A%20%20%0A%20%20%20-%20Tests%20are%20in%20%60models/gemini/models/tests/%60.%20%20%0A%20%20%20-%20Follow%20the%20structure%20in%20%60TEST_INSTRUCTIONS.md%60%20for%20running%20and%20extending%20tests.%20%20%0A%20%20%20-%20Add%20or%20update%20tests%20to%20cover%20Veo2/Veo3%2C%20especially%20for%20video%20input/output.%20%20%0A%20%20%20-%20Use%20pytest%20for%20both%20unit%20%28no%20API%20key%29%20and%20integration%20%28API%20key%20required%29%20tests.%20%20%0A%20%20%20-%20Example%3A%20%20%0A%20%20%20%20%20%60%60%60bash%0A%20%20%20%20%20uv%20run%20pytest%20models/tests/%0A%20%20%20%20%20%60%60%60%0A%0A6.%20%2A%2ADeployment%20and%20Usage%2A%2A%3A%20%20%0A%20%20%20-%20After%20making%20changes%2C%20update%20the%20manifest%20version%20and%20ensure%20dependencies%20are%20installed.%20%20%0A%20%20%20-%20Install%20the%20plugin%20in%20your%20Dify%20instance%2C%20configure%20the%20API%20key%20in%20Model%20Provider%20settings%2C%20and%20verify%20that%20Veo2/Veo3%20appear%20in%20the%20model%20list.%20%20%0A%20%20%20-%20For%20best%20performance%20with%20video%2C%20set%20the%20%60Files%20URL%60%20if%20using%20%60url%60%20mode%20for%20%60MULTIMODAL_SEND_FORMAT%60%20in%20the%20provider%20settings.%0A%0AIf%20Veo2/Veo3%20have%20unique%20API%20endpoints%20or%20parameters%20not%20covered%20by%20the%20current%20Gemini%20logic%2C%20you%E2%80%99ll%20need%20to%20adjust%20the%20provider%20code%20accordingly.%20Otherwise%2C%20the%20process%20above%20matches%20how%20new%20Gemini%20models%20are%20typically%20added.%20%20%0AReferences%3A%20%20%0A-%20%5BGemini%20model%20YAML%20example%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/llm/gemini-2.5-flash.yaml%23L1-L116%29%20%20%0A-%20%5BProvider%20logic%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/llm/llm.py%23L1-L1018%29%20%20%0A-%20%5BProvider%20config%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/provider/google.yaml%23L1-L64%29%20%20%0A-%20%5BTesting%20instructions%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/d3eb62d32100ee489a4f4e5583370547a9dfa806/models/gemini/models/tests/TEST_INSTRUCTIONS.md%29%20%20%0A%0ALet%20me%20know%20if%20you%20need%20a%20sample%20YAML%20or%20have%20questions%20about%20any%20step%21)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 5, 2026):

@dosu
Now, I have a question: I need to modify an existing plugin, not create a new plugin project. However, when I refer to the official documentation for creating a new model plugin, I notice that the plugin client doesn't seem to have an operation for this (modifying an existing one or run it). So, how should I conduct actual testing and running? After modifying the code, how do I install this plugin in my Dify environment?

@WilliamChen-luckbob commented on GitHub (Jan 5, 2026): @dosu Now, I have a question: I need to modify an existing plugin, not create a new plugin project. However, when I refer to the official documentation for creating a new model plugin, I notice that the plugin client doesn't seem to have an operation for this (modifying an existing one or run it). So, how should I conduct actual testing and running? After modifying the code, how do I install this plugin in my Dify environment?
Author
Owner

@dosubot[bot] commented on GitHub (Jan 5, 2026):

To test and run a modified existing plugin in Dify, you should first make your code and config changes locally. Run unit and integration tests (for example, with pytest) in the plugin repo to verify your changes before deployment.

To install your modified plugin in your Dify environment, you need to package it (as a dify_pkg binary or similar format), then upload it using the plugin management system. Dify uses a dedicated plugin daemon service to manage plugins. After packaging, upload your plugin via the PluginInstaller (Python class) or through the Dify UI/API, and trigger an install or upgrade operation. There is no hot-reload—after each code change, you must repackage and reinstall the plugin for changes to take effect. The plugin daemon handles storage, installation, and versioning, so you don’t need to restart the whole Dify stack—just update the plugin package and reinstall or upgrade it (reference).

For best practices: always test in a development or staging environment before deploying to production, and check for missing dependencies or installation errors using the plugin management tools.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 5, 2026): <!-- Answer --> To test and run a modified existing plugin in Dify, you should first make your code and config changes locally. Run unit and integration tests (for example, with `pytest`) in the plugin repo to verify your changes before deployment. To install your modified plugin in your Dify environment, you need to package it (as a `dify_pkg` binary or similar format), then upload it using the plugin management system. Dify uses a dedicated plugin daemon service to manage plugins. After packaging, upload your plugin via the PluginInstaller (Python class) or through the Dify UI/API, and trigger an install or upgrade operation. There is no hot-reload—after each code change, you must repackage and reinstall the plugin for changes to take effect. The plugin daemon handles storage, installation, and versioning, so you don’t need to restart the whole Dify stack—just update the plugin package and reinstall or upgrade it ([reference](https://github.com/langgenius/dify/blob/a72044aa86185fdce3e89b4dff605559c34a417a/api/core/plugin/impl/plugin.py#L24-L301)). For best practices: always test in a development or staging environment before deploying to production, and check for missing dependencies or installation errors using the plugin management tools. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/c5eea4ef-1176-420f-b867-18d29133df72?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/c5eea4ef-1176-420f-b867-18d29133df72?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/c5eea4ef-1176-420f-b867-18d29133df72?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/c5eea4ef-1176-420f-b867-18d29133df72?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/c5eea4ef-1176-420f-b867-18d29133df72?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/c5eea4ef-1176-420f-b867-18d29133df72?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/c5eea4ef-1176-420f-b867-18d29133df72?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=To%20test%20and%20run%20a%20modified%20existing%20plugin%20in%20Dify%2C%20you%20should%20first%20make%20your%20code%20and%20config%20changes%20locally.%20Run%20unit%20and%20integration%20tests%20%28for%20example%2C%20with%20%60pytest%60%29%20in%20the%20plugin%20repo%20to%20verify%20your%20changes%20before%20deployment.%0A%0ATo%20install%20your%20modified%20plugin%20in%20your%20Dify%20environment%2C%20you%20need%20to%20package%20it%20%28as%20a%20%60dify_pkg%60%20binary%20or%20similar%20format%29%2C%20then%20upload%20it%20using%20the%20plugin%20management%20system.%20Dify%20uses%20a%20dedicated%20plugin%20daemon%20service%20to%20manage%20plugins.%20After%20packaging%2C%20upload%20your%20plugin%20via%20the%20PluginInstaller%20%28Python%20class%29%20or%20through%20the%20Dify%20UI/API%2C%20and%20trigger%20an%20install%20or%20upgrade%20operation.%20There%20is%20no%20hot-reload%E2%80%94after%20each%20code%20change%2C%20you%20must%20repackage%20and%20reinstall%20the%20plugin%20for%20changes%20to%20take%20effect.%20The%20plugin%20daemon%20handles%20storage%2C%20installation%2C%20and%20versioning%2C%20so%20you%20don%E2%80%99t%20need%20to%20restart%20the%20whole%20Dify%20stack%E2%80%94just%20update%20the%20plugin%20package%20and%20reinstall%20or%20upgrade%20it%C2%A0%28%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/a72044aa86185fdce3e89b4dff605559c34a417a/api/core/plugin/impl/plugin.py%23L24-L301%29%29.%0A%0AFor%20best%20practices%3A%20always%20test%20in%20a%20development%20or%20staging%20environment%20before%20deploying%20to%20production%2C%20and%20check%20for%20missing%20dependencies%20or%20installation%20errors%20using%20the%20plugin%20management%20tools.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 6, 2026):

@dosu
Another question: The API parameters of Veo3 and Veo3.1 differ from those of regular Gemini LLMs. Do I need to create a dedicated LLM node to utilize the features of Veo3.1?

@WilliamChen-luckbob commented on GitHub (Jan 6, 2026): @dosu Another question: The API parameters of Veo3 and Veo3.1 differ from those of regular Gemini LLMs. Do I need to create a dedicated LLM node to utilize the features of Veo3.1?
Author
Owner

@dosubot[bot] commented on GitHub (Jan 6, 2026):

You don't need to create a dedicated LLM node or class for Veo3.1, even if its API parameters differ from regular Gemini LLMs. The Gemini plugin is designed to handle new models by extending the YAML config and adding conditional logic in the shared provider class (GoogleLargeLanguageModel). Unique parameters (like those for Gemini 3 series) are typically supported by updating the model YAML and enhancing methods such as _set_chat_parameters or _set_response_modalities within the same class. Only if the model's API is fundamentally incompatible with the shared logic would a new node/class be needed, but that's not the current pattern for Gemini plugin development citation.

So, to support Veo3.1, update the YAML config for the model and extend the shared logic as needed for its parameters and features.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 6, 2026): <!-- Answer --> You don't need to create a dedicated LLM node or class for Veo3.1, even if its API parameters differ from regular Gemini LLMs. The Gemini plugin is designed to handle new models by extending the YAML config and adding conditional logic in the shared provider class (GoogleLargeLanguageModel). Unique parameters (like those for Gemini 3 series) are typically supported by updating the model YAML and enhancing methods such as _set_chat_parameters or _set_response_modalities within the same class. Only if the model's API is fundamentally incompatible with the shared logic would a new node/class be needed, but that's not the current pattern for Gemini plugin development [citation](https://github.com/langgenius/dify-official-plugins/blob/04dcfdc4d2f2e2bba3829eaecb6089d587557267/models/gemini/models/llm/llm.py#L57-L1018). So, to support Veo3.1, update the YAML config for the model and extend the shared logic as needed for its parameters and features. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/3755efbf-0f72-4904-8f47-a44da7cdd81d?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/3755efbf-0f72-4904-8f47-a44da7cdd81d?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/3755efbf-0f72-4904-8f47-a44da7cdd81d?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/3755efbf-0f72-4904-8f47-a44da7cdd81d?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/3755efbf-0f72-4904-8f47-a44da7cdd81d?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/3755efbf-0f72-4904-8f47-a44da7cdd81d?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/3755efbf-0f72-4904-8f47-a44da7cdd81d?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%20don%27t%20need%20to%20create%20a%20dedicated%20LLM%20node%20or%20class%20for%20Veo3.1%2C%20even%20if%20its%20API%20parameters%20differ%20from%20regular%20Gemini%20LLMs.%20The%20Gemini%20plugin%20is%20designed%20to%20handle%20new%20models%20by%20extending%20the%20YAML%20config%20and%20adding%20conditional%20logic%20in%20the%20shared%20provider%20class%20%28GoogleLargeLanguageModel%29.%20Unique%20parameters%20%28like%20those%20for%20Gemini%203%20series%29%20are%20typically%20supported%20by%20updating%20the%20model%20YAML%20and%20enhancing%20methods%20such%20as%20_set_chat_parameters%20or%20_set_response_modalities%20within%20the%20same%20class.%20Only%20if%20the%20model%27s%20API%20is%20fundamentally%20incompatible%20with%20the%20shared%20logic%20would%20a%20new%20node/class%20be%20needed%2C%20but%20that%27s%20not%20the%20current%20pattern%20for%20Gemini%20plugin%20development%C2%A0%5Bcitation%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/04dcfdc4d2f2e2bba3829eaecb6089d587557267/models/gemini/models/llm/llm.py%23L57-L1018%29.%20%0A%0ASo%2C%20to%20support%20Veo3.1%2C%20update%20the%20YAML%20config%20for%20the%20model%20and%20extend%20the%20shared%20logic%20as%20needed%20for%20its%20parameters%20and%20features.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 9, 2026):

@dosu
How can I dynamically introduce parameters such as video length and negative prompts into the LLM without modifying the node? For example, if these two pieces of data are stored in the context variables, how should I pass them to the LLM?

@WilliamChen-luckbob commented on GitHub (Jan 9, 2026): @dosu How can I dynamically introduce parameters such as video length and negative prompts into the LLM without modifying the node? For example, if these two pieces of data are stored in the context variables, how should I pass them to the LLM?
Author
Owner

@dosubot[bot] commented on GitHub (Jan 9, 2026):

You can dynamically introduce parameters like video length and negative prompts into the LLM without modifying the node by leveraging how Dify workflows and the Gemini plugin handle parameters:

  • In Dify, context variables (from the VariablePool) can be mapped to model parameters at runtime. The LLM node collects variables referenced in your workflow (via variable selectors, prompt config, or jinja2 variables) and merges them into the model_parameters dictionary before invoking the LLM. This means any value present in the context can be passed as a parameter to the LLM if you reference it in your workflow configuration or prompt template citation.

  • To support new parameters like video_length or negative_prompt, add them to the parameter_rules section of your Gemini model YAML config. Then, in your workflow, ensure these variables are available in the context and referenced in your node's completion_params or prompt config. The Gemini plugin will pick them up from model_parameters and use them if you handle them in the provider logic citation.

  • For advanced scenarios, you can register a callback (see core/model_runtime/callbacks/base_callback.py) to intercept and mutate model_parameters before the LLM call. This lets you inject or transform parameters from context variables dynamically, without touching the node or core logic citation.

In summary: expose your desired parameters in the model YAML, ensure your workflow extracts the relevant context variables, and map them to model_parameters (either directly or via a callback). The Gemini plugin will forward these to the LLM as long as they're present in model_parameters and handled in the config setters citation.

If you want a concrete example or YAML snippet for Veo3.x, let me know!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 9, 2026): <!-- Answer --> You can dynamically introduce parameters like video length and negative prompts into the LLM without modifying the node by leveraging how Dify workflows and the Gemini plugin handle parameters: - In Dify, context variables (from the VariablePool) can be mapped to model parameters at runtime. The LLM node collects variables referenced in your workflow (via variable selectors, prompt config, or jinja2 variables) and merges them into the model_parameters dictionary before invoking the LLM. This means any value present in the context can be passed as a parameter to the LLM if you reference it in your workflow configuration or prompt template [citation](https://github.com/langgenius/dify/blob/0711dd4159457d62a937273dd2d8162d92b4e5a1/api/core/workflow/nodes/llm/node.py#L52-L981). - To support new parameters like video_length or negative_prompt, add them to the parameter_rules section of your Gemini model YAML config. Then, in your workflow, ensure these variables are available in the context and referenced in your node's completion_params or prompt config. The Gemini plugin will pick them up from model_parameters and use them if you handle them in the provider logic [citation](https://github.com/langgenius/dify-official-plugins/blob/f10c92c01654946287a99085d73d5c488df904a2/models/gemini/models/llm/gemini-2.5-flash.yaml#L18-L109). - For advanced scenarios, you can register a callback (see core/model_runtime/callbacks/base_callback.py) to intercept and mutate model_parameters before the LLM call. This lets you inject or transform parameters from context variables dynamically, without touching the node or core logic [citation](https://github.com/langgenius/dify/blob/0711dd4159457d62a937273dd2d8162d92b4e5a1/api/core/model_runtime/callbacks/base_callback.py#L17-L51). In summary: expose your desired parameters in the model YAML, ensure your workflow extracts the relevant context variables, and map them to model_parameters (either directly or via a callback). The Gemini plugin will forward these to the LLM as long as they're present in model_parameters and handled in the config setters [citation](https://github.com/langgenius/dify-official-plugins/blob/f10c92c01654946287a99085d73d5c488df904a2/models/gemini/models/llm/llm.py#L252-L986). If you want a concrete example or YAML snippet for Veo3.x, let me know! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/6a9fb464-cbd0-460a-b52d-ed4af8c92ce9?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/6a9fb464-cbd0-460a-b52d-ed4af8c92ce9?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/6a9fb464-cbd0-460a-b52d-ed4af8c92ce9?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/6a9fb464-cbd0-460a-b52d-ed4af8c92ce9?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/6a9fb464-cbd0-460a-b52d-ed4af8c92ce9?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/6a9fb464-cbd0-460a-b52d-ed4af8c92ce9?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/6a9fb464-cbd0-460a-b52d-ed4af8c92ce9?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%20can%20dynamically%20introduce%20parameters%20like%20video%20length%20and%20negative%20prompts%20into%20the%20LLM%20without%20modifying%20the%20node%20by%20leveraging%20how%20Dify%20workflows%20and%20the%20Gemini%20plugin%20handle%20parameters%3A%0A%0A-%20In%20Dify%2C%20context%20variables%20%28from%20the%20VariablePool%29%20can%20be%20mapped%20to%20model%20parameters%20at%20runtime.%20The%20LLM%20node%20collects%20variables%20referenced%20in%20your%20workflow%20%28via%20variable%20selectors%2C%20prompt%20config%2C%20or%20jinja2%20variables%29%20and%20merges%20them%20into%20the%20model_parameters%20dictionary%20before%20invoking%20the%20LLM.%20This%20means%20any%20value%20present%20in%20the%20context%20can%20be%20passed%20as%20a%20parameter%20to%20the%20LLM%20if%20you%20reference%20it%20in%20your%20workflow%20configuration%20or%20prompt%20template%C2%A0%5Bcitation%5D%28https%3A//github.com/langgenius/dify/blob/0711dd4159457d62a937273dd2d8162d92b4e5a1/api/core/workflow/nodes/llm/node.py%23L52-L981%29.%0A%0A-%20To%20support%20new%20parameters%20like%20video_length%20or%20negative_prompt%2C%20add%20them%20to%20the%20parameter_rules%20section%20of%20your%20Gemini%20model%20YAML%20config.%20Then%2C%20in%20your%20workflow%2C%20ensure%20these%20variables%20are%20available%20in%20the%20context%20and%20referenced%20in%20your%20node%27s%20completion_params%20or%20prompt%20config.%20The%20Gemini%20plugin%20will%20pick%20them%20up%20from%20model_parameters%20and%20use%20them%20if%20you%20handle%20them%20in%20the%20provider%20logic%C2%A0%5Bcitation%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/f10c92c01654946287a99085d73d5c488df904a2/models/gemini/models/llm/gemini-2.5-flash.yaml%23L18-L109%29.%0A%0A-%20For%20advanced%20scenarios%2C%20you%20can%20register%20a%20callback%20%28see%20core/model_runtime/callbacks/base_callback.py%29%20to%20intercept%20and%20mutate%20model_parameters%20before%20the%20LLM%20call.%20This%20lets%20you%20inject%20or%20transform%20parameters%20from%20context%20variables%20dynamically%2C%20without%20touching%20the%20node%20or%20core%20logic%C2%A0%5Bcitation%5D%28https%3A//github.com/langgenius/dify/blob/0711dd4159457d62a937273dd2d8162d92b4e5a1/api/core/model_runtime/callbacks/base_callback.py%23L17-L51%29.%0A%0AIn%20summary%3A%20expose%20your%20desired%20parameters%20in%20the%20model%20YAML%2C%20ensure%20your%20workflow%20extracts%20the%20relevant%20context%20variables%2C%20and%20map%20them%20to%20model_parameters%20%28either%20directly%20or%20via%20a%20callback%29.%20The%20Gemini%20plugin%20will%20forward%20these%20to%20the%20LLM%20as%20long%20as%20they%27re%20present%20in%20model_parameters%20and%20handled%20in%20the%20config%20setters%C2%A0%5Bcitation%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/f10c92c01654946287a99085d73d5c488df904a2/models/gemini/models/llm/llm.py%23L252-L986%29.%0A%0AIf%20you%20want%20a%20concrete%20example%20or%20YAML%20snippet%20for%20Veo3.x%2C%20let%20me%20know%21)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 9, 2026):

@dosu
During the process of setting up remote debugging for my self-hosted Dify instance, executing the application through the main function yields no error logs, and the debugging plugins are not visible in the Dify plugin management dashboard.
I did configure the api secret copied from dify plugin page, and the host and port are correct.

@WilliamChen-luckbob commented on GitHub (Jan 9, 2026): @dosu During the process of setting up remote debugging for my self-hosted Dify instance, executing the application through the main function yields no error logs, and the debugging plugins are not visible in the Dify plugin management dashboard. I did configure the api secret copied from dify plugin page, and the host and port are correct.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 9, 2026):

If your debugging plugin isn't showing up in the Dify dashboard and there are no error logs, here are the most common causes and fixes:

  • Make sure INSTALL_METHOD is set to remote in your plugin's environment. If it's set to local, the plugin only emits heartbeat events and won't register with Dify or appear in the dashboard reference.
  • Double-check your .env and Docker configs: avoid using localhost or 127.0.0.1 for service endpoints in Docker. Use your host's internal IP or host.docker.internal so containers can communicate reference.
  • Confirm that the Dify core and plugin daemon are running, reachable, and on the same network. Version mismatches between Dify and the plugin daemon can cause silent failures—make sure both are up to date reference.
  • Check your plugin's manifest.yaml and provider implementation for required fields and methods (especially _validate_credentials). Formatting errors or missing methods can cause blank pages or missing plugins in the dashboard reference.
  • If you recently upgraded or migrated your environment, run the migration commands to extract, install, and migrate plugin data:
    uv run flask extract-plugins --workers=20
    uv run flask install-plugins --workers=2
    uv run flask migrate-data-for-plugin
    
    This ensures plugins and models are properly registered reference.
  • If you still see no error logs, check both the Dify app and plugin daemon container logs for backend errors. Sometimes silent failures are due to network misconfiguration or missing migration steps.
  • As a last resort, try packaging your plugin as a .difypkg file and uploading it via the "Install via Local File" option in the Dify UI to rule out remote registration issues reference.

If none of these steps work, please share your exact .env, the command sequence you use to start the plugin, and any relevant logs for deeper diagnosis.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 9, 2026): <!-- Answer --> If your debugging plugin isn't showing up in the Dify dashboard and there are no error logs, here are the most common causes and fixes: - Make sure INSTALL_METHOD is set to `remote` in your plugin's environment. If it's set to `local`, the plugin only emits heartbeat events and won't register with Dify or appear in the dashboard [reference](https://github.com/langgenius/dify-official-plugins/issues/1953). - Double-check your `.env` and Docker configs: avoid using `localhost` or `127.0.0.1` for service endpoints in Docker. Use your host's internal IP or `host.docker.internal` so containers can communicate [reference](https://github.com/langgenius/dify-official-plugins/issues/1145). - Confirm that the Dify core and plugin daemon are running, reachable, and on the same network. Version mismatches between Dify and the plugin daemon can cause silent failures—make sure both are up to date [reference](https://github.com/langgenius/dify-official-plugins/issues/1171). - Check your plugin's `manifest.yaml` and provider implementation for required fields and methods (especially `_validate_credentials`). Formatting errors or missing methods can cause blank pages or missing plugins in the dashboard [reference](https://github.com/langgenius/dify-official-plugins/issues/1953). - If you recently upgraded or migrated your environment, run the migration commands to extract, install, and migrate plugin data: ``` uv run flask extract-plugins --workers=20 uv run flask install-plugins --workers=2 uv run flask migrate-data-for-plugin ``` This ensures plugins and models are properly registered [reference](https://github.com/langgenius/dify-official-plugins/issues/1145). - If you still see no error logs, check both the Dify app and plugin daemon container logs for backend errors. Sometimes silent failures are due to network misconfiguration or missing migration steps. - As a last resort, try packaging your plugin as a `.difypkg` file and uploading it via the "Install via Local File" option in the Dify UI to rule out remote registration issues [reference](https://github.com/langgenius/dify-official-plugins/issues/1171). If none of these steps work, please share your exact `.env`, the command sequence you use to start the plugin, and any relevant logs for deeper diagnosis. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/41bd4bf6-f58a-42f0-8c48-3da257c53bbf?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/41bd4bf6-f58a-42f0-8c48-3da257c53bbf?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/41bd4bf6-f58a-42f0-8c48-3da257c53bbf?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/41bd4bf6-f58a-42f0-8c48-3da257c53bbf?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/41bd4bf6-f58a-42f0-8c48-3da257c53bbf?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/41bd4bf6-f58a-42f0-8c48-3da257c53bbf?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/41bd4bf6-f58a-42f0-8c48-3da257c53bbf?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=If%20your%20debugging%20plugin%20isn%27t%20showing%20up%20in%20the%20Dify%20dashboard%20and%20there%20are%20no%20error%20logs%2C%20here%20are%20the%20most%20common%20causes%20and%20fixes%3A%0A%0A-%20Make%20sure%20INSTALL_METHOD%20is%20set%20to%20%60remote%60%20in%20your%20plugin%27s%20environment.%20If%20it%27s%20set%20to%20%60local%60%2C%20the%20plugin%20only%20emits%20heartbeat%20events%20and%20won%27t%20register%20with%20Dify%20or%20appear%20in%20the%20dashboard%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1953%29.%0A-%20Double-check%20your%20%60.env%60%20and%20Docker%20configs%3A%20avoid%20using%20%60localhost%60%20or%20%60127.0.0.1%60%20for%20service%20endpoints%20in%20Docker.%20Use%20your%20host%27s%20internal%20IP%20or%20%60host.docker.internal%60%20so%20containers%20can%20communicate%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1145%29.%0A-%20Confirm%20that%20the%20Dify%20core%20and%20plugin%20daemon%20are%20running%2C%20reachable%2C%20and%20on%20the%20same%20network.%20Version%20mismatches%20between%20Dify%20and%20the%20plugin%20daemon%20can%20cause%20silent%20failures%E2%80%94make%20sure%20both%20are%20up%20to%20date%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1171%29.%0A-%20Check%20your%20plugin%27s%20%60manifest.yaml%60%20and%20provider%20implementation%20for%20required%20fields%20and%20methods%20%28especially%20%60_validate_credentials%60%29.%20Formatting%20errors%20or%20missing%20methods%20can%20cause%20blank%20pages%20or%20missing%20plugins%20in%20the%20dashboard%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1953%29.%0A-%20If%20you%20recently%20upgraded%20or%20migrated%20your%20environment%2C%20run%20the%20migration%20commands%20to%20extract%2C%20install%2C%20and%20migrate%20plugin%20data%3A%0A%20%20%60%60%60%0A%20%20uv%20run%20flask%20extract-plugins%20--workers%3D20%0A%20%20uv%20run%20flask%20install-plugins%20--workers%3D2%0A%20%20uv%20run%20flask%20migrate-data-for-plugin%0A%20%20%60%60%60%0A%20%20This%20ensures%20plugins%20and%20models%20are%20properly%20registered%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1145%29.%0A-%20If%20you%20still%20see%20no%20error%20logs%2C%20check%20both%20the%20Dify%20app%20and%20plugin%20daemon%20container%20logs%20for%20backend%20errors.%20Sometimes%20silent%20failures%20are%20due%20to%20network%20misconfiguration%20or%20missing%20migration%20steps.%0A-%20As%20a%20last%20resort%2C%20try%20packaging%20your%20plugin%20as%20a%20%60.difypkg%60%20file%20and%20uploading%20it%20via%20the%20%22Install%20via%20Local%20File%22%20option%20in%20the%20Dify%20UI%20to%20rule%20out%20remote%20registration%20issues%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1171%29.%0A%0AIf%20none%20of%20these%20steps%20work%2C%20please%20share%20your%20exact%20%60.env%60%2C%20the%20command%20sequence%20you%20use%20to%20start%20the%20plugin%2C%20and%20any%20relevant%20logs%20for%20deeper%20diagnosis.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 9, 2026):

@dosu
I start the service via Docker Compose, and the existing plugins in the marketplace can be installed without any issues. I have configured the relevant plugin debugging settings in the .env file of Dify as follows:
PLUGIN_DEBUGGING_HOST=0.0.0.0
PLUGIN_DEBUGGING_PORT=5003
EXPOSE_PLUGIN_DEBUGGING_HOST=0.0.0.0
EXPOSE_PLUGIN_DEBUGGING_PORT=5003
Subsequently, I modified the docker-compose-template.yaml file to proxy the 5003 port through Nginx (the 80 port was already set up with a reverse proxy, so I used another port to configure a reverse proxy for 5003).
After that, I regenerate the yaml file by ./generate_docker_compose and docker compose up again to restart the whole docker.
but the problem remains.
How can I verify that my plugin debugging service has started correctly?

@WilliamChen-luckbob commented on GitHub (Jan 9, 2026): @dosu I start the service via Docker Compose, and the existing plugins in the marketplace can be installed without any issues. I have configured the relevant plugin debugging settings in the .env file of Dify as follows: PLUGIN_DEBUGGING_HOST=0.0.0.0 PLUGIN_DEBUGGING_PORT=5003 EXPOSE_PLUGIN_DEBUGGING_HOST=0.0.0.0 EXPOSE_PLUGIN_DEBUGGING_PORT=5003 Subsequently, I modified the `docker-compose-template.yaml` file to proxy the 5003 port through Nginx (the 80 port was already set up with a reverse proxy, so I used another port to configure a reverse proxy for 5003). After that, I regenerate the yaml file by `./generate_docker_compose` and `docker compose up` again to restart the whole docker. but the problem remains. How can I verify that my plugin debugging service has started correctly?
Author
Owner

@dosubot[bot] commented on GitHub (Jan 9, 2026):

To verify if your plugin debugging service is running correctly in Docker Compose:

  1. Run docker compose ps and check that the plugin_daemon container is up and that port 5003 is exposed as expected.
  2. Check the logs with docker compose logs plugin_daemon for any startup errors or heartbeat messages.
  3. From your host, try curl http://localhost:5003 (or the relevant IP/hostname) to see if the service responds. If you’re using Nginx as a reverse proxy for 5003, also test the proxied endpoint.
  4. Make sure INSTALL_METHOD is set to remote in your plugin’s environment; if it’s set to local, the plugin will not register with Dify and won’t appear in the dashboard—remote mode is required for dashboard visibility (reference).
  5. Double-check that you’re not using localhost or 127.0.0.1 for service endpoints inside Docker; use host.docker.internal or your host’s LAN IP so containers can communicate (reference).
  6. If you recently upgraded or migrated, run these inside Docker to ensure plugin data is registered:
    uv run flask extract-plugins --workers=20
    uv run flask install-plugins --workers=2
    uv run flask migrate-data-for-plugin
    
    (reference)
  7. If the plugin still doesn’t appear, check both plugin_daemon and Dify core logs for errors, and review your Nginx config to ensure proxying for port 5003 is correct and buffering is disabled for debugging endpoints (reference).

If you see heartbeat logs with null session IDs, the service is running in local mode and won’t show up in the dashboard. If it’s in remote mode and still not visible, focus on network, environment variable, and log diagnostics.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 9, 2026): <!-- Answer --> To verify if your plugin debugging service is running correctly in Docker Compose: 1. Run docker compose ps and check that the plugin_daemon container is up and that port 5003 is exposed as expected. 2. Check the logs with docker compose logs plugin_daemon for any startup errors or heartbeat messages. 3. From your host, try curl http://localhost:5003 (or the relevant IP/hostname) to see if the service responds. If you’re using Nginx as a reverse proxy for 5003, also test the proxied endpoint. 4. Make sure INSTALL_METHOD is set to remote in your plugin’s environment; if it’s set to local, the plugin will not register with Dify and won’t appear in the dashboard—remote mode is required for dashboard visibility [(reference)](https://github.com/langgenius/dify-official-plugins/issues/1953). 5. Double-check that you’re not using localhost or 127.0.0.1 for service endpoints inside Docker; use host.docker.internal or your host’s LAN IP so containers can communicate [(reference)](https://github.com/langgenius/dify-official-plugins/issues/1145). 6. If you recently upgraded or migrated, run these inside Docker to ensure plugin data is registered: ``` uv run flask extract-plugins --workers=20 uv run flask install-plugins --workers=2 uv run flask migrate-data-for-plugin ``` [(reference)](https://github.com/langgenius/dify-official-plugins/issues/1145) 7. If the plugin still doesn’t appear, check both plugin_daemon and Dify core logs for errors, and review your Nginx config to ensure proxying for port 5003 is correct and buffering is disabled for debugging endpoints [(reference)](https://github.com/langgenius/dify-official-plugins/issues/2312). If you see heartbeat logs with null session IDs, the service is running in local mode and won’t show up in the dashboard. If it’s in remote mode and still not visible, focus on network, environment variable, and log diagnostics. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/e4946871-c329-4f2d-86c4-5d5cd086c85a?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/e4946871-c329-4f2d-86c4-5d5cd086c85a?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/e4946871-c329-4f2d-86c4-5d5cd086c85a?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/e4946871-c329-4f2d-86c4-5d5cd086c85a?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/e4946871-c329-4f2d-86c4-5d5cd086c85a?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/e4946871-c329-4f2d-86c4-5d5cd086c85a?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/e4946871-c329-4f2d-86c4-5d5cd086c85a?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=To%20verify%20if%20your%20plugin%20debugging%20service%20is%20running%20correctly%20in%20Docker%20Compose%3A%0A%0A1.%20Run%20docker%20compose%20ps%20and%20check%20that%20the%20plugin_daemon%20container%20is%20up%20and%20that%20port%205003%20is%20exposed%20as%20expected.%20%0A2.%20Check%20the%20logs%20with%20docker%20compose%20logs%20plugin_daemon%20for%20any%20startup%20errors%20or%20heartbeat%20messages.%0A3.%20From%20your%20host%2C%20try%20curl%20http%3A//localhost%3A5003%20%28or%20the%20relevant%20IP/hostname%29%20to%20see%20if%20the%20service%20responds.%20If%20you%E2%80%99re%20using%20Nginx%20as%20a%20reverse%20proxy%20for%205003%2C%20also%20test%20the%20proxied%20endpoint.%0A4.%20Make%20sure%20INSTALL_METHOD%20is%20set%20to%20remote%20in%20your%20plugin%E2%80%99s%20environment%3B%20if%20it%E2%80%99s%20set%20to%20local%2C%20the%20plugin%20will%20not%20register%20with%20Dify%20and%20won%E2%80%99t%20appear%20in%20the%20dashboard%E2%80%94remote%20mode%20is%20required%20for%20dashboard%20visibility%20%5B%28reference%29%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1953%29.%0A5.%20Double-check%20that%20you%E2%80%99re%20not%20using%20localhost%20or%20127.0.0.1%20for%20service%20endpoints%20inside%20Docker%3B%20use%20host.docker.internal%20or%20your%20host%E2%80%99s%20LAN%20IP%20so%20containers%20can%20communicate%20%5B%28reference%29%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1145%29.%0A6.%20If%20you%20recently%20upgraded%20or%20migrated%2C%20run%20these%20inside%20Docker%20to%20ensure%20plugin%20data%20is%20registered%3A%0A%20%20%20%60%60%60%0A%20%20%20uv%20run%20flask%20extract-plugins%20--workers%3D20%0A%20%20%20uv%20run%20flask%20install-plugins%20--workers%3D2%0A%20%20%20uv%20run%20flask%20migrate-data-for-plugin%0A%20%20%20%60%60%60%0A%20%20%20%5B%28reference%29%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1145%29%0A7.%20If%20the%20plugin%20still%20doesn%E2%80%99t%20appear%2C%20check%20both%20plugin_daemon%20and%20Dify%20core%20logs%20for%20errors%2C%20and%20review%20your%20Nginx%20config%20to%20ensure%20proxying%20for%20port%205003%20is%20correct%20and%20buffering%20is%20disabled%20for%20debugging%20endpoints%20%5B%28reference%29%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/2312%29.%0A%0AIf%20you%20see%20heartbeat%20logs%20with%20null%20session%20IDs%2C%20the%20service%20is%20running%20in%20local%20mode%20and%20won%E2%80%99t%20show%20up%20in%20the%20dashboard.%20If%20it%E2%80%99s%20in%20remote%20mode%20and%20still%20not%20visible%2C%20focus%20on%20network%2C%20environment%20variable%2C%20and%20log%20diagnostics.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 11, 2026):

@dosu
by curl localhost:5003, there is an error curl: (56) Recv failure: Connection reset by peer returned immediately

@WilliamChen-luckbob commented on GitHub (Jan 11, 2026): @dosu by curl `localhost:5003`, there is an error `curl: (56) Recv failure: Connection reset by peer` returned immediately
Author
Owner

@dosubot[bot] commented on GitHub (Jan 11, 2026):

The error curl: (56) Recv failure: Connection reset by peer on localhost:5003 usually means the service isn't listening or is immediately closing the connection. In your setup, the plugin_daemon is exposing port 5003, but by default it's only attached to the default Docker network—not the internal ssrf_proxy_network used by other core services. This can break inter-container communication and cause connection resets. To fix this, add both networks to the plugin_daemon service in your docker-compose.yaml:

plugin_daemon:
  # ... existing config ...
  networks:
    - ssrf_proxy_network
    - default

Then restart your containers. Also, if you're proxying through Nginx, make sure you add a location block for the debugging port—by default, the Dify Nginx config does not proxy port 5003, so requests to that port will fail unless you add a rule similar to how /e/ proxies to port 5002 reference.

After these changes, try curling again. If you still get the error, check the plugin_daemon logs for startup errors and verify with docker compose ps that the service is healthy and the port is exposed reference.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 11, 2026): <!-- Answer --> The error `curl: (56) Recv failure: Connection reset by peer` on localhost:5003 usually means the service isn't listening or is immediately closing the connection. In your setup, the plugin_daemon is exposing port 5003, but by default it's only attached to the default Docker network—not the internal ssrf_proxy_network used by other core services. This can break inter-container communication and cause connection resets. To fix this, add both networks to the plugin_daemon service in your docker-compose.yaml: ```yaml plugin_daemon: # ... existing config ... networks: - ssrf_proxy_network - default ``` Then restart your containers. Also, if you're proxying through Nginx, make sure you add a location block for the debugging port—by default, the Dify Nginx config does not proxy port 5003, so requests to that port will fail unless you add a rule similar to how `/e/` proxies to port 5002 [reference](https://github.com/langgenius/dify/blob/0c2729d9b30cda8ae596b0ece3783124fb4cd232/docker/nginx/conf.d/default.conf.template#L3-L58). After these changes, try curling again. If you still get the error, check the plugin_daemon logs for startup errors and verify with `docker compose ps` that the service is healthy and the port is exposed [reference](https://github.com/langgenius/dify/blob/0c2729d9b30cda8ae596b0ece3783124fb4cd232/docker/docker-compose-template.yaml#L23-L920). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/c587c6b5-51db-4609-ace8-2ad4198e64db?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/c587c6b5-51db-4609-ace8-2ad4198e64db?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/c587c6b5-51db-4609-ace8-2ad4198e64db?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/c587c6b5-51db-4609-ace8-2ad4198e64db?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/c587c6b5-51db-4609-ace8-2ad4198e64db?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/c587c6b5-51db-4609-ace8-2ad4198e64db?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/c587c6b5-51db-4609-ace8-2ad4198e64db?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20error%20%60curl%3A%20%2856%29%20Recv%20failure%3A%20Connection%20reset%20by%20peer%60%20on%20localhost%3A5003%20usually%20means%20the%20service%20isn%27t%20listening%20or%20is%20immediately%20closing%20the%20connection.%20In%20your%20setup%2C%20the%20plugin_daemon%20is%20exposing%20port%205003%2C%20but%20by%20default%20it%27s%20only%20attached%20to%20the%20default%20Docker%20network%E2%80%94not%20the%20internal%20ssrf_proxy_network%20used%20by%20other%20core%20services.%20This%20can%20break%20inter-container%20communication%20and%20cause%20connection%20resets.%20To%20fix%20this%2C%20add%20both%20networks%20to%20the%20plugin_daemon%20service%20in%20your%20docker-compose.yaml%3A%0A%0A%60%60%60yaml%0Aplugin_daemon%3A%0A%20%20%23%20...%20existing%20config%20...%0A%20%20networks%3A%0A%20%20%20%20-%20ssrf_proxy_network%0A%20%20%20%20-%20default%0A%60%60%60%0A%0AThen%20restart%20your%20containers.%20Also%2C%20if%20you%27re%20proxying%20through%20Nginx%2C%20make%20sure%20you%20add%20a%20location%20block%20for%20the%20debugging%20port%E2%80%94by%20default%2C%20the%20Dify%20Nginx%20config%20does%20not%20proxy%20port%205003%2C%20so%20requests%20to%20that%20port%20will%20fail%20unless%20you%20add%20a%20rule%20similar%20to%20how%20%60/e/%60%20proxies%20to%20port%205002%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/0c2729d9b30cda8ae596b0ece3783124fb4cd232/docker/nginx/conf.d/default.conf.template%23L3-L58%29.%0A%0AAfter%20these%20changes%2C%20try%20curling%20again.%20If%20you%20still%20get%20the%20error%2C%20check%20the%20plugin_daemon%20logs%20for%20startup%20errors%20and%20verify%20with%20%60docker%20compose%20ps%60%20that%20the%20service%20is%20healthy%20and%20the%20port%20is%20exposed%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/0c2729d9b30cda8ae596b0ece3783124fb4cd232/docker/docker-compose-template.yaml%23L23-L920%29.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 11, 2026):

@dosu
I found that the externally exposed port in the plugin-daemon mount configuration of the Docker Compose file was set to 5003:5003 by default. Therefore, I removed the reverse proxy for the relevant port in the Nginx configuration. After regenerating the Docker Compose configuration file and restarting the service, I encountered this issue. I also tried setting the relevant host to 127.0.0.1 or localhost, but both of these resulted in the error: curl: (56) Recv failure: Connection reset by peer.

So I decided to use 0.0.0.0 as host and add this configuration in plugin-daemon

plugin_daemon:
  # ... existing config ...
  networks:
    - ssrf_proxy_network
    - default

But got an new error:

curl -v localhost:5003
* About to connect() to localhost port 5003 (#0)
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 5003 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost:5003
> Accept: */*
> 
handshake failed, invalid handshake message
* Connection #0 to host localhost left intact

here are part of the settings of .env and docker-compose-template.yaml:

.env

PLUGIN_DEBUGGING_HOST=0.0.0.0
PLUGIN_DEBUGGING_PORT=5003
EXPOSE_PLUGIN_DEBUGGING_HOST=0.0.0.0
EXPOSE_PLUGIN_DEBUGGING_PORT=5003

docker-compose-template.yaml

# plugin daemon
  plugin_daemon:
    image: langgenius/dify-plugin-daemon:0.5.1-local
    restart: always
    environment:
      # Use the shared environment variables.
      <<: *shared-api-worker-env
      DB_DATABASE: ${DB_PLUGIN_DATABASE:-dify_plugin}
      SERVER_PORT: ${PLUGIN_DAEMON_PORT:-5002}
      SERVER_KEY: ${PLUGIN_DAEMON_KEY:-lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi}
      MAX_PLUGIN_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800}
      PPROF_ENABLED: ${PLUGIN_PPROF_ENABLED:-false}
      DIFY_INNER_API_URL: ${PLUGIN_DIFY_INNER_API_URL:-http://api:5001}
      DIFY_INNER_API_KEY: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1}
      PLUGIN_REMOTE_INSTALLING_HOST: ${PLUGIN_DEBUGGING_HOST:-0.0.0.0}
      PLUGIN_REMOTE_INSTALLING_PORT: ${PLUGIN_DEBUGGING_PORT:-5003}
      PLUGIN_WORKING_PATH: ${PLUGIN_WORKING_PATH:-/app/storage/cwd}
      FORCE_VERIFYING_SIGNATURE: ${FORCE_VERIFYING_SIGNATURE:-true}
      PYTHON_ENV_INIT_TIMEOUT: ${PLUGIN_PYTHON_ENV_INIT_TIMEOUT:-120}
      PLUGIN_MAX_EXECUTION_TIMEOUT: ${PLUGIN_MAX_EXECUTION_TIMEOUT:-600}
      PLUGIN_STDIO_BUFFER_SIZE: ${PLUGIN_STDIO_BUFFER_SIZE:-1024}
      PLUGIN_STDIO_MAX_BUFFER_SIZE: ${PLUGIN_STDIO_MAX_BUFFER_SIZE:-5242880}
      PIP_MIRROR_URL: ${PIP_MIRROR_URL:-}
      PLUGIN_STORAGE_TYPE: ${PLUGIN_STORAGE_TYPE:-local}
      PLUGIN_STORAGE_LOCAL_ROOT: ${PLUGIN_STORAGE_LOCAL_ROOT:-/app/storage}
      PLUGIN_INSTALLED_PATH: ${PLUGIN_INSTALLED_PATH:-plugin}
      PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages}
      PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets}
      PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-}
      S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false}
      S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false}
      S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-}
      S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false}
      AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-}
      AWS_SECRET_KEY: ${PLUGIN_AWS_SECRET_KEY:-}
      AWS_REGION: ${PLUGIN_AWS_REGION:-}
      AZURE_BLOB_STORAGE_CONNECTION_STRING: ${PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING:-}
      AZURE_BLOB_STORAGE_CONTAINER_NAME: ${PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME:-}
      TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-}
      TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-}
      TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-}
      ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-}
      ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-}
      ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-}
      ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-}
      ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4}
      ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-}
      VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-}
      VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-}
      VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-}
      VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-}
      SENTRY_ENABLED: ${PLUGIN_SENTRY_ENABLED:-false}
      SENTRY_DSN: ${PLUGIN_SENTRY_DSN:-}
    ports:
      - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}"
    volumes:
      - ./volumes/plugin_daemon:/app/storage
    depends_on:
      db_postgres:
        condition: service_healthy
        required: false
      db_mysql:
        condition: service_healthy
        required: false
      oceanbase:
        condition: service_healthy
        required: false
      seekdb:
        condition: service_healthy
        required: false
    networks:
      - ssrf_proxy_network
      - default

@WilliamChen-luckbob commented on GitHub (Jan 11, 2026): @dosu I found that the externally exposed port in the plugin-daemon mount configuration of the Docker Compose file was set to 5003:5003 by default. Therefore, I removed the reverse proxy for the relevant port in the Nginx configuration. After regenerating the Docker Compose configuration file and restarting the service, I encountered this issue. I also tried setting the relevant host to 127.0.0.1 or [localhost](https://localhost/), but both of these resulted in the error: `curl: (56) Recv failure: Connection reset by peer.` So I decided to use 0.0.0.0 as host and add this configuration in `plugin-daemon` ``` plugin_daemon: # ... existing config ... networks: - ssrf_proxy_network - default ``` But got an new error: ``` curl -v localhost:5003 * About to connect() to localhost port 5003 (#0) * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 5003 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.29.0 > Host: localhost:5003 > Accept: */* > handshake failed, invalid handshake message * Connection #0 to host localhost left intact ``` here are part of the settings of `.env` and `docker-compose-template.yaml`: .env ``` PLUGIN_DEBUGGING_HOST=0.0.0.0 PLUGIN_DEBUGGING_PORT=5003 EXPOSE_PLUGIN_DEBUGGING_HOST=0.0.0.0 EXPOSE_PLUGIN_DEBUGGING_PORT=5003 ``` docker-compose-template.yaml ``` # plugin daemon plugin_daemon: image: langgenius/dify-plugin-daemon:0.5.1-local restart: always environment: # Use the shared environment variables. <<: *shared-api-worker-env DB_DATABASE: ${DB_PLUGIN_DATABASE:-dify_plugin} SERVER_PORT: ${PLUGIN_DAEMON_PORT:-5002} SERVER_KEY: ${PLUGIN_DAEMON_KEY:-lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi} MAX_PLUGIN_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} PPROF_ENABLED: ${PLUGIN_PPROF_ENABLED:-false} DIFY_INNER_API_URL: ${PLUGIN_DIFY_INNER_API_URL:-http://api:5001} DIFY_INNER_API_KEY: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} PLUGIN_REMOTE_INSTALLING_HOST: ${PLUGIN_DEBUGGING_HOST:-0.0.0.0} PLUGIN_REMOTE_INSTALLING_PORT: ${PLUGIN_DEBUGGING_PORT:-5003} PLUGIN_WORKING_PATH: ${PLUGIN_WORKING_PATH:-/app/storage/cwd} FORCE_VERIFYING_SIGNATURE: ${FORCE_VERIFYING_SIGNATURE:-true} PYTHON_ENV_INIT_TIMEOUT: ${PLUGIN_PYTHON_ENV_INIT_TIMEOUT:-120} PLUGIN_MAX_EXECUTION_TIMEOUT: ${PLUGIN_MAX_EXECUTION_TIMEOUT:-600} PLUGIN_STDIO_BUFFER_SIZE: ${PLUGIN_STDIO_BUFFER_SIZE:-1024} PLUGIN_STDIO_MAX_BUFFER_SIZE: ${PLUGIN_STDIO_MAX_BUFFER_SIZE:-5242880} PIP_MIRROR_URL: ${PIP_MIRROR_URL:-} PLUGIN_STORAGE_TYPE: ${PLUGIN_STORAGE_TYPE:-local} PLUGIN_STORAGE_LOCAL_ROOT: ${PLUGIN_STORAGE_LOCAL_ROOT:-/app/storage} PLUGIN_INSTALLED_PATH: ${PLUGIN_INSTALLED_PATH:-plugin} PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} AWS_SECRET_KEY: ${PLUGIN_AWS_SECRET_KEY:-} AWS_REGION: ${PLUGIN_AWS_REGION:-} AZURE_BLOB_STORAGE_CONNECTION_STRING: ${PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING:-} AZURE_BLOB_STORAGE_CONTAINER_NAME: ${PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME:-} TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} SENTRY_ENABLED: ${PLUGIN_SENTRY_ENABLED:-false} SENTRY_DSN: ${PLUGIN_SENTRY_DSN:-} ports: - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}" volumes: - ./volumes/plugin_daemon:/app/storage depends_on: db_postgres: condition: service_healthy required: false db_mysql: condition: service_healthy required: false oceanbase: condition: service_healthy required: false seekdb: condition: service_healthy required: false networks: - ssrf_proxy_network - default ```
Author
Owner

@dosubot[bot] commented on GitHub (Jan 11, 2026):

The error you’re seeing is expected: the plugin-daemon debugging service on port 5003 does not respond to plain HTTP GET requests or raw TCP connections. It expects authenticated HTTP requests to specific API endpoints with an X-Api-Key header and JSON payloads—not a generic curl to /—so you’ll get a handshake error if you try that reference.

To verify the service is running and accessible:

  • Make sure both EXPOSE_PLUGIN_DEBUGGING_PORT and PLUGIN_DEBUGGING_PORT are set to 5003, and the Docker Compose mapping is 5003:5003.
  • Both the API and plugin_daemon containers must be on the same Docker networks (default and ssrf_proxy_network).
  • In the API service, set PLUGIN_REMOTE_INSTALL_HOST to plugin_daemon (the Docker Compose service name), not localhost, and PLUGIN_REMOTE_INSTALL_PORT to 5003 reference.
  • Ensure DIFY_INNER_API_KEY and INNER_API_KEY_FOR_PLUGIN are the same in both containers.
  • Restart both containers after making changes.

To test connectivity, use an HTTP client to call a real API endpoint (like /plugin/{tenant}/management/list) with the required headers, not just curl localhost:5003 reference. If you need to check if the service is listening, check the plugin-daemon logs for a line like Launching gnet with 8 event-loops, listening on: tcp://0.0.0.0:5003 reference.

If you follow these steps and use the correct API endpoints, your debugging service should be accessible and work as expected.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 11, 2026): <!-- Answer --> The error you’re seeing is expected: the plugin-daemon debugging service on port 5003 does not respond to plain HTTP GET requests or raw TCP connections. It expects authenticated HTTP requests to specific API endpoints with an `X-Api-Key` header and JSON payloads—not a generic curl to `/`—so you’ll get a handshake error if you try that [reference](https://github.com/langgenius/dify/blob/9fad97ec9bc45daf2ac45a514470aa08a7362aaa/api/core/plugin/impl/base.py#L39-L312). To verify the service is running and accessible: - Make sure both `EXPOSE_PLUGIN_DEBUGGING_PORT` and `PLUGIN_DEBUGGING_PORT` are set to 5003, and the Docker Compose mapping is `5003:5003`. - Both the API and plugin_daemon containers must be on the same Docker networks (`default` and `ssrf_proxy_network`). - In the API service, set `PLUGIN_REMOTE_INSTALL_HOST` to `plugin_daemon` (the Docker Compose service name), not `localhost`, and `PLUGIN_REMOTE_INSTALL_PORT` to `5003` [reference](https://github.com/langgenius/dify/issues/29541). - Ensure `DIFY_INNER_API_KEY` and `INNER_API_KEY_FOR_PLUGIN` are the same in both containers. - Restart both containers after making changes. To test connectivity, use an HTTP client to call a real API endpoint (like `/plugin/{tenant}/management/list`) with the required headers, not just `curl localhost:5003` [reference](https://github.com/langgenius/dify/blob/9fad97ec9bc45daf2ac45a514470aa08a7362aaa/api/core/plugin/impl/plugin.py#L24-L301). If you need to check if the service is listening, check the plugin-daemon logs for a line like `Launching gnet with 8 event-loops, listening on: tcp://0.0.0.0:5003` [reference](https://github.com/langgenius/dify/issues/14654#issuecomment-2701960636). If you follow these steps and use the correct API endpoints, your debugging service should be accessible and work as expected. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/7d1f151b-b192-473f-aa24-14ed9bc0b146?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/7d1f151b-b192-473f-aa24-14ed9bc0b146?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/7d1f151b-b192-473f-aa24-14ed9bc0b146?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/7d1f151b-b192-473f-aa24-14ed9bc0b146?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/7d1f151b-b192-473f-aa24-14ed9bc0b146?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/7d1f151b-b192-473f-aa24-14ed9bc0b146?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/7d1f151b-b192-473f-aa24-14ed9bc0b146?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20error%20you%E2%80%99re%20seeing%20is%20expected%3A%20the%20plugin-daemon%20debugging%20service%20on%20port%205003%20does%20not%20respond%20to%20plain%20HTTP%20GET%20requests%20or%20raw%20TCP%20connections.%20It%20expects%20authenticated%20HTTP%20requests%20to%20specific%20API%20endpoints%20with%20an%20%60X-Api-Key%60%20header%20and%20JSON%20payloads%E2%80%94not%20a%20generic%20curl%20to%20%60/%60%E2%80%94so%20you%E2%80%99ll%20get%20a%20handshake%20error%20if%20you%20try%20that%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/9fad97ec9bc45daf2ac45a514470aa08a7362aaa/api/core/plugin/impl/base.py%23L39-L312%29.%0A%0ATo%20verify%20the%20service%20is%20running%20and%20accessible%3A%0A%0A-%20Make%20sure%20both%20%60EXPOSE_PLUGIN_DEBUGGING_PORT%60%20and%20%60PLUGIN_DEBUGGING_PORT%60%20are%20set%20to%205003%2C%20and%20the%20Docker%20Compose%20mapping%20is%20%605003%3A5003%60.%0A-%20Both%20the%20API%20and%20plugin_daemon%20containers%20must%20be%20on%20the%20same%20Docker%20networks%20%28%60default%60%20and%20%60ssrf_proxy_network%60%29.%0A-%20In%20the%20API%20service%2C%20set%20%60PLUGIN_REMOTE_INSTALL_HOST%60%20to%20%60plugin_daemon%60%20%28the%20Docker%20Compose%20service%20name%29%2C%20not%20%60localhost%60%2C%20and%20%60PLUGIN_REMOTE_INSTALL_PORT%60%20to%20%605003%60%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/issues/29541%29.%0A-%20Ensure%20%60DIFY_INNER_API_KEY%60%20and%20%60INNER_API_KEY_FOR_PLUGIN%60%20are%20the%20same%20in%20both%20containers.%0A-%20Restart%20both%20containers%20after%20making%20changes.%0A%0ATo%20test%20connectivity%2C%20use%20an%20HTTP%20client%20to%20call%20a%20real%20API%20endpoint%20%28like%20%60/plugin/%7Btenant%7D/management/list%60%29%20with%20the%20required%20headers%2C%20not%20just%20%60curl%20localhost%3A5003%60%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/9fad97ec9bc45daf2ac45a514470aa08a7362aaa/api/core/plugin/impl/plugin.py%23L24-L301%29.%20If%20you%20need%20to%20check%20if%20the%20service%20is%20listening%2C%20check%20the%20plugin-daemon%20logs%20for%20a%20line%20like%20%60Launching%20gnet%20with%208%20event-loops%2C%20listening%20on%3A%20tcp%3A//0.0.0.0%3A5003%60%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/issues/14654%23issuecomment-2701960636%29.%0A%0AIf%20you%20follow%20these%20steps%20and%20use%20the%20correct%20API%20endpoints%2C%20your%20debugging%20service%20should%20be%20accessible%20and%20work%20as%20expected.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 11, 2026):

@dosu
Well, I don’t believe Veo can reuse the existing logic in Gemini plugin—aside from initializing the genai function. Video generation relies on entirely different functions, parameters, and configuration classes.

As you can see from below:

normal generation:

  def generate_content_stream(
      self,
      *,
      model: str,
      contents: types.ContentListUnionDict,
      config: Optional[types.GenerateContentConfigOrDict] = None,
  ) -> Iterator[types.GenerateContentResponse]:
      ...


  def generate_content(
      self,
      *,
      model: str,
      contents: types.ContentListUnionDict,
      config: Optional[types.GenerateContentConfigOrDict] = None,
  ) -> types.GenerateContentResponse:

video generation:

def generate_videos(
      self,
      *,
      model: str,
      prompt: Optional[str] = None,
      image: Optional[types.ImageOrDict] = None,
      video: Optional[types.VideoOrDict] = None,
      source: Optional[types.GenerateVideosSourceOrDict] = None,
      config: Optional[types.GenerateVideosConfigOrDict] = None,
  ) -> types.GenerateVideosOperation:

If I were to implement this logic within the _generate method, it would result in an excessively large if-else block to accommodate both the existing generation logic and video generation.

Is there a standardized approach to implementing video generation in the official Gemini plugin? For instance, could we create a dedicated _generate_video method exclusively for video generation?

@WilliamChen-luckbob commented on GitHub (Jan 11, 2026): @dosu Well, I don’t believe Veo can reuse the existing logic in Gemini plugin—aside from initializing the genai function. Video generation relies on entirely different functions, parameters, and configuration classes. As you can see from below: normal generation: ``` def generate_content_stream( self, *, model: str, contents: types.ContentListUnionDict, config: Optional[types.GenerateContentConfigOrDict] = None, ) -> Iterator[types.GenerateContentResponse]: ... def generate_content( self, *, model: str, contents: types.ContentListUnionDict, config: Optional[types.GenerateContentConfigOrDict] = None, ) -> types.GenerateContentResponse: ``` video generation: ``` def generate_videos( self, *, model: str, prompt: Optional[str] = None, image: Optional[types.ImageOrDict] = None, video: Optional[types.VideoOrDict] = None, source: Optional[types.GenerateVideosSourceOrDict] = None, config: Optional[types.GenerateVideosConfigOrDict] = None, ) -> types.GenerateVideosOperation: ``` If I were to implement this logic within the `_generate` method, it would result in an excessively large if-else block to accommodate both the existing generation logic and video generation. Is there a standardized approach to implementing video generation in the official Gemini plugin? For instance, could we create a dedicated _generate_video method exclusively for video generation?
Author
Owner

@dosubot[bot] commented on GitHub (Jan 11, 2026):

There’s no standardized or official approach in the Gemini plugin for handling video generation as a distinct logic path. The current pattern is to use a single _generate method with conditional logic for different modalities, but this can get unwieldy for APIs like Veo’s video generation, which have fundamentally different parameters and workflows reference.

For maintainability, it’s idiomatic to introduce a dedicated _generate_video method within the provider class if you want to keep everything in one plugin. Alternatively, if the video API and workflow are very different, you can follow the Dify ecosystem’s precedent for new modalities (like image generation) and create a separate plugin or tool class for video generation reference. Both approaches are compatible with the plugin daemon and Dify’s management system.

If you want to keep things modular and future-proof, separating concerns—either by method or by plugin/tool—is preferred. You can register the new method or class in the manifest and config files, and test it using the same workflow as other plugins. If you want feedback on your proposed structure, consider opening a discussion or RFC with maintainers.

Let me know if you want a concrete example or guidance on where to place the new method or how to register a separate plugin/tool!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 11, 2026): <!-- Answer --> There’s no standardized or official approach in the Gemini plugin for handling video generation as a distinct logic path. The current pattern is to use a single _generate method with conditional logic for different modalities, but this can get unwieldy for APIs like Veo’s video generation, which have fundamentally different parameters and workflows [reference](https://github.com/langgenius/dify-official-plugins/blob/ef6335b1d8e9b19d5d65454b84213f9c09bd2354/models/gemini/models/llm/llm.py#L57-L1024). For maintainability, it’s idiomatic to introduce a dedicated _generate_video method within the provider class if you want to keep everything in one plugin. Alternatively, if the video API and workflow are very different, you can follow the Dify ecosystem’s precedent for new modalities (like image generation) and create a separate plugin or tool class for video generation [reference](https://github.com/langgenius/dify-official-plugins/pull/1695). Both approaches are compatible with the plugin daemon and Dify’s management system. If you want to keep things modular and future-proof, separating concerns—either by method or by plugin/tool—is preferred. You can register the new method or class in the manifest and config files, and test it using the same workflow as other plugins. If you want feedback on your proposed structure, consider opening a discussion or RFC with maintainers. Let me know if you want a concrete example or guidance on where to place the new method or how to register a separate plugin/tool! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/d4a1e6fa-5e9a-4a44-8562-b1750899356f?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/d4a1e6fa-5e9a-4a44-8562-b1750899356f?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/d4a1e6fa-5e9a-4a44-8562-b1750899356f?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/d4a1e6fa-5e9a-4a44-8562-b1750899356f?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/d4a1e6fa-5e9a-4a44-8562-b1750899356f?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/d4a1e6fa-5e9a-4a44-8562-b1750899356f?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/d4a1e6fa-5e9a-4a44-8562-b1750899356f?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=There%E2%80%99s%20no%20standardized%20or%20official%20approach%20in%20the%20Gemini%20plugin%20for%20handling%20video%20generation%20as%20a%20distinct%20logic%20path.%20The%20current%20pattern%20is%20to%20use%20a%20single%20_generate%20method%20with%20conditional%20logic%20for%20different%20modalities%2C%20but%20this%20can%20get%20unwieldy%20for%20APIs%20like%20Veo%E2%80%99s%20video%20generation%2C%20which%20have%20fundamentally%20different%20parameters%20and%20workflows%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/blob/ef6335b1d8e9b19d5d65454b84213f9c09bd2354/models/gemini/models/llm/llm.py%23L57-L1024%29.%0A%0AFor%20maintainability%2C%20it%E2%80%99s%20idiomatic%20to%20introduce%20a%20dedicated%20_generate_video%20method%20within%20the%20provider%20class%20if%20you%20want%20to%20keep%20everything%20in%20one%20plugin.%20Alternatively%2C%20if%20the%20video%20API%20and%20workflow%20are%20very%20different%2C%20you%20can%20follow%20the%20Dify%20ecosystem%E2%80%99s%20precedent%20for%20new%20modalities%20%28like%20image%20generation%29%20and%20create%20a%20separate%20plugin%20or%20tool%20class%20for%20video%20generation%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/pull/1695%29.%20Both%20approaches%20are%20compatible%20with%20the%20plugin%20daemon%20and%20Dify%E2%80%99s%20management%20system.%0A%0AIf%20you%20want%20to%20keep%20things%20modular%20and%20future-proof%2C%20separating%20concerns%E2%80%94either%20by%20method%20or%20by%20plugin/tool%E2%80%94is%20preferred.%20You%20can%20register%20the%20new%20method%20or%20class%20in%20the%20manifest%20and%20config%20files%2C%20and%20test%20it%20using%20the%20same%20workflow%20as%20other%20plugins.%20If%20you%20want%20feedback%20on%20your%20proposed%20structure%2C%20consider%20opening%20a%20discussion%20or%20RFC%20with%20maintainers.%0A%0ALet%20me%20know%20if%20you%20want%20a%20concrete%20example%20or%20guidance%20on%20where%20to%20place%20the%20new%20method%20or%20how%20to%20register%20a%20separate%20plugin/tool%21)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 12, 2026):

@dosu

When I directly drag a tested plugin package to upload a packaged plugin to the Dify page, the following error occurs:
PluginDaemonBadRequestError: plugin verification has been enabled, and the plugin you want to install has a bad signature

Here’s what I’ve done so far:

I tested the plugin via remote debugging, and it worked fine.

I then ran the command dify-plugin-windows-amd64.exe plugin package C:\code\repository-python\dify-official-plugins\tools\gemini_video to package the plugin.

After packaging, I dragged the generated .difypkg file to the Dify upload page, and the above error popped up.

@WilliamChen-luckbob commented on GitHub (Jan 12, 2026): @dosu When I directly drag a tested plugin package to upload a packaged plugin to the Dify page, the following error occurs: `PluginDaemonBadRequestError: plugin verification has been enabled, and the plugin you want to install has a bad signature` Here’s what I’ve done so far: I tested the plugin via remote debugging, and it worked fine. I then ran the command dify-plugin-windows-amd64.exe plugin package C:\code\repository-python\dify-official-plugins\tools\gemini_video to package the plugin. After packaging, I dragged the generated .difypkg file to the Dify upload page, and the above error popped up.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 12, 2026):

This error happens because Dify enforces plugin signature verification by default. When you modify or repackage a plugin, the original signature becomes invalid, so the plugin daemon rejects it as having a "bad signature" details.

For development or testing, you can disable signature verification by setting these in your .env file (usually in your Dify Docker directory):

FORCE_VERIFYING_SIGNATURE=false
ENFORCE_LANGGENIUS_PLUGIN_SIGNATURES=false

Then restart your Dify containers. This will let you upload and install modified plugins without signature checks reference.

For production, you should sign your plugin with your own key pair and configure Dify to trust your public key. The steps are:

  1. Generate a key pair:
    dify signature generate -f your_key_pair
    
  2. Sign your plugin:
    dify signature sign your_plugin_project.difypkg -p your_key_pair.private.pem
    
  3. Place the public key in a directory accessible to the plugin daemon and set these environment variables:
    FORCE_VERIFYING_SIGNATURE=true
    THIRD_PARTY_SIGNATURE_VERIFICATION_ENABLED=true
    THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS=/app/storage/public_keys/your_key_pair.public.pem
    
  4. Restart Dify.

If you still see signature errors after these steps, check for packaging tool warnings, verify your plugin package structure, and review the plugin daemon logs for more details reference.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 12, 2026): <!-- Answer --> This error happens because Dify enforces plugin signature verification by default. When you modify or repackage a plugin, the original signature becomes invalid, so the plugin daemon rejects it as having a "bad signature" [details](https://github.com/langgenius/dify/issues/29025). For development or testing, you can disable signature verification by setting these in your `.env` file (usually in your Dify Docker directory): ``` FORCE_VERIFYING_SIGNATURE=false ENFORCE_LANGGENIUS_PLUGIN_SIGNATURES=false ``` Then restart your Dify containers. This will let you upload and install modified plugins without signature checks [reference](https://github.com/langgenius/dify/issues/29025). For production, you should sign your plugin with your own key pair and configure Dify to trust your public key. The steps are: 1. Generate a key pair: ``` dify signature generate -f your_key_pair ``` 2. Sign your plugin: ``` dify signature sign your_plugin_project.difypkg -p your_key_pair.private.pem ``` 3. Place the public key in a directory accessible to the plugin daemon and set these environment variables: ``` FORCE_VERIFYING_SIGNATURE=true THIRD_PARTY_SIGNATURE_VERIFICATION_ENABLED=true THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS=/app/storage/public_keys/your_key_pair.public.pem ``` 4. Restart Dify. If you still see signature errors after these steps, check for packaging tool warnings, verify your plugin package structure, and review the plugin daemon logs for more details [reference](https://github.com/langgenius/dify/issues/29025#issuecomment-3600986276). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/8b4ef62e-ea56-4aeb-9575-d664465d0afd?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8b4ef62e-ea56-4aeb-9575-d664465d0afd?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8b4ef62e-ea56-4aeb-9575-d664465d0afd?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8b4ef62e-ea56-4aeb-9575-d664465d0afd?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8b4ef62e-ea56-4aeb-9575-d664465d0afd?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8b4ef62e-ea56-4aeb-9575-d664465d0afd?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8b4ef62e-ea56-4aeb-9575-d664465d0afd?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20error%20happens%20because%20Dify%20enforces%20plugin%20signature%20verification%20by%20default.%20When%20you%20modify%20or%20repackage%20a%20plugin%2C%20the%20original%20signature%20becomes%20invalid%2C%20so%20the%20plugin%20daemon%20rejects%20it%20as%20having%20a%20%22bad%20signature%22%C2%A0%5Bdetails%5D%28https%3A//github.com/langgenius/dify/issues/29025%29.%0A%0AFor%20development%20or%20testing%2C%20you%20can%20disable%20signature%20verification%20by%20setting%20these%20in%20your%20%60.env%60%20file%20%28usually%20in%20your%20Dify%20Docker%20directory%29%3A%0A%0A%60%60%60%0AFORCE_VERIFYING_SIGNATURE%3Dfalse%0AENFORCE_LANGGENIUS_PLUGIN_SIGNATURES%3Dfalse%0A%60%60%60%0A%0AThen%20restart%20your%20Dify%20containers.%20This%20will%20let%20you%20upload%20and%20install%20modified%20plugins%20without%20signature%20checks%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/issues/29025%29.%0A%0AFor%20production%2C%20you%20should%20sign%20your%20plugin%20with%20your%20own%20key%20pair%20and%20configure%20Dify%20to%20trust%20your%20public%20key.%20The%20steps%20are%3A%0A%0A1.%20Generate%20a%20key%20pair%3A%0A%20%20%20%60%60%60%0A%20%20%20dify%20signature%20generate%20-f%20your_key_pair%0A%20%20%20%60%60%60%0A2.%20Sign%20your%20plugin%3A%0A%20%20%20%60%60%60%0A%20%20%20dify%20signature%20sign%20your_plugin_project.difypkg%20-p%20your_key_pair.private.pem%0A%20%20%20%60%60%60%0A3.%20Place%20the%20public%20key%20in%20a%20directory%20accessible%20to%20the%20plugin%20daemon%20and%20set%20these%20environment%20variables%3A%0A%20%20%20%60%60%60%0A%20%20%20FORCE_VERIFYING_SIGNATURE%3Dtrue%0A%20%20%20THIRD_PARTY_SIGNATURE_VERIFICATION_ENABLED%3Dtrue%0A%20%20%20THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS%3D/app/storage/public_keys/your_key_pair.public.pem%0A%20%20%20%60%60%60%0A4.%20Restart%20Dify.%0A%0AIf%20you%20still%20see%20signature%20errors%20after%20these%20steps%2C%20check%20for%20packaging%20tool%20warnings%2C%20verify%20your%20plugin%20package%20structure%2C%20and%20review%20the%20plugin%20daemon%20logs%20for%20more%20details%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/issues/29025%23issuecomment-3600986276%29.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 12, 2026):

@dosu
That is exactly what I did. Where can I find the detailed log? I just got an API error.

@WilliamChen-luckbob commented on GitHub (Jan 12, 2026): @dosu That is exactly what I did. Where can I find the detailed log? I just got an API error.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 12, 2026):

You can find detailed logs for plugin installation and signature verification errors in your Docker Compose setup by running docker compose logs plugin_daemon. These logs will show signature verification failures, Python tracebacks, or JSON decode errors from the plugin daemon during install attempts. You can also check the main Dify app logs with docker compose logs api or by reading /app/logs/server.log inside the api container for backend errors related to plugin upload and verification. If you want even more detail, set LOG_LEVEL=DEBUG and ENABLE_REQUEST_LOGGING=true in your .env file, then restart your containers. Look for keywords like signature, verification, or bad signature in the logs right after reproducing the error to pinpoint the cause.
Reference 1
Reference 2
Reference 3
Reference 4

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 12, 2026): <!-- Answer --> You can find detailed logs for plugin installation and signature verification errors in your Docker Compose setup by running `docker compose logs plugin_daemon`. These logs will show signature verification failures, Python tracebacks, or JSON decode errors from the plugin daemon during install attempts. You can also check the main Dify app logs with `docker compose logs api` or by reading `/app/logs/server.log` inside the `api` container for backend errors related to plugin upload and verification. If you want even more detail, set `LOG_LEVEL=DEBUG` and `ENABLE_REQUEST_LOGGING=true` in your `.env` file, then restart your containers. Look for keywords like `signature`, `verification`, or `bad signature` in the logs right after reproducing the error to pinpoint the cause. [Reference 1](https://github.com/langgenius/dify-official-plugins/issues/1811) [Reference 2](https://github.com/langgenius/dify-official-plugins/issues/1953) [Reference 3](https://github.com/langgenius/dify/blob/b63dfbf654ed2191aa94aa4bcf8d09c09cc90759/api/core/plugin/impl/base.py#L3-L364) [Reference 4](https://github.com/langgenius/dify/blob/b63dfbf654ed2191aa94aa4bcf8d09c09cc90759/docker/.env.example#L69-L1385) <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/973706cf-3fd8-4e94-86b2-dadfbfcfc2da?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/973706cf-3fd8-4e94-86b2-dadfbfcfc2da?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/973706cf-3fd8-4e94-86b2-dadfbfcfc2da?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/973706cf-3fd8-4e94-86b2-dadfbfcfc2da?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/973706cf-3fd8-4e94-86b2-dadfbfcfc2da?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/973706cf-3fd8-4e94-86b2-dadfbfcfc2da?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/973706cf-3fd8-4e94-86b2-dadfbfcfc2da?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%20can%20find%20detailed%20logs%20for%20plugin%20installation%20and%20signature%20verification%20errors%20in%20your%20Docker%20Compose%20setup%20by%20running%20%60docker%20compose%20logs%20plugin_daemon%60.%20These%20logs%20will%20show%20signature%20verification%20failures%2C%20Python%20tracebacks%2C%20or%20JSON%20decode%20errors%20from%20the%20plugin%20daemon%20during%20install%20attempts.%20You%20can%20also%20check%20the%20main%20Dify%20app%20logs%20with%20%60docker%20compose%20logs%20api%60%20or%20by%20reading%20%60/app/logs/server.log%60%20inside%20the%20%60api%60%20container%20for%20backend%20errors%20related%20to%20plugin%20upload%20and%20verification.%20If%20you%20want%20even%20more%20detail%2C%20set%20%60LOG_LEVEL%3DDEBUG%60%20and%20%60ENABLE_REQUEST_LOGGING%3Dtrue%60%20in%20your%20%60.env%60%20file%2C%20then%20restart%20your%20containers.%20Look%20for%20keywords%20like%20%60signature%60%2C%20%60verification%60%2C%20or%20%60bad%20signature%60%20in%20the%20logs%20right%20after%20reproducing%20the%20error%20to%20pinpoint%20the%20cause.%20%20%0A%5BReference%201%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1811%29%20%20%0A%5BReference%202%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1953%29%20%20%0A%5BReference%203%5D%28https%3A//github.com/langgenius/dify/blob/b63dfbf654ed2191aa94aa4bcf8d09c09cc90759/api/core/plugin/impl/base.py%23L3-L364%29%20%20%0A%5BReference%204%5D%28https%3A//github.com/langgenius/dify/blob/b63dfbf654ed2191aa94aa4bcf8d09c09cc90759/docker/.env.example%23L69-L1385%29)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 12, 2026):

@dosu
Here is all the detailed data I got:

api-1               | 2026-01-12 10:10:57.670 DEBUG [Dummy-38] [_trace.py:47]  - close.complete
api-1               | 2026-01-12 10:10:57.673 INFO [Dummy-38] [ext_request_logging.py:73]  - POST /console/api/workspaces/current/plugin/upload/pkg 400 66.367 
api-1               | 2026-01-12 10:10:57.674 DEBUG [Dummy-38] [ext_request_logging.py:96]  - Response 400 BAD REQUEST application/json, Response Body:
api-1               | {
api-1               |   "code": "invalid_param",
api-1               |   "message": "req_id: b50cb94ed8 PluginDaemonBadRequestError: plugin verification has been enabled, and the plugin you want to install has a bad signature",
api-1               |   "status": 400
api-1               | }

/console/api/workspaces/current/plugin/upload/pkg this request send the file to server but seems crashed before a signature check, maybe there are some bug in parameter?

------WebKitFormBoundaryaAqGjgLGc4BfHq2c
Content-Disposition: form-data; name="pkg"; filename="gemini_video.signed.difypkg"
Content-Type: application/octet-stream


------WebKitFormBoundaryaAqGjgLGc4BfHq2c--

the file is OK when running dify-plugin-windows-amd64.exe signature verify gemini_video.signed.difypkg -p plugin_pub.public.pem

and the plugin_pub.public.pem is currectly set in specific directory.

@WilliamChen-luckbob commented on GitHub (Jan 12, 2026): @dosu Here is all the detailed data I got: ``` api-1 | 2026-01-12 10:10:57.670 DEBUG [Dummy-38] [_trace.py:47] - close.complete api-1 | 2026-01-12 10:10:57.673 INFO [Dummy-38] [ext_request_logging.py:73] - POST /console/api/workspaces/current/plugin/upload/pkg 400 66.367 api-1 | 2026-01-12 10:10:57.674 DEBUG [Dummy-38] [ext_request_logging.py:96] - Response 400 BAD REQUEST application/json, Response Body: api-1 | { api-1 | "code": "invalid_param", api-1 | "message": "req_id: b50cb94ed8 PluginDaemonBadRequestError: plugin verification has been enabled, and the plugin you want to install has a bad signature", api-1 | "status": 400 api-1 | } ``` `/console/api/workspaces/current/plugin/upload/pkg` this request send the file to server but seems crashed before a signature check, maybe there are some bug in parameter? ``` ------WebKitFormBoundaryaAqGjgLGc4BfHq2c Content-Disposition: form-data; name="pkg"; filename="gemini_video.signed.difypkg" Content-Type: application/octet-stream ------WebKitFormBoundaryaAqGjgLGc4BfHq2c-- ``` the file is OK when running `dify-plugin-windows-amd64.exe signature verify gemini_video.signed.difypkg -p plugin_pub.public.pem` and the plugin_pub.public.pem is currectly set in specific directory.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 12, 2026):

Your plugin package passing local signature verification but failing in Dify usually means the plugin daemon can't find or use your public key for verification, or the environment isn't set up as expected.

Here’s what to check:

  1. Exec into the plugin_daemon container and confirm that your public key (plugin_pub.public.pem) is present at the exact path set in the THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS environment variable. The default mount is /app/storage, so if you set THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS=/app/storage/public_keys/plugin_pub.public.pem, the file must exist there inside the container—not just on your host (docker-compose reference).

  2. Inside the plugin_daemon container, run printenv to confirm FORCE_VERIFYING_SIGNATURE, ENFORCE_LANGGENIUS_PLUGIN_SIGNATURES, and THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS are set as you expect (.env.example reference).

  3. Make sure the plugin package was not modified after signing, and that the public key you used for local verification is the same one referenced by the plugin daemon.

  4. Try uploading the plugin again, then immediately run docker compose logs plugin_daemon to capture any detailed error messages about the signature verification process. Look for lines mentioning “signature”, “verification”, or “bad signature”.

Common issues are: the public key file is missing or in the wrong place inside the container, the environment variable path is wrong, or the plugin was repackaged after signing. If you still get the error, paste the relevant plugin_daemon log output here for further diagnosis.

Let me know what you find!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 12, 2026): <!-- Answer --> Your plugin package passing local signature verification but failing in Dify usually means the plugin daemon can't find or use your public key for verification, or the environment isn't set up as expected. Here’s what to check: 1. Exec into the plugin_daemon container and confirm that your public key (plugin_pub.public.pem) is present at the exact path set in the THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS environment variable. The default mount is /app/storage, so if you set THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS=/app/storage/public_keys/plugin_pub.public.pem, the file must exist there inside the container—not just on your host ([docker-compose reference](https://github.com/langgenius/dify/blob/b63dfbf654ed2191aa94aa4bcf8d09c09cc90759/docker/docker-compose-template.yaml#L280-L327)). 2. Inside the plugin_daemon container, run printenv to confirm FORCE_VERIFYING_SIGNATURE, ENFORCE_LANGGENIUS_PLUGIN_SIGNATURES, and THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS are set as you expect ([.env.example reference](https://github.com/langgenius/dify/blob/b63dfbf654ed2191aa94aa4bcf8d09c09cc90759/docker/.env.example#L1364-L1405)). 3. Make sure the plugin package was not modified after signing, and that the public key you used for local verification is the same one referenced by the plugin daemon. 4. Try uploading the plugin again, then immediately run docker compose logs plugin_daemon to capture any detailed error messages about the signature verification process. Look for lines mentioning “signature”, “verification”, or “bad signature”. Common issues are: the public key file is missing or in the wrong place inside the container, the environment variable path is wrong, or the plugin was repackaged after signing. If you still get the error, paste the relevant plugin_daemon log output here for further diagnosis. Let me know what you find! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/061d6030-ec06-412e-9e5c-2836c0f9eb5c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/061d6030-ec06-412e-9e5c-2836c0f9eb5c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/061d6030-ec06-412e-9e5c-2836c0f9eb5c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/061d6030-ec06-412e-9e5c-2836c0f9eb5c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/061d6030-ec06-412e-9e5c-2836c0f9eb5c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/061d6030-ec06-412e-9e5c-2836c0f9eb5c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/061d6030-ec06-412e-9e5c-2836c0f9eb5c?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Your%20plugin%20package%20passing%20local%20signature%20verification%20but%20failing%20in%20Dify%20usually%20means%20the%20plugin%20daemon%20can%27t%20find%20or%20use%20your%20public%20key%20for%20verification%2C%20or%20the%20environment%20isn%27t%20set%20up%20as%20expected.%0A%0AHere%E2%80%99s%20what%20to%20check%3A%0A%0A1.%20Exec%20into%20the%20plugin_daemon%20container%20and%20confirm%20that%20your%20public%20key%20%28plugin_pub.public.pem%29%20is%20present%20at%20the%20exact%20path%20set%20in%20the%20THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS%20environment%20variable.%20The%20default%20mount%20is%20/app/storage%2C%20so%20if%20you%20set%20THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS%3D/app/storage/public_keys/plugin_pub.public.pem%2C%20the%20file%20must%20exist%20there%20inside%20the%20container%E2%80%94not%20just%20on%20your%20host%C2%A0%28%5Bdocker-compose%20reference%5D%28https%3A//github.com/langgenius/dify/blob/b63dfbf654ed2191aa94aa4bcf8d09c09cc90759/docker/docker-compose-template.yaml%23L280-L327%29%29.%0A%0A2.%20Inside%20the%20plugin_daemon%20container%2C%20run%20printenv%20to%20confirm%20FORCE_VERIFYING_SIGNATURE%2C%20ENFORCE_LANGGENIUS_PLUGIN_SIGNATURES%2C%20and%20THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS%20are%20set%20as%20you%20expect%C2%A0%28%5B.env.example%20reference%5D%28https%3A//github.com/langgenius/dify/blob/b63dfbf654ed2191aa94aa4bcf8d09c09cc90759/docker/.env.example%23L1364-L1405%29%29.%0A%0A3.%20Make%20sure%20the%20plugin%20package%20was%20not%20modified%20after%20signing%2C%20and%20that%20the%20public%20key%20you%20used%20for%20local%20verification%20is%20the%20same%20one%20referenced%20by%20the%20plugin%20daemon.%0A%0A4.%20Try%20uploading%20the%20plugin%20again%2C%20then%20immediately%20run%20docker%20compose%20logs%20plugin_daemon%20to%20capture%20any%20detailed%20error%20messages%20about%20the%20signature%20verification%20process.%20Look%20for%20lines%20mentioning%20%E2%80%9Csignature%E2%80%9D%2C%20%E2%80%9Cverification%E2%80%9D%2C%20or%20%E2%80%9Cbad%20signature%E2%80%9D.%0A%0ACommon%20issues%20are%3A%20the%20public%20key%20file%20is%20missing%20or%20in%20the%20wrong%20place%20inside%20the%20container%2C%20the%20environment%20variable%20path%20is%20wrong%2C%20or%20the%20plugin%20was%20repackaged%20after%20signing.%20If%20you%20still%20get%20the%20error%2C%20paste%20the%20relevant%20plugin_daemon%20log%20output%20here%20for%20further%20diagnosis.%0A%0ALet%20me%20know%20what%20you%20find%21)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026):

@dosu
Finally, I’m still in testing and found a big debugging issue with the Dify plugin’s structure:

First, I can’t run it in PyCharm debug mode at all — the program just freezes forever with no output, it’s completely stuck. But it works fine when run in pycharm normally, and it is normal connecting to dify.

Second, even without breakpoints, I tried packaging it and running it in Dify: there’s no trace of plugin logs anywhere, and it keeps throwing errors even though unit tests pass.

Here I need some help, how can I find my plugin's logs? I'm now using docker compose logs

but the plugin logs never occurs, I have set all the logs to WARNING and above, but NOTHING showing in the logs at all.

This is making testing really tough for me.

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026): @dosu Finally, I’m still in testing and found a big debugging issue with the Dify plugin’s structure: First, I can’t run it in PyCharm debug mode at all — the program just freezes forever with no output, it’s completely stuck. But it works fine when run in pycharm normally, and it is normal connecting to dify. Second, even without breakpoints, I tried packaging it and running it in Dify: there’s no trace of plugin logs anywhere, and it keeps throwing errors even though unit tests pass. Here I need some help, how can I find my plugin's logs? I'm now using `docker compose logs` but the plugin logs never occurs, I have set all the logs to WARNING and above, but NOTHING showing in the logs at all. This is making testing really tough for me.
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026):

@dosu
Also, I’m trying to figure out how to use the existing structure to save videos to /app/storage/tools/xxx. I checked other tools for reference but couldn’t find any code to configure this path setup.

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026): @dosu Also, I’m trying to figure out how to use the existing structure to save videos to `/app/storage/tools/xxx`. I checked other tools for reference but couldn’t find any code to configure this path setup.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 13, 2026):

There’s no built-in helper or config for this—plugins/tools just construct the path manually. To save videos to /app/storage/tools/xxx, use standard Python code like:

import os

tool_dir = '/app/storage/tools/xxx'
os.makedirs(tool_dir, exist_ok=True)  # Ensure the directory exists

video_path = os.path.join(tool_dir, 'your_video.mp4')
with open(video_path, 'wb') as f:
    f.write(video_bytes)  # video_bytes is your binary video data

This directory is writable by default if your Docker volume is mounted correctly. If you want to make the output path configurable, you can use an environment variable or a plugin config parameter, but most plugins just hardcode or build the path inline. Always call os.makedirs with exist_ok=True before writing, to avoid errors if the directory doesn’t exist.

There’s no convention for subdirectory naming—just use a unique name for your tool or plugin. If you need to persist files across restarts, /app/storage/tools/xxx is the right place.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 13, 2026): <!-- Answer --> There’s no built-in helper or config for this—plugins/tools just construct the path manually. To save videos to /app/storage/tools/xxx, use standard Python code like: ```python import os tool_dir = '/app/storage/tools/xxx' os.makedirs(tool_dir, exist_ok=True) # Ensure the directory exists video_path = os.path.join(tool_dir, 'your_video.mp4') with open(video_path, 'wb') as f: f.write(video_bytes) # video_bytes is your binary video data ``` This directory is writable by default if your Docker volume is mounted correctly. If you want to make the output path configurable, you can use an environment variable or a plugin config parameter, but most plugins just hardcode or build the path inline. Always call os.makedirs with exist_ok=True before writing, to avoid errors if the directory doesn’t exist. There’s no convention for subdirectory naming—just use a unique name for your tool or plugin. If you need to persist files across restarts, /app/storage/tools/xxx is the right place. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/39d77e11-278d-46c9-a5a8-2dbe8ac8c930?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/39d77e11-278d-46c9-a5a8-2dbe8ac8c930?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/39d77e11-278d-46c9-a5a8-2dbe8ac8c930?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/39d77e11-278d-46c9-a5a8-2dbe8ac8c930?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/39d77e11-278d-46c9-a5a8-2dbe8ac8c930?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/39d77e11-278d-46c9-a5a8-2dbe8ac8c930?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/39d77e11-278d-46c9-a5a8-2dbe8ac8c930?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=There%E2%80%99s%20no%20built-in%20helper%20or%20config%20for%20this%E2%80%94plugins/tools%20just%20construct%20the%20path%20manually.%20To%20save%20videos%20to%20/app/storage/tools/xxx%2C%20use%20standard%20Python%20code%20like%3A%0A%0A%60%60%60python%0Aimport%20os%0A%0Atool_dir%20%3D%20%27/app/storage/tools/xxx%27%0Aos.makedirs%28tool_dir%2C%20exist_ok%3DTrue%29%20%20%23%20Ensure%20the%20directory%20exists%0A%0Avideo_path%20%3D%20os.path.join%28tool_dir%2C%20%27your_video.mp4%27%29%0Awith%20open%28video_path%2C%20%27wb%27%29%20as%20f%3A%0A%20%20%20%20f.write%28video_bytes%29%20%20%23%20video_bytes%20is%20your%20binary%20video%20data%0A%60%60%60%0A%0AThis%20directory%20is%20writable%20by%20default%20if%20your%20Docker%20volume%20is%20mounted%20correctly.%20If%20you%20want%20to%20make%20the%20output%20path%20configurable%2C%20you%20can%20use%20an%20environment%20variable%20or%20a%20plugin%20config%20parameter%2C%20but%20most%20plugins%20just%20hardcode%20or%20build%20the%20path%20inline.%20Always%20call%20os.makedirs%20with%20exist_ok%3DTrue%20before%20writing%2C%20to%20avoid%20errors%20if%20the%20directory%20doesn%E2%80%99t%20exist.%0A%0AThere%E2%80%99s%20no%20convention%20for%20subdirectory%20naming%E2%80%94just%20use%20a%20unique%20name%20for%20your%20tool%20or%20plugin.%20If%20you%20need%20to%20persist%20files%20across%20restarts%2C%20/app/storage/tools/xxx%20is%20the%20right%20place.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026):

@dosu
But when I checked the source code, I couldn’t find any code in the tools that explicitly saves the generated images/videos to /app/storage/tools/xxx. However, in practice, all files end up being saved here, and xxx is a UUID — I don’t know how this ID is generated either.

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026): @dosu But when I checked the source code, I couldn’t find any code in the tools that explicitly saves the generated images/videos to /app/storage/tools/xxx. However, in practice, all files end up being saved here, and xxx is a UUID — I don’t know how this ID is generated either.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 13, 2026):

The reason you don’t see explicit code in most tools for saving files to /app/storage/tools/xxx is because Dify handles this automatically via its core file management logic. When a tool or plugin saves a file (like an image or video), it typically calls a helper like ToolFileManager.create_file_by_raw or create_file_by_url. These methods generate a unique filename using uuid4().hex and combine it with the tenant ID to build a path like tools/{tenant_id}/{uuid}.{ext}. The file is then saved to /app/storage/tools/{tenant_id}/{uuid}.{ext} in your container see source code.

So, the “xxx” UUID you see is actually part of the filename, not a directory. It’s generated automatically for each file to ensure uniqueness. The tenant ID is used as the directory under /app/storage/tools, organizing files per tenant. This is why all generated files end up in that structure, even if you don’t see manual path handling in the plugin/tool code.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 13, 2026): <!-- Answer --> The reason you don’t see explicit code in most tools for saving files to `/app/storage/tools/xxx` is because Dify handles this automatically via its core file management logic. When a tool or plugin saves a file (like an image or video), it typically calls a helper like `ToolFileManager.create_file_by_raw` or `create_file_by_url`. These methods generate a unique filename using `uuid4().hex` and combine it with the tenant ID to build a path like `tools/{tenant_id}/{uuid}.{ext}`. The file is then saved to `/app/storage/tools/{tenant_id}/{uuid}.{ext}` in your container [see source code](https://github.com/langgenius/dify/blob/1fbdf6b465b8f2df07fb9db2cc09adc42be79b6b/api/core/tools/tool_file_manager.py#L10-L150). So, the “xxx” UUID you see is actually part of the filename, not a directory. It’s generated automatically for each file to ensure uniqueness. The tenant ID is used as the directory under `/app/storage/tools`, organizing files per tenant. This is why all generated files end up in that structure, even if you don’t see manual path handling in the plugin/tool code. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/8d170284-f488-4865-a1db-ef449374905c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8d170284-f488-4865-a1db-ef449374905c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8d170284-f488-4865-a1db-ef449374905c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8d170284-f488-4865-a1db-ef449374905c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8d170284-f488-4865-a1db-ef449374905c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8d170284-f488-4865-a1db-ef449374905c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8d170284-f488-4865-a1db-ef449374905c?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20reason%20you%20don%E2%80%99t%20see%20explicit%20code%20in%20most%20tools%20for%20saving%20files%20to%20%60/app/storage/tools/xxx%60%20is%20because%20Dify%20handles%20this%20automatically%20via%20its%20core%20file%20management%20logic.%20When%20a%20tool%20or%20plugin%20saves%20a%20file%20%28like%20an%20image%20or%20video%29%2C%20it%20typically%20calls%20a%20helper%20like%20%60ToolFileManager.create_file_by_raw%60%20or%20%60create_file_by_url%60.%20These%20methods%20generate%20a%20unique%20filename%20using%20%60uuid4%28%29.hex%60%20and%20combine%20it%20with%20the%20tenant%20ID%20to%20build%20a%20path%20like%20%60tools/%7Btenant_id%7D/%7Buuid%7D.%7Bext%7D%60.%20The%20file%20is%20then%20saved%20to%20%60/app/storage/tools/%7Btenant_id%7D/%7Buuid%7D.%7Bext%7D%60%20in%20your%20container%C2%A0%5Bsee%20source%20code%5D%28https%3A//github.com/langgenius/dify/blob/1fbdf6b465b8f2df07fb9db2cc09adc42be79b6b/api/core/tools/tool_file_manager.py%23L10-L150%29.%0A%0ASo%2C%20the%20%E2%80%9Cxxx%E2%80%9D%20UUID%20you%20see%20is%20actually%20part%20of%20the%20filename%2C%20not%20a%20directory.%20It%E2%80%99s%20generated%20automatically%20for%20each%20file%20to%20ensure%20uniqueness.%20The%20tenant%20ID%20is%20used%20as%20the%20directory%20under%20%60/app/storage/tools%60%2C%20organizing%20files%20per%20tenant.%20This%20is%20why%20all%20generated%20files%20end%20up%20in%20that%20structure%2C%20even%20if%20you%20don%E2%80%99t%20see%20manual%20path%20handling%20in%20the%20plugin/tool%20code.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026):

@dosu
well ,can you help me to findout where to find the plugin's real log? when plugin running failes, I got this error:

Run failed: An error occurred in the william_chen/gemini_video/gemini_video, please contact the author of william_chen/gemini_video/gemini_video for help, error type: ValidationError, error details: 1 validation error for SessionMessage
data
Input should be a valid dictionary [type=dict_type, input_value=(ToolInvokeMessage(type=<...qh5r.mp4'), meta=None),), input_type=tuple]
For further information visit https://errors.pydantic.dev/2.12/v/dict_type

But it is not actually what I need, I don't know which line is the cause, here is my code:

import json
import logging
import re
import traceback
import uuid
from typing import Any, Generator

from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from google import genai


class TestDownloadVideoTool(Tool):
    def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
        try:
            _gemini_api_key = self.runtime.credentials["gemini_api_key"]
            genai_client = genai.Client(
                api_key=_gemini_api_key,
            )

            logging.warning(f"Generating video with parameters: {json.dumps(tool_parameters)}")

            video_url = tool_parameters.get("video_url")
            video_bytes = genai_client.files.download(file=video_url)
            pattern = r'/files/([^:]+):download'

            # regex match
            match = re.search(pattern, video_url)
            if match:
                target_str = match.group(1)
                video_name = f'{target_str}.mp4'
            else:
                id = str(uuid.uuid4())
                video_name = f"output{id}.mp4"

            logging.warning(f"Generated video name: {video_name}")

            # save
            video = genai.types.Video(video_bytes=video_bytes, mime_type="video/mp4")

            logging.warning(f"video downloaded")

            video.save(video_name)
            yield self.create_text_message(f"Video is available at: {video_name}"),
            yield self.create_blob_message(blob=video.video_bytes, meta={"mime_type": "video/mp4"}),
        except Exception as e:
            logging.error(f'{e}\ntraceback:{traceback.format_exc()}')
            raise e

As you can see, my logging level is info, but nothing printed as expected, just a log with no key cause.

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026): @dosu well ,can you help me to findout where to find the plugin's real log? when plugin running failes, I got this error: ``` Run failed: An error occurred in the william_chen/gemini_video/gemini_video, please contact the author of william_chen/gemini_video/gemini_video for help, error type: ValidationError, error details: 1 validation error for SessionMessage data Input should be a valid dictionary [type=dict_type, input_value=(ToolInvokeMessage(type=<...qh5r.mp4'), meta=None),), input_type=tuple] For further information visit https://errors.pydantic.dev/2.12/v/dict_type ``` But it is not actually what I need, I don't know which line is the cause, here is my code: ``` import json import logging import re import traceback import uuid from typing import Any, Generator from dify_plugin import Tool from dify_plugin.entities.tool import ToolInvokeMessage from google import genai class TestDownloadVideoTool(Tool): def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: try: _gemini_api_key = self.runtime.credentials["gemini_api_key"] genai_client = genai.Client( api_key=_gemini_api_key, ) logging.warning(f"Generating video with parameters: {json.dumps(tool_parameters)}") video_url = tool_parameters.get("video_url") video_bytes = genai_client.files.download(file=video_url) pattern = r'/files/([^:]+):download' # regex match match = re.search(pattern, video_url) if match: target_str = match.group(1) video_name = f'{target_str}.mp4' else: id = str(uuid.uuid4()) video_name = f"output{id}.mp4" logging.warning(f"Generated video name: {video_name}") # save video = genai.types.Video(video_bytes=video_bytes, mime_type="video/mp4") logging.warning(f"video downloaded") video.save(video_name) yield self.create_text_message(f"Video is available at: {video_name}"), yield self.create_blob_message(blob=video.video_bytes, meta={"mime_type": "video/mp4"}), except Exception as e: logging.error(f'{e}\ntraceback:{traceback.format_exc()}') raise e ``` As you can see, my logging level is info, but nothing printed as expected, just a log with no key cause.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 13, 2026):

Your plugin logs should appear in the plugin_daemon logs—use docker compose logs plugin_daemon or check the ./volumes/plugin_daemon directory on your host if you have it mounted. To make sure your logs are actually emitted, add this at the top of your plugin code (before any logging calls):

import logging
logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()])

If you want more detail, set LOG_LEVEL=DEBUG and ENABLE_REQUEST_LOGGING=True in your .env and restart your containers. You can also set LOG_FILE=/app/logs/server.log to write logs to a file, which you can access with docker compose logs api or by reading the file inside the container details.

Your ValidationError is caused by a subtle Python syntax issue: you have a trailing comma after your yield statements, which makes them yield a tuple instead of a single message object. For example:

yield self.create_text_message(f"Video is available at: {video_name}"),  # <-- yields a tuple!

should be:

yield self.create_text_message(f"Video is available at: {video_name}")

Remove the trailing commas from both yield statements. This will fix the Pydantic error and make debugging much easier reference.

If you still don’t see logs, double-check your logging config and make sure your plugin isn’t swallowing exceptions before logging them. For runtime errors, always check both plugin_daemon and api logs for tracebacks reference.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 13, 2026): <!-- Answer --> Your plugin logs should appear in the plugin_daemon logs—use `docker compose logs plugin_daemon` or check the `./volumes/plugin_daemon` directory on your host if you have it mounted. To make sure your logs are actually emitted, add this at the top of your plugin code (before any logging calls): ```python import logging logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()]) ``` If you want more detail, set `LOG_LEVEL=DEBUG` and `ENABLE_REQUEST_LOGGING=True` in your `.env` and restart your containers. You can also set `LOG_FILE=/app/logs/server.log` to write logs to a file, which you can access with `docker compose logs api` or by reading the file inside the container [details](https://github.com/langgenius/dify/issues/25491). Your ValidationError is caused by a subtle Python syntax issue: you have a trailing comma after your yield statements, which makes them yield a tuple instead of a single message object. For example: ```python yield self.create_text_message(f"Video is available at: {video_name}"), # <-- yields a tuple! ``` should be: ```python yield self.create_text_message(f"Video is available at: {video_name}") ``` Remove the trailing commas from both yield statements. This will fix the Pydantic error and make debugging much easier [reference](https://errors.pydantic.dev/2.12/v/dict_type). If you still don’t see logs, double-check your logging config and make sure your plugin isn’t swallowing exceptions before logging them. For runtime errors, always check both plugin_daemon and api logs for tracebacks [reference](https://github.com/langgenius/dify-official-plugins/issues/1811). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/51d4aad8-f980-4e6d-b37c-5fc01ac1c526?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/51d4aad8-f980-4e6d-b37c-5fc01ac1c526?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/51d4aad8-f980-4e6d-b37c-5fc01ac1c526?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/51d4aad8-f980-4e6d-b37c-5fc01ac1c526?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/51d4aad8-f980-4e6d-b37c-5fc01ac1c526?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/51d4aad8-f980-4e6d-b37c-5fc01ac1c526?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/51d4aad8-f980-4e6d-b37c-5fc01ac1c526?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Your%20plugin%20logs%20should%20appear%20in%20the%20plugin_daemon%20logs%E2%80%94use%20%60docker%20compose%20logs%20plugin_daemon%60%20or%20check%20the%20%60./volumes/plugin_daemon%60%20directory%20on%20your%20host%20if%20you%20have%20it%20mounted.%20To%20make%20sure%20your%20logs%20are%20actually%20emitted%2C%20add%20this%20at%20the%20top%20of%20your%20plugin%20code%20%28before%20any%20logging%20calls%29%3A%0A%0A%60%60%60python%0Aimport%20logging%0Alogging.basicConfig%28level%3Dlogging.INFO%2C%20handlers%3D%5Blogging.StreamHandler%28%29%5D%29%0A%60%60%60%0A%0AIf%20you%20want%20more%20detail%2C%20set%20%60LOG_LEVEL%3DDEBUG%60%20and%20%60ENABLE_REQUEST_LOGGING%3DTrue%60%20in%20your%20%60.env%60%20and%20restart%20your%20containers.%20You%20can%20also%20set%20%60LOG_FILE%3D/app/logs/server.log%60%20to%20write%20logs%20to%20a%20file%2C%20which%20you%20can%20access%20with%20%60docker%20compose%20logs%20api%60%20or%20by%20reading%20the%20file%20inside%20the%20container%C2%A0%5Bdetails%5D%28https%3A//github.com/langgenius/dify/issues/25491%29.%0A%0AYour%20ValidationError%20is%20caused%20by%20a%20subtle%20Python%20syntax%20issue%3A%20you%20have%20a%20trailing%20comma%20after%20your%20yield%20statements%2C%20which%20makes%20them%20yield%20a%20tuple%20instead%20of%20a%20single%20message%20object.%20For%20example%3A%0A%0A%60%60%60python%0Ayield%20self.create_text_message%28f%22Video%20is%20available%20at%3A%20%7Bvideo_name%7D%22%29%2C%20%20%23%20%3C--%20yields%20a%20tuple%21%0A%60%60%60%0Ashould%20be%3A%0A%60%60%60python%0Ayield%20self.create_text_message%28f%22Video%20is%20available%20at%3A%20%7Bvideo_name%7D%22%29%0A%60%60%60%0ARemove%20the%20trailing%20commas%20from%20both%20yield%20statements.%20This%20will%20fix%20the%20Pydantic%20error%20and%20make%20debugging%20much%20easier%C2%A0%5Breference%5D%28https%3A//errors.pydantic.dev/2.12/v/dict_type%29.%0A%0AIf%20you%20still%20don%E2%80%99t%20see%20logs%2C%20double-check%20your%20logging%20config%20and%20make%20sure%20your%20plugin%20isn%E2%80%99t%20swallowing%20exceptions%20before%20logging%20them.%20For%20runtime%20errors%2C%20always%20check%20both%20plugin_daemon%20and%20api%20logs%20for%20tracebacks%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/1811%29.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026):

@dosu
Greate! the plugin is running, but plugin logs are still not showing at all.
Also, I have another problem, how to display a video in chat conversation?
As you can see, my testing code is showing below:

import json
import logging
import re
import traceback
import uuid
from typing import Any, Generator

from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from google import genai

logging.basicConfig(
    level=logging.INFO,
    handlers=[logging.StreamHandler()],
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)


class TestDownloadVideoTool(Tool):
    def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
        try:
            _gemini_api_key = self.runtime.credentials["gemini_api_key"]
            genai_client = genai.Client(
                api_key=_gemini_api_key,
            )

            logging.warning(f"Generating video with parameters: {json.dumps(tool_parameters)}")
            yield self.create_text_message(f"Generating video with parameters: {json.dumps(tool_parameters)}")

            video_url = tool_parameters.get("video_url")
            video_bytes = genai_client.files.download(file=video_url)
            pattern = r'/files/([^:]+):download'

            # regex match
            match = re.search(pattern, video_url)
            if match:
                target_str = match.group(1)
                video_name = f'{target_str}.mp4'
            else:
                id = str(uuid.uuid4())
                video_name = f"output{id}.mp4"

            logging.warning(f"Generated video name: {video_name}")
            yield self.create_text_message(f"Generated video name: {video_name}")

            # save
            video = genai.types.Video(video_bytes=video_bytes, mime_type="video/mp4")

            logging.warning(f"video downloaded")
            yield self.create_text_message(f"video downloaded")

            video.save(video_name)

            yield self.create_text_message(f"Video is available at: {video_name}")
            yield self.create_blob_message(blob=video.video_bytes, meta={"mime_type": "video/mp4"})
        except Exception as e:
            logging.error(f'{e}\ntraceback:{traceback.format_exc()}')
            raise e

this row does not printing anything.
yield self.create_blob_message(blob=video.video_bytes, meta={"mime_type": "video/mp4"})
I need the MP4 file showing in chatbox

also, no plugin logs in plugin_daemon container
here is the plugin_daemon log

deo&plugin_id=william_chen%2Fgemini_video"
plugin_daemon-1  | [GIN] 2026/01/13 - 10:06:35 | 200 |  694.208993ms |   192.168.160.8 | POST     "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/dispatch/tool/invoke"
plugin_daemon-1  | [GIN] 2026/01/13 - 10:06:39 | 200 |      921.46µs |   192.168.160.8 | GET      "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/tool?provider=gemini_video&plugin_id=william_chen%2Fgemini_video"
plugin_daemon-1  | [GIN] 2026/01/13 - 10:06:45 | 200 |     615.327µs |   192.168.160.8 | GET      "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/triggers?page=1&page_size=256"
plugin_daemon-1  | [GIN] 2026/01/13 - 10:06:47 | 200 |     998.768µs |   192.168.160.8 | GET      "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/tool?provider=gemini_video&plugin_id=william_chen%2Fgemini_video"
plugin_daemon-1  | [GIN] 2026/01/13 - 10:06:53 | 200 |     2.25897ms |   192.168.160.8 | GET      "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/triggers?page=1&page_size=256"
plugin_daemon-1  | [GIN] 2026/01/13 - 10:06:55 | 200 |     971.233µs |   192.168.160.8 | GET      "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/tool?provider=gemini_video&plugin_id=william_chen%2Fgemini_video"

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026): @dosu Greate! the plugin is running, but plugin logs are still not showing at all. Also, I have another problem, how to display a video in chat conversation? As you can see, my testing code is showing below: ``` import json import logging import re import traceback import uuid from typing import Any, Generator from dify_plugin import Tool from dify_plugin.entities.tool import ToolInvokeMessage from google import genai logging.basicConfig( level=logging.INFO, handlers=[logging.StreamHandler()], format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) class TestDownloadVideoTool(Tool): def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: try: _gemini_api_key = self.runtime.credentials["gemini_api_key"] genai_client = genai.Client( api_key=_gemini_api_key, ) logging.warning(f"Generating video with parameters: {json.dumps(tool_parameters)}") yield self.create_text_message(f"Generating video with parameters: {json.dumps(tool_parameters)}") video_url = tool_parameters.get("video_url") video_bytes = genai_client.files.download(file=video_url) pattern = r'/files/([^:]+):download' # regex match match = re.search(pattern, video_url) if match: target_str = match.group(1) video_name = f'{target_str}.mp4' else: id = str(uuid.uuid4()) video_name = f"output{id}.mp4" logging.warning(f"Generated video name: {video_name}") yield self.create_text_message(f"Generated video name: {video_name}") # save video = genai.types.Video(video_bytes=video_bytes, mime_type="video/mp4") logging.warning(f"video downloaded") yield self.create_text_message(f"video downloaded") video.save(video_name) yield self.create_text_message(f"Video is available at: {video_name}") yield self.create_blob_message(blob=video.video_bytes, meta={"mime_type": "video/mp4"}) except Exception as e: logging.error(f'{e}\ntraceback:{traceback.format_exc()}') raise e ``` this row does not printing anything. `yield self.create_blob_message(blob=video.video_bytes, meta={"mime_type": "video/mp4"})` I need the MP4 file showing in chatbox also, no plugin logs in plugin_daemon container here is the plugin_daemon log ``` deo&plugin_id=william_chen%2Fgemini_video" plugin_daemon-1 | [GIN] 2026/01/13 - 10:06:35 | 200 | 694.208993ms | 192.168.160.8 | POST "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/dispatch/tool/invoke" plugin_daemon-1 | [GIN] 2026/01/13 - 10:06:39 | 200 | 921.46µs | 192.168.160.8 | GET "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/tool?provider=gemini_video&plugin_id=william_chen%2Fgemini_video" plugin_daemon-1 | [GIN] 2026/01/13 - 10:06:45 | 200 | 615.327µs | 192.168.160.8 | GET "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/triggers?page=1&page_size=256" plugin_daemon-1 | [GIN] 2026/01/13 - 10:06:47 | 200 | 998.768µs | 192.168.160.8 | GET "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/tool?provider=gemini_video&plugin_id=william_chen%2Fgemini_video" plugin_daemon-1 | [GIN] 2026/01/13 - 10:06:53 | 200 | 2.25897ms | 192.168.160.8 | GET "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/triggers?page=1&page_size=256" plugin_daemon-1 | [GIN] 2026/01/13 - 10:06:55 | 200 | 971.233µs | 192.168.160.8 | GET "/plugin/41c856e9-8ea1-4833-bdf3-eb1cb593b3fc/management/tool?provider=gemini_video&plugin_id=william_chen%2Fgemini_video" ```
Author
Owner

@dosubot[bot] commented on GitHub (Jan 13, 2026):

For plugin logs: Python logging output from your plugin should appear in the plugin_daemon logs if logging is configured correctly. Make sure you call logging.basicConfig at the very top of your plugin (before any logging calls or imports that might configure logging). If logs still don’t show, try setting LOG_LEVEL=DEBUG and ENABLE_REQUEST_LOGGING=True in your .env, then restart all containers. Also, check if your plugin is running as a subprocess—sometimes logs are only visible in the plugin_daemon container if the plugin isn’t running in a separate process or if stdout isn’t captured.

For video display in chat: Dify’s chat UI does not support inline MP4 playback directly in the chat bubble. Instead, it shows files as downloadable/previewable attachments using the FileList component. To ensure your video appears as an attachment, yield a blob message with both mime_type and filename in the meta, like this:

yield self.create_blob_message(
    blob=video.video_bytes,
    meta={
        "mime_type": "video/mp4",
        "filename": video_name,  # e.g., "output123.mp4"
    }
)

This will make the video show up as a downloadable file in the chat. If you want users to preview the video, they’ll need to click the file in the FileList; inline playback is not currently supported in the chat bubble itself. If you want to provide a direct download or streaming link, you can also yield a text or link message with the file URL.

Let me know if you still don’t see logs after these changes, or if the video file isn’t appearing as an attachment in chat.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 13, 2026): <!-- Answer --> For plugin logs: Python logging output from your plugin should appear in the plugin_daemon logs if logging is configured correctly. Make sure you call logging.basicConfig at the very top of your plugin (before any logging calls or imports that might configure logging). If logs still don’t show, try setting LOG_LEVEL=DEBUG and ENABLE_REQUEST_LOGGING=True in your .env, then restart all containers. Also, check if your plugin is running as a subprocess—sometimes logs are only visible in the plugin_daemon container if the plugin isn’t running in a separate process or if stdout isn’t captured. For video display in chat: Dify’s chat UI does not support inline MP4 playback directly in the chat bubble. Instead, it shows files as downloadable/previewable attachments using the FileList component. To ensure your video appears as an attachment, yield a blob message with both mime_type and filename in the meta, like this: ```python yield self.create_blob_message( blob=video.video_bytes, meta={ "mime_type": "video/mp4", "filename": video_name, # e.g., "output123.mp4" } ) ``` This will make the video show up as a downloadable file in the chat. If you want users to preview the video, they’ll need to click the file in the FileList; inline playback is not currently supported in the chat bubble itself. If you want to provide a direct download or streaming link, you can also yield a text or link message with the file URL. Let me know if you still don’t see logs after these changes, or if the video file isn’t appearing as an attachment in chat. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/589545fa-8bb9-4dad-a6ac-c43d6b45031b?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/589545fa-8bb9-4dad-a6ac-c43d6b45031b?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/589545fa-8bb9-4dad-a6ac-c43d6b45031b?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/589545fa-8bb9-4dad-a6ac-c43d6b45031b?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/589545fa-8bb9-4dad-a6ac-c43d6b45031b?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/589545fa-8bb9-4dad-a6ac-c43d6b45031b?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/589545fa-8bb9-4dad-a6ac-c43d6b45031b?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=For%20plugin%20logs%3A%20Python%20logging%20output%20from%20your%20plugin%20should%20appear%20in%20the%20plugin_daemon%20logs%20if%20logging%20is%20configured%20correctly.%20Make%20sure%20you%20call%20logging.basicConfig%20at%20the%20very%20top%20of%20your%20plugin%20%28before%20any%20logging%20calls%20or%20imports%20that%20might%20configure%20logging%29.%20If%20logs%20still%20don%E2%80%99t%20show%2C%20try%20setting%20LOG_LEVEL%3DDEBUG%20and%20ENABLE_REQUEST_LOGGING%3DTrue%20in%20your%20.env%2C%20then%20restart%20all%20containers.%20Also%2C%20check%20if%20your%20plugin%20is%20running%20as%20a%20subprocess%E2%80%94sometimes%20logs%20are%20only%20visible%20in%20the%20plugin_daemon%20container%20if%20the%20plugin%20isn%E2%80%99t%20running%20in%20a%20separate%20process%20or%20if%20stdout%20isn%E2%80%99t%20captured.%0A%0AFor%20video%20display%20in%20chat%3A%20Dify%E2%80%99s%20chat%20UI%20does%20not%20support%20inline%20MP4%20playback%20directly%20in%20the%20chat%20bubble.%20Instead%2C%20it%20shows%20files%20as%20downloadable/previewable%20attachments%20using%20the%20FileList%20component.%20To%20ensure%20your%20video%20appears%20as%20an%20attachment%2C%20yield%20a%20blob%20message%20with%20both%20mime_type%20and%20filename%20in%20the%20meta%2C%20like%20this%3A%0A%0A%60%60%60python%0Ayield%20self.create_blob_message%28%0A%20%20%20%20blob%3Dvideo.video_bytes%2C%0A%20%20%20%20meta%3D%7B%0A%20%20%20%20%20%20%20%20%22mime_type%22%3A%20%22video/mp4%22%2C%0A%20%20%20%20%20%20%20%20%22filename%22%3A%20video_name%2C%20%20%23%20e.g.%2C%20%22output123.mp4%22%0A%20%20%20%20%7D%0A%29%0A%60%60%60%0A%0AThis%20will%20make%20the%20video%20show%20up%20as%20a%20downloadable%20file%20in%20the%20chat.%20If%20you%20want%20users%20to%20preview%20the%20video%2C%20they%E2%80%99ll%20need%20to%20click%20the%20file%20in%20the%20FileList%3B%20inline%20playback%20is%20not%20currently%20supported%20in%20the%20chat%20bubble%20itself.%20If%20you%20want%20to%20provide%20a%20direct%20download%20or%20streaming%20link%2C%20you%20can%20also%20yield%20a%20text%20or%20link%20message%20with%20the%20file%20URL.%0A%0ALet%20me%20know%20if%20you%20still%20don%E2%80%99t%20see%20logs%20after%20these%20changes%2C%20or%20if%20the%20video%20file%20isn%E2%80%99t%20appearing%20as%20an%20attachment%20in%20chat.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026):

@dosu
actually, this code not showing anything. Here is my yield codes after downloading and saving the video from google.

# save video to local
video.save(video_name)
# yield the content in chat bubble
yield self.create_text_message(f"Video is available at: {video_name}\n\n")
yield self.create_blob_message(blob=video.video_bytes, meta={"mime_type": "video/mp4", "filename": video_name})

the video is properly saved in /app/storage/tools/{tenant_id}/{uuid}.{ext} and I checked it by xftp the video content is OK.
the create_text_message function works well but the create_blob_message function seems not working.

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026): @dosu actually, this code not showing anything. Here is my yield codes after downloading and saving the video from google. ```python # save video to local video.save(video_name) # yield the content in chat bubble yield self.create_text_message(f"Video is available at: {video_name}\n\n") yield self.create_blob_message(blob=video.video_bytes, meta={"mime_type": "video/mp4", "filename": video_name}) ``` the video is properly saved in `/app/storage/tools/{tenant_id}/{uuid}.{ext}` and I checked it by xftp the video content is OK. the `create_text_message` function works well but the `create_blob_message` function seems not working.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 13, 2026):

The reason your video isn't showing as an attachment in chat is that simply yielding create_blob_message with the video bytes and meta is not enough. Dify's backend expects to handle the file registration and linking automatically: when you yield a blob message, the system saves the file, registers it, and then emits a BINARY_LINK message with a URL for the frontend to display as an attachment. However, this only works if the blob is bytes, the meta includes both "mime_type" and "filename", and your Dify version is 1.4.0 or above (which requires the key "filename", not "file_name") ref.

Your code looks correct, but here are a few things to check:

  1. Make sure video.video_bytes is actually bytes, not a file object or something else.
  2. Double-check that meta uses "filename" (not "file_name") and "mime_type".
  3. Don't manually save the file to /app/storage/tools/xxx; just yield the blob message and let Dify handle the rest.
  4. If you are running a workflow or agent, ensure downstream nodes are not filtering out BINARY_LINK or LINK messages ref.
  5. If you are on Dify <1.4.0, upgrade, as older versions may not handle blob/file messages correctly.

A minimal working example:

yield self.create_blob_message(
    blob=video.video_bytes,  # must be bytes
    meta={
        "mime_type": "video/mp4",
        "filename": video_name,  # e.g., "output123.mp4"
    }
)

If this still doesn't work, check your Dify backend logs for errors related to file registration or message transformation. Also, note that the chat UI will show the video as a downloadable attachment, not as an inline player ref.

Let me know if you still don't see the file in chat after these checks!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 13, 2026): <!-- Answer --> The reason your video isn't showing as an attachment in chat is that simply yielding create_blob_message with the video bytes and meta is not enough. Dify's backend expects to handle the file registration and linking automatically: when you yield a blob message, the system saves the file, registers it, and then emits a BINARY_LINK message with a URL for the frontend to display as an attachment. However, this only works if the blob is bytes, the meta includes both "mime_type" and "filename", and your Dify version is 1.4.0 or above (which requires the key "filename", not "file_name") [ref](https://github.com/langgenius/dify/issues/19882). Your code looks correct, but here are a few things to check: 1. Make sure video.video_bytes is actually bytes, not a file object or something else. 2. Double-check that meta uses "filename" (not "file_name") and "mime_type". 3. Don't manually save the file to /app/storage/tools/xxx; just yield the blob message and let Dify handle the rest. 4. If you are running a workflow or agent, ensure downstream nodes are not filtering out BINARY_LINK or LINK messages [ref](https://github.com/langgenius/dify/issues/21307). 5. If you are on Dify <1.4.0, upgrade, as older versions may not handle blob/file messages correctly. A minimal working example: ```python yield self.create_blob_message( blob=video.video_bytes, # must be bytes meta={ "mime_type": "video/mp4", "filename": video_name, # e.g., "output123.mp4" } ) ``` If this still doesn't work, check your Dify backend logs for errors related to file registration or message transformation. Also, note that the chat UI will show the video as a downloadable attachment, not as an inline player [ref](https://github.com/langgenius/dify/issues/25391). Let me know if you still don't see the file in chat after these checks! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/03c0b01c-277d-4e31-b371-2b22e0f8ec7d?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/03c0b01c-277d-4e31-b371-2b22e0f8ec7d?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/03c0b01c-277d-4e31-b371-2b22e0f8ec7d?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/03c0b01c-277d-4e31-b371-2b22e0f8ec7d?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/03c0b01c-277d-4e31-b371-2b22e0f8ec7d?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/03c0b01c-277d-4e31-b371-2b22e0f8ec7d?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/03c0b01c-277d-4e31-b371-2b22e0f8ec7d?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20reason%20your%20video%20isn%27t%20showing%20as%20an%20attachment%20in%20chat%20is%20that%20simply%20yielding%20create_blob_message%20with%20the%20video%20bytes%20and%20meta%20is%20not%20enough.%20Dify%27s%20backend%20expects%20to%20handle%20the%20file%20registration%20and%20linking%20automatically%3A%20when%20you%20yield%20a%20blob%20message%2C%20the%20system%20saves%20the%20file%2C%20registers%20it%2C%20and%20then%20emits%20a%20BINARY_LINK%20message%20with%20a%20URL%20for%20the%20frontend%20to%20display%20as%20an%20attachment.%20However%2C%20this%20only%20works%20if%20the%20blob%20is%20bytes%2C%20the%20meta%20includes%20both%20%22mime_type%22%20and%20%22filename%22%2C%20and%20your%20Dify%20version%20is%201.4.0%20or%20above%20%28which%20requires%20the%20key%20%22filename%22%2C%20not%20%22file_name%22%29%C2%A0%5Bref%5D%28https%3A//github.com/langgenius/dify/issues/19882%29.%0A%0AYour%20code%20looks%20correct%2C%20but%20here%20are%20a%20few%20things%20to%20check%3A%0A%0A1.%20Make%20sure%20video.video_bytes%20is%20actually%20bytes%2C%20not%20a%20file%20object%20or%20something%20else.%0A2.%20Double-check%20that%20meta%20uses%20%22filename%22%20%28not%20%22file_name%22%29%20and%20%22mime_type%22.%0A3.%20Don%27t%20manually%20save%20the%20file%20to%20/app/storage/tools/xxx%3B%20just%20yield%20the%20blob%20message%20and%20let%20Dify%20handle%20the%20rest.%0A4.%20If%20you%20are%20running%20a%20workflow%20or%20agent%2C%20ensure%20downstream%20nodes%20are%20not%20filtering%20out%20BINARY_LINK%20or%20LINK%20messages%C2%A0%5Bref%5D%28https%3A//github.com/langgenius/dify/issues/21307%29.%0A5.%20If%20you%20are%20on%20Dify%20%3C1.4.0%2C%20upgrade%2C%20as%20older%20versions%20may%20not%20handle%20blob/file%20messages%20correctly.%0A%0AA%20minimal%20working%20example%3A%0A%0A%60%60%60python%0Ayield%20self.create_blob_message%28%0A%20%20%20%20blob%3Dvideo.video_bytes%2C%20%20%23%20must%20be%20bytes%0A%20%20%20%20meta%3D%7B%0A%20%20%20%20%20%20%20%20%22mime_type%22%3A%20%22video/mp4%22%2C%0A%20%20%20%20%20%20%20%20%22filename%22%3A%20video_name%2C%20%20%23%20e.g.%2C%20%22output123.mp4%22%0A%20%20%20%20%7D%0A%29%0A%60%60%60%0A%0AIf%20this%20still%20doesn%27t%20work%2C%20check%20your%20Dify%20backend%20logs%20for%20errors%20related%20to%20file%20registration%20or%20message%20transformation.%20Also%2C%20note%20that%20the%20chat%20UI%20will%20show%20the%20video%20as%20a%20downloadable%20attachment%2C%20not%20as%20an%20inline%20player%C2%A0%5Bref%5D%28https%3A//github.com/langgenius/dify/issues/25391%29.%0A%0ALet%20me%20know%20if%20you%20still%20don%27t%20see%20the%20file%20in%20chat%20after%20these%20checks%21)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 13, 2026):

@dosu

api-1               | 
api-1               | [on_tool_execution]
api-1               | Tool: test_download_video
api-1               | Outputs: {"type":"text","message":{"text":"video downloaded\n\n"},"meta":null}
api-1               | 
api-1               | 
api-1               | [on_tool_execution]
api-1               | Tool: test_download_video
api-1               | Outputs: {"type":"text","message":{"text":"Video is bytes check: True\n\n"},"meta":null}
api-1               | 
api-1               | 
api-1               | [on_tool_execution]
api-1               | Tool: test_download_video
api-1               | Outputs: {"type":"text","message":{"text":"Generated video name: y0dfitgnqh5r.mp4\n\n"},"meta":null}
api-1               | 
api-1               | 
api-1               | [on_tool_execution]
api-1               | Tool: test_download_video
api-1               | Outputs: {"type":"blob","message":{"blob":"AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAGRl1dWlk2P7D1hsOSDySl1goh37EgQAAAABtYW5pZmVzdAAAAAAAAAAAAAAAGOxqdW1iAAAAHmp1bWRjMnBhABEAEIAAAKoAOJtxA2MycGEAAAAYxmp1bWIAAABHanVtZGMybWEAEQAQgAAAqgA4m3EDdXJuOmMycGE6YzU3ZDgyZGYtZDUzMS1mNDIwLWQxMTEtNDM3ZDJjZDZmNDEyAAAAE4RqdW1iAAAAKGp1bWRjMmNzABEAEIAAAKoAOJtxA2MycGEuc2lnbmF0dXJlAAAAE1RjYm9y0oRZBq6iASYYIYJZA8IwggO+MIIDRKADAgECAhMZ6Eg6Ykj7RbGFox9nK4RAB+19MAoGCCqGSM49BAMDMFExCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApHb29nbGUgTExDMS0wKwYDVQQDDCRHb29nbGUgQzJQQSBNZWRpYSBTZXJ2aWNlcyAxUCBJQ0EgRzMwHhcNMjUwODE1MTczNzUyWhcNMjYwODEwMTczNzUxWjBrMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIExMQzEcMBoGA1UECxMTR29vZ2xlIFN5c3RlbSA5ODY0OTEpMCcGA1UEAxMgR29vZ2xlIE1lZGlhIFByb2Nlc3NpbmcgU2VydmljZXMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARUyWnWfjJrFNw0g/TEayCZWFwyK5MLCiQEY1RONZQ3NNQ7XCnIsAluog+M83DCbSeIkm60QqtjrydJ0IPS04Obo4IB3zCCAdswDgYDVR0PAQH/BAQDAgbAMB8GA1UdJQQYMBYGCCsGAQUFBwMEBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBXknJ+Lq83sBh

As you can see from the log, the blob message is truncated so dify is not able to show it.

Dify clearly does not support such blob operations directly, as confirmed by your references. They are not using create_blob_message to return a file, they just returned a raw file link with signature which they got from Gemini or somewhere else.

However, videos generated via Gemini cannot be downloaded unless the API key is directly appended to the URL parameters — a workaround that is unfeasible, since exposing my API key to end users poses severe security risks.

To date, Gemini has yet to resolve this issue, which means it is impossible to return either a downloadable link or a byte blob as a response.

Based on my observations, when a plugin runs within the Dify framework, regardless of the filename specified by the plugin during storage, the file will invariably be saved to the path /app/storage/tools/{tenant_id}/{uuid}.{ext} with a new UID. This behavior applies even to files that are identical in both content and original filename.

Moreover, there is no mechanism to map the UID of the saved file to the filename I originally intended. In other words, if I wish to expose file links to users through alternative approaches, I would need to implement a method to locate the corresponding files — a capability that the plugin does not appear to support.

Furthermore, integrating additional storage methods into the plugin would violate the Single Responsibility Principle of software design.

In addition, given the limitations of local disk capacity, uploading files to OSS would be a far more elegant and scalable solution.

So here I need some guide:

  1. Is there any decode or encode process before create_blob_message?
  2. How to configure dify to save the generated file to oss, not /app/storage/tools/xxx?
  3. How to grab the actual saved file url after calling the plugin?
  4. If it is really possible to show a downloadable file link, please show me the way step by step
@WilliamChen-luckbob commented on GitHub (Jan 13, 2026): @dosu ``` api-1 | api-1 | [on_tool_execution] api-1 | Tool: test_download_video api-1 | Outputs: {"type":"text","message":{"text":"video downloaded\n\n"},"meta":null} api-1 | api-1 | api-1 | [on_tool_execution] api-1 | Tool: test_download_video api-1 | Outputs: {"type":"text","message":{"text":"Video is bytes check: True\n\n"},"meta":null} api-1 | api-1 | api-1 | [on_tool_execution] api-1 | Tool: test_download_video api-1 | Outputs: {"type":"text","message":{"text":"Generated video name: y0dfitgnqh5r.mp4\n\n"},"meta":null} api-1 | api-1 | api-1 | [on_tool_execution] api-1 | Tool: test_download_video api-1 | Outputs: {"type":"blob","message":{"blob":"AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAGRl1dWlk2P7D1hsOSDySl1goh37EgQAAAABtYW5pZmVzdAAAAAAAAAAAAAAAGOxqdW1iAAAAHmp1bWRjMnBhABEAEIAAAKoAOJtxA2MycGEAAAAYxmp1bWIAAABHanVtZGMybWEAEQAQgAAAqgA4m3EDdXJuOmMycGE6YzU3ZDgyZGYtZDUzMS1mNDIwLWQxMTEtNDM3ZDJjZDZmNDEyAAAAE4RqdW1iAAAAKGp1bWRjMmNzABEAEIAAAKoAOJtxA2MycGEuc2lnbmF0dXJlAAAAE1RjYm9y0oRZBq6iASYYIYJZA8IwggO+MIIDRKADAgECAhMZ6Eg6Ykj7RbGFox9nK4RAB+19MAoGCCqGSM49BAMDMFExCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApHb29nbGUgTExDMS0wKwYDVQQDDCRHb29nbGUgQzJQQSBNZWRpYSBTZXJ2aWNlcyAxUCBJQ0EgRzMwHhcNMjUwODE1MTczNzUyWhcNMjYwODEwMTczNzUxWjBrMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIExMQzEcMBoGA1UECxMTR29vZ2xlIFN5c3RlbSA5ODY0OTEpMCcGA1UEAxMgR29vZ2xlIE1lZGlhIFByb2Nlc3NpbmcgU2VydmljZXMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARUyWnWfjJrFNw0g/TEayCZWFwyK5MLCiQEY1RONZQ3NNQ7XCnIsAluog+M83DCbSeIkm60QqtjrydJ0IPS04Obo4IB3zCCAdswDgYDVR0PAQH/BAQDAgbAMB8GA1UdJQQYMBYGCCsGAQUFBwMEBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBXknJ+Lq83sBh ``` As you can see from the log, the blob message is truncated so dify is not able to show it. Dify clearly does not support such blob operations directly, as confirmed by your references. They are not using create_blob_message to return a file, they just returned a raw file link with signature which they got from Gemini or somewhere else. However, videos generated via Gemini cannot be downloaded unless the API key is directly appended to the URL parameters — a workaround that is unfeasible, since exposing my API key to end users poses severe security risks. To date, Gemini has yet to resolve this issue, which means it is impossible to return either a downloadable link or a byte blob as a response. Based on my observations, when a plugin runs within the Dify framework, regardless of the filename specified by the plugin during storage, the file will invariably be saved to the path /app/storage/tools/{tenant_id}/{uuid}.{ext} with a new UID. This behavior applies even to files that are identical in both content and original filename. Moreover, there is no mechanism to map the UID of the saved file to the filename I originally intended. In other words, if I wish to expose file links to users through alternative approaches, I would need to implement a method to locate the corresponding files — a capability that the plugin does not appear to support. Furthermore, integrating additional storage methods into the plugin would violate the Single Responsibility Principle of software design. In addition, given the limitations of local disk capacity, uploading files to OSS would be a far more elegant and scalable solution. So here I need some guide: 1. Is there any decode or encode process before create_blob_message? 2. How to configure dify to save the generated file to oss, not `/app/storage/tools/xxx`? 3. How to grab the actual saved file url after calling the plugin? 4. If it is really possible to show a downloadable file link, please show me the way step by step
Author
Owner

@dosubot[bot] commented on GitHub (Jan 13, 2026):

Here’s how Dify handles plugin file storage and downloadable links:

  1. Encode/decode process: When you yield a create_blob_message, Dify base64-encodes the bytes for transmission and decodes them on the backend. However, just yielding a blob isn’t enough for chat file attachments—the backend expects to persist the file and register it as a tool file, then emit a message with a signed URL for download or preview. The meta should include both "mime_type" and "filename" (not "file_name"), and the blob must be raw bytes, not a file object or tuple reference.

  2. Configuring OSS for file storage: To save plugin-generated files to OSS (like Aliyun OSS, S3, etc.) instead of local disk, set the STORAGE_TYPE environment variable in your .env (or Docker Compose) to the desired backend (e.g., ALIYUN_OSS, S3, etc.), and provide the required credentials and bucket info (e.g., ALIYUN_OSS_BUCKET_NAME, ALIYUN_OSS_ACCESS_KEY, etc.). Dify’s storage abstraction will then save all files—including plugin outputs—to OSS transparently reference.

  3. Getting the saved file URL: After the plugin yields a blob message, Dify saves the file and generates a signed temporary URL for secure access. This URL is constructed using the configured FILES_URL (for external access) or INTERNAL_FILES_URL (for internal/plugin access), and includes a signature, timestamp, and nonce for security. The signed URL looks like:

    {FILES_URL}/files/tools/{tool_file_id}.{ext}?timestamp=...&nonce=...&sign=...
    

    You can retrieve this URL from the message files attached to the chat message or by referencing the file in your plugin/tool output reference.

  4. How to show a downloadable file link in chat:

    • In your plugin, after generating the video bytes, use Dify’s ToolFileManager (or let the plugin framework handle it) to save the file.
    • Yield a blob message with the video bytes and meta including "mime_type": "video/mp4" and "filename": "your_video.mp4".
    • Dify will persist the file, register it, and emit a message with a signed URL.
    • The chat UI will display the file as a downloadable attachment (not inline video), and users can click to download or preview it.
    • Example:
      yield self.create_blob_message(
          blob=video_bytes,
          meta={
              "mime_type": "video/mp4",
              "filename": "your_video.mp4"
          }
      )
      
    • Do not manually save the file to /app/storage/tools/xxx; let Dify handle it.

Caveats:

  • The filename you specify is stored as metadata, but the actual file is saved with a UUID-based name for uniqueness.
  • The signed URL is temporary and secure; you don’t need to expose any API keys.
  • Inline video preview in chat is not supported; files appear as attachments for download/preview only reference.

If you want to customize file storage further or need to surface the signed URL directly in your plugin output, you can use ToolFileManager’s sign_file method to generate the URL after saving the file.

Let me know if you want a concrete code snippet for your plugin or have questions about OSS config!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 13, 2026): <!-- Answer --> Here’s how Dify handles plugin file storage and downloadable links: 1. **Encode/decode process:** When you yield a `create_blob_message`, Dify base64-encodes the bytes for transmission and decodes them on the backend. However, just yielding a blob isn’t enough for chat file attachments—the backend expects to persist the file and register it as a tool file, then emit a message with a signed URL for download or preview. The meta should include both `"mime_type"` and `"filename"` (not `"file_name"`), and the blob must be raw bytes, not a file object or tuple [reference](https://github.com/langgenius/dify/blob/d095bd413b1cb7075d7609e4c2b47d1f34619596/api/core/tools/entities/tool_entities.py#L136-L247). 2. **Configuring OSS for file storage:** To save plugin-generated files to OSS (like Aliyun OSS, S3, etc.) instead of local disk, set the `STORAGE_TYPE` environment variable in your `.env` (or Docker Compose) to the desired backend (e.g., `ALIYUN_OSS`, `S3`, etc.), and provide the required credentials and bucket info (e.g., `ALIYUN_OSS_BUCKET_NAME`, `ALIYUN_OSS_ACCESS_KEY`, etc.). Dify’s storage abstraction will then save all files—including plugin outputs—to OSS transparently [reference](https://github.com/langgenius/dify/blob/d095bd413b1cb7075d7609e4c2b47d1f34619596/docker/.env.example#L44-L524). 3. **Getting the saved file URL:** After the plugin yields a blob message, Dify saves the file and generates a signed temporary URL for secure access. This URL is constructed using the configured `FILES_URL` (for external access) or `INTERNAL_FILES_URL` (for internal/plugin access), and includes a signature, timestamp, and nonce for security. The signed URL looks like: ``` {FILES_URL}/files/tools/{tool_file_id}.{ext}?timestamp=...&nonce=...&sign=... ``` You can retrieve this URL from the message files attached to the chat message or by referencing the file in your plugin/tool output [reference](https://github.com/langgenius/dify/blob/d095bd413b1cb7075d7609e4c2b47d1f34619596/api/core/tools/tool_file_manager.py#L36-L218). 4. **How to show a downloadable file link in chat:** - In your plugin, after generating the video bytes, use Dify’s ToolFileManager (or let the plugin framework handle it) to save the file. - Yield a blob message with the video bytes and meta including `"mime_type": "video/mp4"` and `"filename": "your_video.mp4"`. - Dify will persist the file, register it, and emit a message with a signed URL. - The chat UI will display the file as a downloadable attachment (not inline video), and users can click to download or preview it. - Example: ```python yield self.create_blob_message( blob=video_bytes, meta={ "mime_type": "video/mp4", "filename": "your_video.mp4" } ) ``` - Do not manually save the file to `/app/storage/tools/xxx`; let Dify handle it. **Caveats:** - The filename you specify is stored as metadata, but the actual file is saved with a UUID-based name for uniqueness. - The signed URL is temporary and secure; you don’t need to expose any API keys. - Inline video preview in chat is not supported; files appear as attachments for download/preview only [reference](https://github.com/langgenius/dify/blob/d095bd413b1cb7075d7609e4c2b47d1f34619596/api/core/tools/tool_file_manager.py#L36-L218). If you want to customize file storage further or need to surface the signed URL directly in your plugin output, you can use ToolFileManager’s `sign_file` method to generate the URL after saving the file. Let me know if you want a concrete code snippet for your plugin or have questions about OSS config! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/08b22964-9bb2-4056-87dc-2059b6b6b24e?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/08b22964-9bb2-4056-87dc-2059b6b6b24e?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/08b22964-9bb2-4056-87dc-2059b6b6b24e?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/08b22964-9bb2-4056-87dc-2059b6b6b24e?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/08b22964-9bb2-4056-87dc-2059b6b6b24e?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/08b22964-9bb2-4056-87dc-2059b6b6b24e?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/08b22964-9bb2-4056-87dc-2059b6b6b24e?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Here%E2%80%99s%20how%20Dify%20handles%20plugin%20file%20storage%20and%20downloadable%20links%3A%0A%0A1.%20%2A%2AEncode/decode%20process%3A%2A%2A%20When%20you%20yield%20a%20%60create_blob_message%60%2C%20Dify%20base64-encodes%20the%20bytes%20for%20transmission%20and%20decodes%20them%20on%20the%20backend.%20However%2C%20just%20yielding%20a%20blob%20isn%E2%80%99t%20enough%20for%20chat%20file%20attachments%E2%80%94the%20backend%20expects%20to%20persist%20the%20file%20and%20register%20it%20as%20a%20tool%20file%2C%20then%20emit%20a%20message%20with%20a%20signed%20URL%20for%20download%20or%20preview.%20The%20meta%20should%20include%20both%20%60%22mime_type%22%60%20and%20%60%22filename%22%60%20%28not%20%60%22file_name%22%60%29%2C%20and%20the%20blob%20must%20be%20raw%20bytes%2C%20not%20a%20file%20object%20or%20tuple%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/d095bd413b1cb7075d7609e4c2b47d1f34619596/api/core/tools/entities/tool_entities.py%23L136-L247%29.%0A%0A2.%20%2A%2AConfiguring%20OSS%20for%20file%20storage%3A%2A%2A%20To%20save%20plugin-generated%20files%20to%20OSS%20%28like%20Aliyun%20OSS%2C%20S3%2C%20etc.%29%20instead%20of%20local%20disk%2C%20set%20the%20%60STORAGE_TYPE%60%20environment%20variable%20in%20your%20%60.env%60%20%28or%20Docker%20Compose%29%20to%20the%20desired%20backend%20%28e.g.%2C%20%60ALIYUN_OSS%60%2C%20%60S3%60%2C%20etc.%29%2C%20and%20provide%20the%20required%20credentials%20and%20bucket%20info%20%28e.g.%2C%20%60ALIYUN_OSS_BUCKET_NAME%60%2C%20%60ALIYUN_OSS_ACCESS_KEY%60%2C%20etc.%29.%20Dify%E2%80%99s%20storage%20abstraction%20will%20then%20save%20all%20files%E2%80%94including%20plugin%20outputs%E2%80%94to%20OSS%20transparently%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/d095bd413b1cb7075d7609e4c2b47d1f34619596/docker/.env.example%23L44-L524%29.%0A%0A3.%20%2A%2AGetting%20the%20saved%20file%20URL%3A%2A%2A%20After%20the%20plugin%20yields%20a%20blob%20message%2C%20Dify%20saves%20the%20file%20and%20generates%20a%20signed%20temporary%20URL%20for%20secure%20access.%20This%20URL%20is%20constructed%20using%20the%20configured%20%60FILES_URL%60%20%28for%20external%20access%29%20or%20%60INTERNAL_FILES_URL%60%20%28for%20internal/plugin%20access%29%2C%20and%20includes%20a%20signature%2C%20timestamp%2C%20and%20nonce%20for%20security.%20The%20signed%20URL%20looks%20like%3A%20%20%0A%20%20%20%60%60%60%0A%20%20%20%7BFILES_URL%7D/files/tools/%7Btool_file_id%7D.%7Bext%7D%3Ftimestamp%3D...%26nonce%3D...%26sign%3D...%0A%20%20%20%60%60%60%0A%20%20%20You%20can%20retrieve%20this%20URL%20from%20the%20message%20files%20attached%20to%20the%20chat%20message%20or%20by%20referencing%20the%20file%20in%20your%20plugin/tool%20output%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/d095bd413b1cb7075d7609e4c2b47d1f34619596/api/core/tools/tool_file_manager.py%23L36-L218%29.%0A%0A4.%20%2A%2AHow%20to%20show%20a%20downloadable%20file%20link%20in%20chat%3A%2A%2A%20%20%0A%20%20%20-%20In%20your%20plugin%2C%20after%20generating%20the%20video%20bytes%2C%20use%20Dify%E2%80%99s%20ToolFileManager%20%28or%20let%20the%20plugin%20framework%20handle%20it%29%20to%20save%20the%20file.%20%20%0A%20%20%20-%20Yield%20a%20blob%20message%20with%20the%20video%20bytes%20and%20meta%20including%20%60%22mime_type%22%3A%20%22video/mp4%22%60%20and%20%60%22filename%22%3A%20%22your_video.mp4%22%60.%20%20%0A%20%20%20-%20Dify%20will%20persist%20the%20file%2C%20register%20it%2C%20and%20emit%20a%20message%20with%20a%20signed%20URL.%20%20%0A%20%20%20-%20The%20chat%20UI%20will%20display%20the%20file%20as%20a%20downloadable%20attachment%20%28not%20inline%20video%29%2C%20and%20users%20can%20click%20to%20download%20or%20preview%20it.%20%20%0A%20%20%20-%20Example%3A%0A%20%20%20%20%20%60%60%60python%0A%20%20%20%20%20yield%20self.create_blob_message%28%0A%20%20%20%20%20%20%20%20%20blob%3Dvideo_bytes%2C%0A%20%20%20%20%20%20%20%20%20meta%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%22mime_type%22%3A%20%22video/mp4%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%22filename%22%3A%20%22your_video.mp4%22%0A%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%29%0A%20%20%20%20%20%60%60%60%0A%20%20%20-%20Do%20not%20manually%20save%20the%20file%20to%20%60/app/storage/tools/xxx%60%3B%20let%20Dify%20handle%20it.%0A%0A%2A%2ACaveats%3A%2A%2A%20%20%0A-%20The%20filename%20you%20specify%20is%20stored%20as%20metadata%2C%20but%20the%20actual%20file%20is%20saved%20with%20a%20UUID-based%20name%20for%20uniqueness.%20%20%0A-%20The%20signed%20URL%20is%20temporary%20and%20secure%3B%20you%20don%E2%80%99t%20need%20to%20expose%20any%20API%20keys.%20%20%0A-%20Inline%20video%20preview%20in%20chat%20is%20not%20supported%3B%20files%20appear%20as%20attachments%20for%20download/preview%20only%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/d095bd413b1cb7075d7609e4c2b47d1f34619596/api/core/tools/tool_file_manager.py%23L36-L218%29.%0A%0AIf%20you%20want%20to%20customize%20file%20storage%20further%20or%20need%20to%20surface%20the%20signed%20URL%20directly%20in%20your%20plugin%20output%2C%20you%20can%20use%20ToolFileManager%E2%80%99s%20%60sign_file%60%20method%20to%20generate%20the%20URL%20after%20saving%20the%20file.%0A%0ALet%20me%20know%20if%20you%20want%20a%20concrete%20code%20snippet%20for%20your%20plugin%20or%20have%20questions%20about%20OSS%20config%21)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 14, 2026):

@dosu
after uploaded an image and try to run video generation, there is an error:

Run failed: An error occurred in the william_chen/gemini_video/gemini_video, please contact the author of william_chen/gemini_video/gemini_video for help, error type: ValueError, error details: Invalid file URL '/files/175381c4-c51b-4087-8fd7-f92bb884a35f/file-preview?timestamp=1768373744&nonce=beab8cb976f46cde5124cda1e97ac68c&sign=NOHp8BQpWV3Nm7aB8pwHjs_i1wj5WPbHKeD5cDWIJEg%3D': Request URL is missing an 'http://' or 'https://' protocol.. Ensure the FILES_URL environment variable is set in your .env file

Here is the setting:

FILES_URL=http://api:5001
INTERNAL_FILES_URL=http://api:5001

Then it is OK to generate the video, but unable to download it from dify
because FILES_URL is not reachable by explorer

I don't like to expose another 5001 port , Dify is running in docker compose, so I think using nginx in the container would be better.
I have checked the nginx volume, it has a config as below:

# Please do not directly edit this file. Instead, modify the .env variables related to NGINX configuration.

server {
    listen 80;
    server_name _;

    location /console/api {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    location /api {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    location /v1 {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    location /files {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    location /explore {
      proxy_pass http://web:3000;
      include proxy.conf;
    }

    location /e/ {
      proxy_pass http://plugin_daemon:5002;
      proxy_set_header Dify-Hook-Url $scheme://$host$request_uri;
      include proxy.conf;
    }

    location / {
      proxy_pass http://web:3000;
      include proxy.conf;
    }

    location /mcp {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    location /triggers {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    # placeholder for acme challenge location


    # placeholder for https config defined in https.conf.template

}

So I have set FILES_URL = https://[my_server_ip]/[exposed_port]/files and trying to run the plugin but there is always a time out error when trying to run plugin,

data: {"event":"error","conversation_id":"158381a4-fc0a-431d-9ac1-3ee2f97d3f5e","message_id":"bb272041-d10f-4169-bb0a-c7a683dbce0a","created_at":1768381699,"code":"invalid_param","status":400,"message":"Run failed: An error occurred in the william_chen/gemini_video/gemini_video, please contact the author of william_chen/gemini_video/gemini_video for help, error type: ConnectTimeout, error details: timed out"}

It seems that the FILES_URL is unreachable, something in docker is trying to visit the host server with public url, and get timed out
How to fix this?

@WilliamChen-luckbob commented on GitHub (Jan 14, 2026): @dosu after uploaded an image and try to run video generation, there is an error: ``` Run failed: An error occurred in the william_chen/gemini_video/gemini_video, please contact the author of william_chen/gemini_video/gemini_video for help, error type: ValueError, error details: Invalid file URL '/files/175381c4-c51b-4087-8fd7-f92bb884a35f/file-preview?timestamp=1768373744&nonce=beab8cb976f46cde5124cda1e97ac68c&sign=NOHp8BQpWV3Nm7aB8pwHjs_i1wj5WPbHKeD5cDWIJEg%3D': Request URL is missing an 'http://' or 'https://' protocol.. Ensure the FILES_URL environment variable is set in your .env file ``` Here is the setting: ``` FILES_URL=http://api:5001 INTERNAL_FILES_URL=http://api:5001 ``` Then it is OK to generate the video, but unable to download it from dify because FILES_URL is not reachable by explorer I don't like to expose another 5001 port , Dify is running in docker compose, so I think using nginx in the container would be better. I have checked the nginx volume, it has a config as below: ``` # Please do not directly edit this file. Instead, modify the .env variables related to NGINX configuration. server { listen 80; server_name _; location /console/api { proxy_pass http://api:5001; include proxy.conf; } location /api { proxy_pass http://api:5001; include proxy.conf; } location /v1 { proxy_pass http://api:5001; include proxy.conf; } location /files { proxy_pass http://api:5001; include proxy.conf; } location /explore { proxy_pass http://web:3000; include proxy.conf; } location /e/ { proxy_pass http://plugin_daemon:5002; proxy_set_header Dify-Hook-Url $scheme://$host$request_uri; include proxy.conf; } location / { proxy_pass http://web:3000; include proxy.conf; } location /mcp { proxy_pass http://api:5001; include proxy.conf; } location /triggers { proxy_pass http://api:5001; include proxy.conf; } # placeholder for acme challenge location # placeholder for https config defined in https.conf.template } ``` So I have set `FILES_URL = https://[my_server_ip]/[exposed_port]/files` and trying to run the plugin but there is always a time out error when trying to run plugin, ``` data: {"event":"error","conversation_id":"158381a4-fc0a-431d-9ac1-3ee2f97d3f5e","message_id":"bb272041-d10f-4169-bb0a-c7a683dbce0a","created_at":1768381699,"code":"invalid_param","status":400,"message":"Run failed: An error occurred in the william_chen/gemini_video/gemini_video, please contact the author of william_chen/gemini_video/gemini_video for help, error type: ConnectTimeout, error details: timed out"} ``` It seems that the FILES_URL is unreachable, something in docker is trying to visit the host server with public url, and get timed out How to fix this?
Author
Owner

@dosubot[bot] commented on GitHub (Jan 14, 2026):

This happens because when you set FILES_URL to a public/external address (like https://[your_server_ip]/files), Docker containers (including your plugin) usually can't reach that address due to Docker's network isolation—so you get timeouts. When you set FILES_URL to an internal Docker address (like http://api:5001), it works for plugins but is not reachable from your browser, so users can't download files.

The solution is to use both FILES_URL and INTERNAL_FILES_URL:

  1. Set FILES_URL in your .env to your public-facing URL (the one your browser uses), e.g.:

    FILES_URL=https://[your-domain-or-ip]/files
    

    This is what Dify will use for download links shown to users.

  2. Set INTERNAL_FILES_URL to the Docker network alias for Nginx (or the API service), e.g.:

    INTERNAL_FILES_URL=http://nginx/files
    

    or, if your Nginx is listening on port 80 and is named nginx in your Docker Compose:

    INTERNAL_FILES_URL=http://nginx
    

    This is what Dify will use for plugins and internal services.

  3. Make sure your nginx service is on the same Docker network as api and plugin_daemon, and that your Nginx config proxies /files to http://api:5001 (which you already have).

  4. Set RESPECT_XFORWARD_HEADERS_ENABLED=true in your .env so Dify generates correct URLs behind the reverse proxy.

  5. Restart all containers after changing these settings.

  6. To verify:

    • From inside a container (e.g., docker compose exec api bash), run curl http://nginx/files/... to check internal access.
    • From your browser, access https://[your-domain-or-ip]/files/... to check external access.

This setup lets plugins access files via the internal Docker network (no timeouts), and users download files via the public URL (no need to expose port 5001) (reference, reference, reference).

If nginx is not resolvable from your containers, you can use the service name defined in your Docker Compose, or try http://host.docker.internal if you're on Docker Desktop.

After this, Dify will generate the correct URLs for both plugins and users, and you should not see timeouts or protocol errors.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 14, 2026): <!-- Answer --> This happens because when you set `FILES_URL` to a public/external address (like `https://[your_server_ip]/files`), Docker containers (including your plugin) usually can't reach that address due to Docker's network isolation—so you get timeouts. When you set `FILES_URL` to an internal Docker address (like `http://api:5001`), it works for plugins but is not reachable from your browser, so users can't download files. The solution is to use both `FILES_URL` and `INTERNAL_FILES_URL`: 1. Set `FILES_URL` in your `.env` to your public-facing URL (the one your browser uses), e.g.: ``` FILES_URL=https://[your-domain-or-ip]/files ``` This is what Dify will use for download links shown to users. 2. Set `INTERNAL_FILES_URL` to the Docker network alias for Nginx (or the API service), e.g.: ``` INTERNAL_FILES_URL=http://nginx/files ``` or, if your Nginx is listening on port 80 and is named `nginx` in your Docker Compose: ``` INTERNAL_FILES_URL=http://nginx ``` This is what Dify will use for plugins and internal services. 3. Make sure your `nginx` service is on the same Docker network as `api` and `plugin_daemon`, and that your Nginx config proxies `/files` to `http://api:5001` (which you already have). 4. Set `RESPECT_XFORWARD_HEADERS_ENABLED=true` in your `.env` so Dify generates correct URLs behind the reverse proxy. 5. Restart all containers after changing these settings. 6. To verify: - From inside a container (e.g., `docker compose exec api bash`), run `curl http://nginx/files/...` to check internal access. - From your browser, access `https://[your-domain-or-ip]/files/...` to check external access. This setup lets plugins access files via the internal Docker network (no timeouts), and users download files via the public URL (no need to expose port 5001) ([reference](https://github.com/langgenius/dify/pull/21382), [reference](https://github.com/langgenius/dify/issues/30780), [reference](https://github.com/langgenius/dify/issues/21937)). If `nginx` is not resolvable from your containers, you can use the service name defined in your Docker Compose, or try `http://host.docker.internal` if you're on Docker Desktop. After this, Dify will generate the correct URLs for both plugins and users, and you should not see timeouts or protocol errors. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/85e0c8d5-f0a0-41fe-9377-521db7e6c1a3?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/85e0c8d5-f0a0-41fe-9377-521db7e6c1a3?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/85e0c8d5-f0a0-41fe-9377-521db7e6c1a3?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/85e0c8d5-f0a0-41fe-9377-521db7e6c1a3?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/85e0c8d5-f0a0-41fe-9377-521db7e6c1a3?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/85e0c8d5-f0a0-41fe-9377-521db7e6c1a3?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/85e0c8d5-f0a0-41fe-9377-521db7e6c1a3?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20happens%20because%20when%20you%20set%20%60FILES_URL%60%20to%20a%20public/external%20address%20%28like%20%60https%3A//%5Byour_server_ip%5D/files%60%29%2C%20Docker%20containers%20%28including%20your%20plugin%29%20usually%20can%27t%20reach%20that%20address%20due%20to%20Docker%27s%20network%20isolation%E2%80%94so%20you%20get%20timeouts.%20When%20you%20set%20%60FILES_URL%60%20to%20an%20internal%20Docker%20address%20%28like%20%60http%3A//api%3A5001%60%29%2C%20it%20works%20for%20plugins%20but%20is%20not%20reachable%20from%20your%20browser%2C%20so%20users%20can%27t%20download%20files.%0A%0AThe%20solution%20is%20to%20use%20both%20%60FILES_URL%60%20and%20%60INTERNAL_FILES_URL%60%3A%0A%0A1.%20Set%20%60FILES_URL%60%20in%20your%20%60.env%60%20to%20your%20public-facing%20URL%20%28the%20one%20your%20browser%20uses%29%2C%20e.g.%3A%0A%20%20%20%60%60%60%0A%20%20%20FILES_URL%3Dhttps%3A//%5Byour-domain-or-ip%5D/files%0A%20%20%20%60%60%60%0A%20%20%20This%20is%20what%20Dify%20will%20use%20for%20download%20links%20shown%20to%20users.%0A%0A2.%20Set%20%60INTERNAL_FILES_URL%60%20to%20the%20Docker%20network%20alias%20for%20Nginx%20%28or%20the%20API%20service%29%2C%20e.g.%3A%0A%20%20%20%60%60%60%0A%20%20%20INTERNAL_FILES_URL%3Dhttp%3A//nginx/files%0A%20%20%20%60%60%60%0A%20%20%20or%2C%20if%20your%20Nginx%20is%20listening%20on%20port%2080%20and%20is%20named%20%60nginx%60%20in%20your%20Docker%20Compose%3A%0A%20%20%20%60%60%60%0A%20%20%20INTERNAL_FILES_URL%3Dhttp%3A//nginx%0A%20%20%20%60%60%60%0A%20%20%20This%20is%20what%20Dify%20will%20use%20for%20plugins%20and%20internal%20services.%0A%0A3.%20Make%20sure%20your%20%60nginx%60%20service%20is%20on%20the%20same%20Docker%20network%20as%20%60api%60%20and%20%60plugin_daemon%60%2C%20and%20that%20your%20Nginx%20config%20proxies%20%60/files%60%20to%20%60http%3A//api%3A5001%60%20%28which%20you%20already%20have%29.%0A%0A4.%20Set%20%60RESPECT_XFORWARD_HEADERS_ENABLED%3Dtrue%60%20in%20your%20%60.env%60%20so%20Dify%20generates%20correct%20URLs%20behind%20the%20reverse%20proxy.%0A%0A5.%20Restart%20all%20containers%20after%20changing%20these%20settings.%0A%0A6.%20To%20verify%3A%20%20%0A%20%20%20-%20From%20inside%20a%20container%20%28e.g.%2C%20%60docker%20compose%20exec%20api%20bash%60%29%2C%20run%20%60curl%20http%3A//nginx/files/...%60%20to%20check%20internal%20access.%20%20%0A%20%20%20-%20From%20your%20browser%2C%20access%20%60https%3A//%5Byour-domain-or-ip%5D/files/...%60%20to%20check%20external%20access.%0A%0AThis%20setup%20lets%20plugins%20access%20files%20via%20the%20internal%20Docker%20network%20%28no%20timeouts%29%2C%20and%20users%20download%20files%20via%20the%20public%20URL%20%28no%20need%20to%20expose%20port%205001%29%C2%A0%28%5Breference%5D%28https%3A//github.com/langgenius/dify/pull/21382%29%2C%20%5Breference%5D%28https%3A//github.com/langgenius/dify/issues/30780%29%2C%20%5Breference%5D%28https%3A//github.com/langgenius/dify/issues/21937%29%29.%0A%0AIf%20%60nginx%60%20is%20not%20resolvable%20from%20your%20containers%2C%20you%20can%20use%20the%20service%20name%20defined%20in%20your%20Docker%20Compose%2C%20or%20try%20%60http%3A//host.docker.internal%60%20if%20you%27re%20on%20Docker%20Desktop.%0A%0AAfter%20this%2C%20Dify%20will%20generate%20the%20correct%20URLs%20for%20both%20plugins%20and%20users%2C%20and%20you%20should%20not%20see%20timeouts%20or%20protocol%20errors.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 14, 2026):

@dosu
Currently, the host machine only exposes port 12345, and this port is whitelisted exclusively for specified IP addresses. The port 80 of Nginx in the Docker environment is mapped to port 12345 of the host machine.
Now, when I try to configure it as you suggested, it simply fails to work and throws a 404 not found error.
The only configuration that enables successful video file saving is setting both attributes to http://api:5001.

@WilliamChen-luckbob commented on GitHub (Jan 14, 2026): @dosu Currently, the host machine only exposes port 12345, and this port is whitelisted exclusively for specified IP addresses. The port 80 of Nginx in the Docker environment is mapped to port 12345 of the host machine. Now, when I try to configure it as you suggested, it simply fails to work and throws a 404 not found error. The only configuration that enables successful video file saving is setting both attributes to http://api:5001.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 14, 2026):

This 404 error happens because, in Docker, containers can’t reach the host’s public IP/port (your Nginx on port 12345) by default. When you set both FILES_URL and INTERNAL_FILES_URL to the public URL (e.g., http://your-host:12345/files), plugins inside Docker try to access files via that public address, but Docker’s network isolation blocks this, resulting in 404s or timeouts (source).

The only reason setting both to http://api:5001 works is because all traffic stays inside the Docker network, but then files aren’t accessible from outside.

To fix this and make both plugin and browser access work through your single exposed port:

  • Set FILES_URL to your public-facing URL (e.g., http://your-host:12345/files) so users get valid download links.
  • Set INTERNAL_FILES_URL to http://nginx/files (or just http://nginx if you don’t want the /files path), so plugins inside Docker access files via the Nginx service on the Docker network.
  • Make sure your nginx service is on the same Docker networks as api and plugin_daemon, and that your Nginx config proxies /files to http://api:5001.
  • Restart all containers after changing these settings.

From inside a container, run curl http://nginx/files/... (replace with a real file path) to confirm internal access works. From your browser, use http://your-host:12345/files/... to confirm external access.

If you still get 404s, check the Nginx logs and confirm the /files location is correctly proxying to the api service. Avoid using localhost or your public IP for INTERNAL_FILES_URL—always use the Docker service name (nginx) for internal access (source).

This setup allows plugins and internal services to access files via Docker networking, while users download files through your single exposed port.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 14, 2026): <!-- Answer --> This 404 error happens because, in Docker, containers can’t reach the host’s public IP/port (your Nginx on port 12345) by default. When you set both FILES_URL and INTERNAL_FILES_URL to the public URL (e.g., `http://your-host:12345/files`), plugins inside Docker try to access files via that public address, but Docker’s network isolation blocks this, resulting in 404s or timeouts ([source](https://github.com/langgenius/dify/issues/28888#issuecomment-3588470630)). The only reason setting both to `http://api:5001` works is because all traffic stays inside the Docker network, but then files aren’t accessible from outside. **To fix this and make both plugin and browser access work through your single exposed port:** - Set `FILES_URL` to your public-facing URL (e.g., `http://your-host:12345/files`) so users get valid download links. - Set `INTERNAL_FILES_URL` to `http://nginx/files` (or just `http://nginx` if you don’t want the `/files` path), so plugins inside Docker access files via the Nginx service on the Docker network. - Make sure your `nginx` service is on the same Docker networks as `api` and `plugin_daemon`, and that your Nginx config proxies `/files` to `http://api:5001`. - Restart all containers after changing these settings. From inside a container, run `curl http://nginx/files/...` (replace with a real file path) to confirm internal access works. From your browser, use `http://your-host:12345/files/...` to confirm external access. If you still get 404s, check the Nginx logs and confirm the `/files` location is correctly proxying to the api service. Avoid using localhost or your public IP for INTERNAL_FILES_URL—always use the Docker service name (`nginx`) for internal access ([source](https://github.com/langgenius/dify/blob/f33b1a33324eb65d7c9176c283bb19677d5f1653/docker/.env.example#L44-L1290)). This setup allows plugins and internal services to access files via Docker networking, while users download files through your single exposed port. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/a35d8f0b-5cc5-422f-89b3-f6cf90a762d8?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/a35d8f0b-5cc5-422f-89b3-f6cf90a762d8?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/a35d8f0b-5cc5-422f-89b3-f6cf90a762d8?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/a35d8f0b-5cc5-422f-89b3-f6cf90a762d8?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/a35d8f0b-5cc5-422f-89b3-f6cf90a762d8?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/a35d8f0b-5cc5-422f-89b3-f6cf90a762d8?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/a35d8f0b-5cc5-422f-89b3-f6cf90a762d8?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20404%20error%20happens%20because%2C%20in%20Docker%2C%20containers%20can%E2%80%99t%20reach%20the%20host%E2%80%99s%20public%20IP/port%20%28your%20Nginx%20on%20port%2012345%29%20by%20default.%20When%20you%20set%20both%20FILES_URL%20and%20INTERNAL_FILES_URL%20to%20the%20public%20URL%20%28e.g.%2C%20%60http%3A//your-host%3A12345/files%60%29%2C%20plugins%20inside%20Docker%20try%20to%20access%20files%20via%20that%20public%20address%2C%20but%20Docker%E2%80%99s%20network%20isolation%20blocks%20this%2C%20resulting%20in%20404s%20or%20timeouts%C2%A0%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/issues/28888%23issuecomment-3588470630%29%29.%0A%0AThe%20only%20reason%20setting%20both%20to%20%60http%3A//api%3A5001%60%20works%20is%20because%20all%20traffic%20stays%20inside%20the%20Docker%20network%2C%20but%20then%20files%20aren%E2%80%99t%20accessible%20from%20outside.%0A%0A%2A%2ATo%20fix%20this%20and%20make%20both%20plugin%20and%20browser%20access%20work%20through%20your%20single%20exposed%20port%3A%2A%2A%0A%0A-%20Set%20%60FILES_URL%60%20to%20your%20public-facing%20URL%20%28e.g.%2C%20%60http%3A//your-host%3A12345/files%60%29%20so%20users%20get%20valid%20download%20links.%0A-%20Set%20%60INTERNAL_FILES_URL%60%20to%20%60http%3A//nginx/files%60%20%28or%20just%20%60http%3A//nginx%60%20if%20you%20don%E2%80%99t%20want%20the%20%60/files%60%20path%29%2C%20so%20plugins%20inside%20Docker%20access%20files%20via%20the%20Nginx%20service%20on%20the%20Docker%20network.%0A-%20Make%20sure%20your%20%60nginx%60%20service%20is%20on%20the%20same%20Docker%20networks%20as%20%60api%60%20and%20%60plugin_daemon%60%2C%20and%20that%20your%20Nginx%20config%20proxies%20%60/files%60%20to%20%60http%3A//api%3A5001%60.%0A-%20Restart%20all%20containers%20after%20changing%20these%20settings.%0A%0AFrom%20inside%20a%20container%2C%20run%20%60curl%20http%3A//nginx/files/...%60%20%28replace%20with%20a%20real%20file%20path%29%20to%20confirm%20internal%20access%20works.%20From%20your%20browser%2C%20use%20%60http%3A//your-host%3A12345/files/...%60%20to%20confirm%20external%20access.%0A%0AIf%20you%20still%20get%20404s%2C%20check%20the%20Nginx%20logs%20and%20confirm%20the%20%60/files%60%20location%20is%20correctly%20proxying%20to%20the%20api%20service.%20Avoid%20using%20localhost%20or%20your%20public%20IP%20for%20INTERNAL_FILES_URL%E2%80%94always%20use%20the%20Docker%20service%20name%20%28%60nginx%60%29%20for%20internal%20access%C2%A0%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/blob/f33b1a33324eb65d7c9176c283bb19677d5f1653/docker/.env.example%23L44-L1290%29%29.%0A%0AThis%20setup%20allows%20plugins%20and%20internal%20services%20to%20access%20files%20via%20Docker%20networking%2C%20while%20users%20download%20files%20through%20your%20single%20exposed%20port.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 14, 2026):

@dosu
A new error, I have changed nothing but self.runtime.credentials will return a totally wrong api secret which I was never configured.
so the plugin continuing throwing 401 error when querying gemini api. The plugin was OK yesterday, but the secret configured for provider is wrong today.
Here is the code

from typing import Any

from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError


class GeminiVideoProvider(ToolProvider):

    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
        if credentials.get("gemini_api_key") is None:
            raise ToolProviderCredentialValidationError("ERROR")

identity:
  author: "william_chen"
  name: "gemini_video"
  label:
    en_US: "Gemini Video"
    zh_Hans: "Gemini 视频"
    ja_JP: "Gemini ビデオ"
  description:
    en_US: "gemini video generation support for Veo 3.1"
    zh_Hans: "gemini 视频支持"
    ja_JP: "gemini ビデオ"
  icon: "icon.svg"
  tags:
    - videos
    - productivity
credentials_for_provider:
  gemini_api_key:
    type: secret-input
    required: true
    label:
      en_US: Gemini API Key
      zh_Hans: Gemini API 密钥
      ja_JP: Gemini API キー
    help:
      en_US: Please input your Gemini API Key
      zh_Hans: 请输入你的 Gemini API 密钥
      ja_JP: Gemini API キーを入力してください
    placeholder:
      en_US: Please input your SerpApi API key
      zh_Hans: 请输入你的 SerpApi API key
tools:
  - tools/gemini_video.yaml
  - tools/test_download_video.yaml
extra:
  python:
    source: provider/gemini_video.py

I have tried times to delete the plugin and install again, remove the plugin node and old configure.
the secret is always wrong when trying to call _gemini_api_key = self.runtime.credentials["gemini_api_key"].

@WilliamChen-luckbob commented on GitHub (Jan 14, 2026): @dosu A new error, I have changed nothing but self.runtime.credentials will return a totally wrong api secret which I was never configured. so the plugin continuing throwing 401 error when querying gemini api. The plugin was OK yesterday, but the secret configured for provider is wrong today. Here is the code ``` Python from typing import Any from dify_plugin import ToolProvider from dify_plugin.errors.tool import ToolProviderCredentialValidationError class GeminiVideoProvider(ToolProvider): def _validate_credentials(self, credentials: dict[str, Any]) -> None: if credentials.get("gemini_api_key") is None: raise ToolProviderCredentialValidationError("ERROR") ``` ``` yaml identity: author: "william_chen" name: "gemini_video" label: en_US: "Gemini Video" zh_Hans: "Gemini 视频" ja_JP: "Gemini ビデオ" description: en_US: "gemini video generation support for Veo 3.1" zh_Hans: "gemini 视频支持" ja_JP: "gemini ビデオ" icon: "icon.svg" tags: - videos - productivity credentials_for_provider: gemini_api_key: type: secret-input required: true label: en_US: Gemini API Key zh_Hans: Gemini API 密钥 ja_JP: Gemini API キー help: en_US: Please input your Gemini API Key zh_Hans: 请输入你的 Gemini API 密钥 ja_JP: Gemini API キーを入力してください placeholder: en_US: Please input your SerpApi API key zh_Hans: 请输入你的 SerpApi API key tools: - tools/gemini_video.yaml - tools/test_download_video.yaml extra: python: source: provider/gemini_video.py ``` I have tried times to delete the plugin and install again, remove the plugin node and old configure. the secret is always wrong when trying to call ` _gemini_api_key = self.runtime.credentials["gemini_api_key"]`.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 14, 2026):

This is almost always caused by credential caching or a mismatch between the provider config in the UI and what’s actually stored in Dify’s backend. Even if you delete and reinstall the plugin, Dify may keep using a stale API key unless you explicitly update or re-save the Gemini API key in the provider settings UI. Here’s what to do:

  1. Go to the Dify UI, find your Gemini provider config, and re-enter (or paste) the correct API key—even if it looks correct—then save. This forces a cache refresh and updates the backend with the new key (source).
  2. Restart all Dify backend and plugin_daemon containers to clear any in-memory or Redis credential caches (source).
  3. Remove any old plugin nodes and provider configs from all workspaces to avoid stale credentials being injected.
  4. If the problem persists, check your Dify database tables (provider_credentials, provider_model_credentials, tool_builtin_providers) for multiple or outdated Gemini credentials—make sure only the correct one is present and marked as default (source).
  5. If you recently upgraded Dify, run the PluginDataMigration script to update provider_name fields and credential references, then restart all services (source).
  6. Double-check your API key for invisible characters or extra spaces, and make sure it matches the endpoint type (domestic/international) required by Gemini (source).

If you still get the wrong key after all this, try deleting and recreating the Gemini provider and credential in the UI, then reconfigure the plugin node and retest. This will force Dify to use the new credential and clear any hidden state or cache issues (source).

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 14, 2026): <!-- Answer --> This is almost always caused by credential caching or a mismatch between the provider config in the UI and what’s actually stored in Dify’s backend. Even if you delete and reinstall the plugin, Dify may keep using a stale API key unless you explicitly update or re-save the Gemini API key in the provider settings UI. Here’s what to do: 1. Go to the Dify UI, find your Gemini provider config, and re-enter (or paste) the correct API key—even if it looks correct—then save. This forces a cache refresh and updates the backend with the new key ([source](https://github.com/langgenius/dify-official-plugins/issues/2243)). 2. Restart all Dify backend and plugin_daemon containers to clear any in-memory or Redis credential caches ([source](https://github.com/langgenius/dify/issues/25264#issuecomment-3258177106)). 3. Remove any old plugin nodes and provider configs from all workspaces to avoid stale credentials being injected. 4. If the problem persists, check your Dify database tables (provider_credentials, provider_model_credentials, tool_builtin_providers) for multiple or outdated Gemini credentials—make sure only the correct one is present and marked as default ([source](https://github.com/langgenius/dify/blob/5008f5e89bcba4388754b81b4c0aae1b009ab64f/api/models/provider.py#L96-L371)). 5. If you recently upgraded Dify, run the PluginDataMigration script to update provider_name fields and credential references, then restart all services ([source](https://github.com/langgenius/dify-official-plugins/issues/2122)). 6. Double-check your API key for invisible characters or extra spaces, and make sure it matches the endpoint type (domestic/international) required by Gemini ([source](https://github.com/langgenius/dify/issues/30611)). If you still get the wrong key after all this, try deleting and recreating the Gemini provider and credential in the UI, then reconfigure the plugin node and retest. This will force Dify to use the new credential and clear any hidden state or cache issues ([source](https://github.com/langgenius/dify/issues/28458)). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/9b4c4f12-fb84-4345-a745-c0dbc9d4337d?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9b4c4f12-fb84-4345-a745-c0dbc9d4337d?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9b4c4f12-fb84-4345-a745-c0dbc9d4337d?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9b4c4f12-fb84-4345-a745-c0dbc9d4337d?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9b4c4f12-fb84-4345-a745-c0dbc9d4337d?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9b4c4f12-fb84-4345-a745-c0dbc9d4337d?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9b4c4f12-fb84-4345-a745-c0dbc9d4337d?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20almost%20always%20caused%20by%20credential%20caching%20or%20a%20mismatch%20between%20the%20provider%20config%20in%20the%20UI%20and%20what%E2%80%99s%20actually%20stored%20in%20Dify%E2%80%99s%20backend.%20Even%20if%20you%20delete%20and%20reinstall%20the%20plugin%2C%20Dify%20may%20keep%20using%20a%20stale%20API%20key%20unless%20you%20explicitly%20update%20or%20re-save%20the%20Gemini%20API%20key%20in%20the%20provider%20settings%20UI.%20Here%E2%80%99s%20what%20to%20do%3A%0A%0A1.%20Go%20to%20the%20Dify%20UI%2C%20find%20your%20Gemini%20provider%20config%2C%20and%20re-enter%20%28or%20paste%29%20the%20correct%20API%20key%E2%80%94even%20if%20it%20looks%20correct%E2%80%94then%20save.%20This%20forces%20a%20cache%20refresh%20and%20updates%20the%20backend%20with%20the%20new%20key%20%28%5Bsource%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/2243%29%29.%0A2.%20Restart%20all%20Dify%20backend%20and%20plugin_daemon%20containers%20to%20clear%20any%20in-memory%20or%20Redis%20credential%20caches%20%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/issues/25264%23issuecomment-3258177106%29%29.%0A3.%20Remove%20any%20old%20plugin%20nodes%20and%20provider%20configs%20from%20all%20workspaces%20to%20avoid%20stale%20credentials%20being%20injected.%0A4.%20If%20the%20problem%20persists%2C%20check%20your%20Dify%20database%20tables%20%28provider_credentials%2C%20provider_model_credentials%2C%20tool_builtin_providers%29%20for%20multiple%20or%20outdated%20Gemini%20credentials%E2%80%94make%20sure%20only%20the%20correct%20one%20is%20present%20and%20marked%20as%20default%20%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/blob/5008f5e89bcba4388754b81b4c0aae1b009ab64f/api/models/provider.py%23L96-L371%29%29.%0A5.%20If%20you%20recently%20upgraded%20Dify%2C%20run%20the%20PluginDataMigration%20script%20to%20update%20provider_name%20fields%20and%20credential%20references%2C%20then%20restart%20all%20services%20%28%5Bsource%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/2122%29%29.%0A6.%20Double-check%20your%20API%20key%20for%20invisible%20characters%20or%20extra%20spaces%2C%20and%20make%20sure%20it%20matches%20the%20endpoint%20type%20%28domestic/international%29%20required%20by%20Gemini%20%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/issues/30611%29%29.%0A%0AIf%20you%20still%20get%20the%20wrong%20key%20after%20all%20this%2C%20try%20deleting%20and%20recreating%20the%20Gemini%20provider%20and%20credential%20in%20the%20UI%2C%20then%20reconfigure%20the%20plugin%20node%20and%20retest.%20This%20will%20force%20Dify%20to%20use%20the%20new%20credential%20and%20clear%20any%20hidden%20state%20or%20cache%20issues%20%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/issues/28458%29%29.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 14, 2026):

@dosu
This is actually what I did:
After running docker compose down && docker compose up -d, I reconfigured the credentials. Once the configuration was completed, the plugin page kept prompting that the credential had been removed and was not using the latest version.
I then selected the credential I had just configured, yet the same error still occurred when running the plugin.
Could it be that the provider is passing a PKCS1_OAEP-encrypted credential to the plugin?
For context: My plugin uses local storage, while the API uses Aliyun OSS. I chose this setup because I do not want the uploaded files and generated files to be stored on the local machine—so I configured the API to use Aliyun OSS, but kept the plugins on local storage.

@WilliamChen-luckbob commented on GitHub (Jan 14, 2026): @dosu This is actually what I did: After running docker compose down && docker compose up -d, I reconfigured the credentials. Once the configuration was completed, the plugin page kept prompting that the credential had been removed and was not using the latest version. I then selected the credential I had just configured, yet the same error still occurred when running the plugin. Could it be that the provider is passing a PKCS1_OAEP-encrypted credential to the plugin? For context: My plugin uses local storage, while the API uses Aliyun OSS. I chose this setup because I do not want the uploaded files and generated files to be stored on the local machine—so I configured the API to use Aliyun OSS, but kept the plugins on local storage.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 14, 2026):

Dify does not use PKCS1_OAEP or any RSA encryption when passing credentials to plugins—credentials are sent as plaintext JSON from the backend to the plugin daemon, so encryption is not the cause of your issue (source). Mixing Aliyun OSS for API storage and local storage for plugins does not affect how credentials are delivered to plugins; credential passing is independent of storage backend.

The persistent credential mismatch you’re seeing is almost always due to credential caching, a provider_name mismatch, or database desynchronization—especially after recent Dify updates. Dify recently changed the provider_name for Gemini from langgenius/google/google to langgenius/gemini/google. If your database still has Gemini credentials with the old provider_name, the backend won’t find or update them, and you’ll get errors or see stale credentials (details and fix).

To resolve this:

  1. Run the PluginDataMigration script (api/services/plugin/data_migration.py) to update all provider_name fields in your database.
  2. After migration, restart all backend and plugin_daemon containers to clear caches.
  3. In the Dify UI, re-save or re-select the correct Gemini API key in the provider settings—even if it looks correct.
  4. Remove any old or duplicate Gemini credentials from the provider settings and database.
  5. If you still see issues, check the plugin_daemon and API logs for credential validation errors.

This will ensure your plugin receives the correct, updated credential. If the problem persists after these steps, a full environment or database reset may be needed to clear out stale state (migration and troubleshooting reference).

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 14, 2026): <!-- Answer --> Dify does not use PKCS1_OAEP or any RSA encryption when passing credentials to plugins—credentials are sent as plaintext JSON from the backend to the plugin daemon, so encryption is not the cause of your issue ([source](https://github.com/langgenius/dify/blob/2bfc54314eeede0352b2abe017a1138cd488e56a/api/core/plugin/impl/tool.py#L15-L156)). Mixing Aliyun OSS for API storage and local storage for plugins does not affect how credentials are delivered to plugins; credential passing is independent of storage backend. The persistent credential mismatch you’re seeing is almost always due to credential caching, a provider_name mismatch, or database desynchronization—especially after recent Dify updates. Dify recently changed the provider_name for Gemini from `langgenius/google/google` to `langgenius/gemini/google`. If your database still has Gemini credentials with the old provider_name, the backend won’t find or update them, and you’ll get errors or see stale credentials ([details and fix](https://github.com/langgenius/dify-official-plugins/issues/2122)). To resolve this: 1. Run the PluginDataMigration script (`api/services/plugin/data_migration.py`) to update all provider_name fields in your database. 2. After migration, restart all backend and plugin_daemon containers to clear caches. 3. In the Dify UI, re-save or re-select the correct Gemini API key in the provider settings—even if it looks correct. 4. Remove any old or duplicate Gemini credentials from the provider settings and database. 5. If you still see issues, check the plugin_daemon and API logs for credential validation errors. This will ensure your plugin receives the correct, updated credential. If the problem persists after these steps, a full environment or database reset may be needed to clear out stale state ([migration and troubleshooting reference](https://github.com/langgenius/dify-official-plugins/issues/2122)). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/3b8bf43f-b0ce-47d0-ab1b-814e4f818121?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/3b8bf43f-b0ce-47d0-ab1b-814e4f818121?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/3b8bf43f-b0ce-47d0-ab1b-814e4f818121?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/3b8bf43f-b0ce-47d0-ab1b-814e4f818121?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/3b8bf43f-b0ce-47d0-ab1b-814e4f818121?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/3b8bf43f-b0ce-47d0-ab1b-814e4f818121?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/3b8bf43f-b0ce-47d0-ab1b-814e4f818121?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Dify%20does%20not%20use%20PKCS1_OAEP%20or%20any%20RSA%20encryption%20when%20passing%20credentials%20to%20plugins%E2%80%94credentials%20are%20sent%20as%20plaintext%20JSON%20from%20the%20backend%20to%20the%20plugin%20daemon%2C%20so%20encryption%20is%20not%20the%20cause%20of%20your%20issue%C2%A0%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/blob/2bfc54314eeede0352b2abe017a1138cd488e56a/api/core/plugin/impl/tool.py%23L15-L156%29%29.%20Mixing%20Aliyun%20OSS%20for%20API%20storage%20and%20local%20storage%20for%20plugins%20does%20not%20affect%20how%20credentials%20are%20delivered%20to%20plugins%3B%20credential%20passing%20is%20independent%20of%20storage%20backend.%0A%0AThe%20persistent%20credential%20mismatch%20you%E2%80%99re%20seeing%20is%20almost%20always%20due%20to%20credential%20caching%2C%20a%20provider_name%20mismatch%2C%20or%20database%20desynchronization%E2%80%94especially%20after%20recent%20Dify%20updates.%20Dify%20recently%20changed%20the%20provider_name%20for%20Gemini%20from%20%60langgenius/google/google%60%20to%20%60langgenius/gemini/google%60.%20If%20your%20database%20still%20has%20Gemini%20credentials%20with%20the%20old%20provider_name%2C%20the%20backend%20won%E2%80%99t%20find%20or%20update%20them%2C%20and%20you%E2%80%99ll%20get%20errors%20or%20see%20stale%20credentials%C2%A0%28%5Bdetails%20and%20fix%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/2122%29%29.%0A%0ATo%20resolve%20this%3A%0A1.%20Run%20the%20PluginDataMigration%20script%20%28%60api/services/plugin/data_migration.py%60%29%20to%20update%20all%20provider_name%20fields%20in%20your%20database.%0A2.%20After%20migration%2C%20restart%20all%20backend%20and%20plugin_daemon%20containers%20to%20clear%20caches.%0A3.%20In%20the%20Dify%20UI%2C%20re-save%20or%20re-select%20the%20correct%20Gemini%20API%20key%20in%20the%20provider%20settings%E2%80%94even%20if%20it%20looks%20correct.%0A4.%20Remove%20any%20old%20or%20duplicate%20Gemini%20credentials%20from%20the%20provider%20settings%20and%20database.%0A5.%20If%20you%20still%20see%20issues%2C%20check%20the%20plugin_daemon%20and%20API%20logs%20for%20credential%20validation%20errors.%0A%0AThis%20will%20ensure%20your%20plugin%20receives%20the%20correct%2C%20updated%20credential.%20If%20the%20problem%20persists%20after%20these%20steps%2C%20a%20full%20environment%20or%20database%20reset%20may%20be%20needed%20to%20clear%20out%20stale%20state%C2%A0%28%5Bmigration%20and%20troubleshooting%20reference%5D%28https%3A//github.com/langgenius/dify-official-plugins/issues/2122%29%29.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 15, 2026):

@dosu
after updating dify to 1.11.3, the oss failed to upload the file with error:

api-1               | 2026-01-15 06:39:18.304 ERROR [Dummy-17] [app.py:875] 7c560923053b5090a4a50dabdcbf57e9 - Exception on /console/api/files/upload [POST]
api-1               | urllib3.exceptions.SSLError: EOF occurred in violation of protocol (_ssl.c:2427)
api-1               | 
api-1               | The above exception was the direct cause of the following exception:
api-1               | 
api-1               | Traceback (most recent call last):
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/requests/adapters.py", line 644, in send
api-1               |     resp = conn.urlopen(
api-1               |            ^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/urllib3/connectionpool.py", line 841, in urlopen
api-1               |     retries = retries.increment(
api-1               |               ^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/urllib3/util/retry.py", line 519, in increment
api-1               |     raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
api-1               |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               | urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='my-aliyun-endpoint', port=443): Max retries exceeded with url: /aigc-properties%2Fupload_files%2F41c856e9-8ea1-4833-bdf3-eb1cb593b3fc%2F9a67a7f0-cb31-4cdd-8035-810ac3c93b01.jpg (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2427)')))
api-1               | 
api-1               | During handling of the above exception, another exception occurred:
api-1               | 
api-1               | Traceback (most recent call last):
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/oss2/http.py", line 47, in do_request
api-1               |     return Response(self.session.request(req.method, req.url,
api-1               |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/requests/sessions.py", line 589, in request
api-1               |     resp = self.send(prep, **send_kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/requests/sessions.py", line 703, in send
api-1               |     r = adapter.send(request, **kwargs)
api-1               |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/requests/adapters.py", line 675, in send
api-1               |     raise SSLError(e, request=request)
api-1               | requests.exceptions.SSLError: HTTPSConnectionPool(host='[my-aliyun-endpoint]', port=443): Max retries exceeded with url: /aigc-properties%2Fupload_files%2F41c856e9-8ea1-4833-bdf3-eb1cb593b3fc%2F9a67a7f0-cb31-4cdd-8035-810ac3c93b01.jpg (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2427)')))
api-1               | 
api-1               | During handling of the above exception, another exception occurred:
api-1               | 
api-1               | Traceback (most recent call last):
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request
api-1               |     rv = self.dispatch_request()
api-1               |          ^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request
api-1               |     return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask_restx/api.py", line 404, in wrapper
api-1               |     resp = resource(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask/views.py", line 110, in view
api-1               |     return current_app.ensure_sync(self.dispatch_request)(**kwargs)  # type: ignore[no-any-return]
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask_restx/resource.py", line 41, in dispatch_request
api-1               |     resp = meth(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/controllers/console/wraps.py", line 230, in decorated
api-1               |     return view(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/libs/login.py", line 80, in decorated_view
api-1               |     return current_app.ensure_sync(func)(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/controllers/console/wraps.py", line 47, in decorated
api-1               |     return view(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/controllers/console/wraps.py", line 132, in decorated
api-1               |     return view(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/controllers/console/files.py", line 83, in post
api-1               |     upload_file = FileService(db.engine).upload_file(
api-1               |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/services/file_service.py", line 85, in upload_file
api-1               |     storage.save(file_key, content)
api-1               |   File "/app/api/extensions/ext_storage.py", line 89, in save
api-1               |     self.storage_runner.save(filename, data)
api-1               |   File "/app/api/extensions/storage/aliyun_oss_storage.py", line 33, in save
api-1               |     self.client.put_object(self.__wrapper_folder_filename(filename), data)
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 669, in put_object
api-1               |     resp = self.__do_object('PUT', key, data=data, headers=headers)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 2984, in __do_object
api-1               |     return self._do(method, self.bucket_name, key, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 237, in _do
api-1               |     resp = self.session.do_request(req, timeout=self.timeout)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/oss2/http.py", line 55, in do_request
api-1               |     raise RequestError(e)
api-1               | oss2.exceptions.RequestError: {'status': -2, 'x-oss-request-id': '', 'details': "RequestError: HTTPSConnectionPool(host='[my-aliyun-endpoint]', port=443): Max retries exceeded with url: /aigc-properties%2Fupload_files%2F41c856e9-8ea1-4833-bdf3-eb1cb593b3fc%2F9a67a7f0-cb31-4cdd-8035-810ac3c93b01.jpg (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2427)')))"}

I have double-checked the .env file and docker-compose-template.yaml to verify if any modifications have been made. I have also configured my custom settings (such as OSS keys) as required.

Additionally, I noticed that some SSL strategies were changed between versions 1.11.1 and 1.11.3, so I kept the SSL configuration consistent with version 1.11.3—only the service host and ports have been modified.

What could be causing this issue?

@WilliamChen-luckbob commented on GitHub (Jan 15, 2026): @dosu after updating dify to 1.11.3, the oss failed to upload the file with error: ``` api-1 | 2026-01-15 06:39:18.304 ERROR [Dummy-17] [app.py:875] 7c560923053b5090a4a50dabdcbf57e9 - Exception on /console/api/files/upload [POST] api-1 | urllib3.exceptions.SSLError: EOF occurred in violation of protocol (_ssl.c:2427) api-1 | api-1 | The above exception was the direct cause of the following exception: api-1 | api-1 | Traceback (most recent call last): api-1 | File "/app/api/.venv/lib/python3.12/site-packages/requests/adapters.py", line 644, in send api-1 | resp = conn.urlopen( api-1 | ^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/urllib3/connectionpool.py", line 841, in urlopen api-1 | retries = retries.increment( api-1 | ^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/urllib3/util/retry.py", line 519, in increment api-1 | raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type] api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='my-aliyun-endpoint', port=443): Max retries exceeded with url: /aigc-properties%2Fupload_files%2F41c856e9-8ea1-4833-bdf3-eb1cb593b3fc%2F9a67a7f0-cb31-4cdd-8035-810ac3c93b01.jpg (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2427)'))) api-1 | api-1 | During handling of the above exception, another exception occurred: api-1 | api-1 | Traceback (most recent call last): api-1 | File "/app/api/.venv/lib/python3.12/site-packages/oss2/http.py", line 47, in do_request api-1 | return Response(self.session.request(req.method, req.url, api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/requests/sessions.py", line 589, in request api-1 | resp = self.send(prep, **send_kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/requests/sessions.py", line 703, in send api-1 | r = adapter.send(request, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/requests/adapters.py", line 675, in send api-1 | raise SSLError(e, request=request) api-1 | requests.exceptions.SSLError: HTTPSConnectionPool(host='[my-aliyun-endpoint]', port=443): Max retries exceeded with url: /aigc-properties%2Fupload_files%2F41c856e9-8ea1-4833-bdf3-eb1cb593b3fc%2F9a67a7f0-cb31-4cdd-8035-810ac3c93b01.jpg (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2427)'))) api-1 | api-1 | During handling of the above exception, another exception occurred: api-1 | api-1 | Traceback (most recent call last): api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request api-1 | rv = self.dispatch_request() api-1 | ^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request api-1 | return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask_restx/api.py", line 404, in wrapper api-1 | resp = resource(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask/views.py", line 110, in view api-1 | return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return] api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask_restx/resource.py", line 41, in dispatch_request api-1 | resp = meth(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/controllers/console/wraps.py", line 230, in decorated api-1 | return view(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/libs/login.py", line 80, in decorated_view api-1 | return current_app.ensure_sync(func)(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/controllers/console/wraps.py", line 47, in decorated api-1 | return view(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/controllers/console/wraps.py", line 132, in decorated api-1 | return view(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/controllers/console/files.py", line 83, in post api-1 | upload_file = FileService(db.engine).upload_file( api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/services/file_service.py", line 85, in upload_file api-1 | storage.save(file_key, content) api-1 | File "/app/api/extensions/ext_storage.py", line 89, in save api-1 | self.storage_runner.save(filename, data) api-1 | File "/app/api/extensions/storage/aliyun_oss_storage.py", line 33, in save api-1 | self.client.put_object(self.__wrapper_folder_filename(filename), data) api-1 | File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 669, in put_object api-1 | resp = self.__do_object('PUT', key, data=data, headers=headers) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 2984, in __do_object api-1 | return self._do(method, self.bucket_name, key, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 237, in _do api-1 | resp = self.session.do_request(req, timeout=self.timeout) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/oss2/http.py", line 55, in do_request api-1 | raise RequestError(e) api-1 | oss2.exceptions.RequestError: {'status': -2, 'x-oss-request-id': '', 'details': "RequestError: HTTPSConnectionPool(host='[my-aliyun-endpoint]', port=443): Max retries exceeded with url: /aigc-properties%2Fupload_files%2F41c856e9-8ea1-4833-bdf3-eb1cb593b3fc%2F9a67a7f0-cb31-4cdd-8035-810ac3c93b01.jpg (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2427)')))"} ``` I have double-checked the .env file and docker-compose-template.yaml to verify if any modifications have been made. I have also configured my custom settings (such as OSS keys) as required. Additionally, I noticed that some SSL strategies were changed between versions 1.11.1 and 1.11.3, so I kept the SSL configuration consistent with version 1.11.3—only the service host and ports have been modified. What could be causing this issue?
Author
Owner

@dosubot[bot] commented on GitHub (Jan 15, 2026):

This SSL error is usually caused by a TLS handshake or protocol mismatch between the Dify container (using oss2/requests/urllib3) and your Aliyun OSS endpoint. Upgrading Dify may have updated Python, oss2, or requests, which can introduce stricter SSL/TLS requirements or drop support for older/insecure protocols, making some endpoints incompatible (source).

Here’s how to troubleshoot and resolve it:

  1. Double-check your ALIYUN_OSS_ENDPOINT: It must start with https:// and match your bucket’s region and protocol. If you use an internal endpoint, make sure it supports TLS 1.2+.

  2. Test connectivity from inside the API container:

    • Run docker compose exec api bash
    • Try curl -v https://your-oss-endpoint
    • If curl fails, the problem is network/firewall or certificate trust.
  3. If your OSS endpoint uses a custom or self-signed certificate, copy the root CA into the container and run update-ca-certificates, then restart the container (source).

  4. As a diagnostic step, set HTTP_REQUEST_NODE_SSL_VERIFY=False in your .env and restart the API container. If uploads work, the issue is with SSL verification (certificate trust or protocol mismatch). Only use this for testing, not production.

  5. Check for proxy/firewall issues: Make sure no HTTP_PROXY or HTTPS_PROXY variables are set unless you intend to use a proxy, and that nothing is blocking outbound 443 traffic from the container.

  6. If the above doesn’t help, create a minimal Python script using httpx or oss2 inside the container to test direct uploads to OSS. This can help isolate whether the issue is with Dify, the OSS endpoint, or the underlying libraries (source).

  7. If disabling SSL verification or using a minimal script works, but Dify still fails in production mode, consider downgrading to Dify 1.11.1 to confirm if this is a regression in the new version or its dependencies.

Let me know what you find after these steps, especially if curl or a minimal script also fails inside the container. This will help pinpoint whether it’s a Dify config issue, a dependency regression, or an OSS endpoint compatibility problem.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 15, 2026): <!-- Answer --> This SSL error is usually caused by a TLS handshake or protocol mismatch between the Dify container (using oss2/requests/urllib3) and your Aliyun OSS endpoint. Upgrading Dify may have updated Python, oss2, or requests, which can introduce stricter SSL/TLS requirements or drop support for older/insecure protocols, making some endpoints incompatible ([source](https://github.com/langgenius/dify/blob/52af829f1fadf2afeb3c62e57137c160e8179050/api/extensions/storage/aliyun_oss_storage.py#L10-L34)). Here’s how to troubleshoot and resolve it: 1. **Double-check your ALIYUN_OSS_ENDPOINT**: It must start with `https://` and match your bucket’s region and protocol. If you use an internal endpoint, make sure it supports TLS 1.2+. 2. **Test connectivity from inside the API container**: - Run `docker compose exec api bash` - Try `curl -v https://your-oss-endpoint` - If curl fails, the problem is network/firewall or certificate trust. 3. **If your OSS endpoint uses a custom or self-signed certificate**, copy the root CA into the container and run `update-ca-certificates`, then restart the container ([source](https://github.com/langgenius/dify/issues/20266)). 4. **As a diagnostic step**, set `HTTP_REQUEST_NODE_SSL_VERIFY=False` in your `.env` and restart the API container. If uploads work, the issue is with SSL verification (certificate trust or protocol mismatch). Only use this for testing, not production. 5. **Check for proxy/firewall issues**: Make sure no HTTP_PROXY or HTTPS_PROXY variables are set unless you intend to use a proxy, and that nothing is blocking outbound 443 traffic from the container. 6. **If the above doesn’t help**, create a minimal Python script using httpx or oss2 inside the container to test direct uploads to OSS. This can help isolate whether the issue is with Dify, the OSS endpoint, or the underlying libraries ([source](https://github.com/langgenius/dify/discussions/27441)). 7. **If disabling SSL verification or using a minimal script works, but Dify still fails in production mode**, consider downgrading to Dify 1.11.1 to confirm if this is a regression in the new version or its dependencies. Let me know what you find after these steps, especially if curl or a minimal script also fails inside the container. This will help pinpoint whether it’s a Dify config issue, a dependency regression, or an OSS endpoint compatibility problem. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/79a11770-671d-4c8b-9fd9-3964b74b961e?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/79a11770-671d-4c8b-9fd9-3964b74b961e?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/79a11770-671d-4c8b-9fd9-3964b74b961e?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/79a11770-671d-4c8b-9fd9-3964b74b961e?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/79a11770-671d-4c8b-9fd9-3964b74b961e?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/79a11770-671d-4c8b-9fd9-3964b74b961e?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/79a11770-671d-4c8b-9fd9-3964b74b961e?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20SSL%20error%20is%20usually%20caused%20by%20a%20TLS%20handshake%20or%20protocol%20mismatch%20between%20the%20Dify%20container%20%28using%20oss2/requests/urllib3%29%20and%20your%20Aliyun%20OSS%20endpoint.%20Upgrading%20Dify%20may%20have%20updated%20Python%2C%20oss2%2C%20or%20requests%2C%20which%20can%20introduce%20stricter%20SSL/TLS%20requirements%20or%20drop%20support%20for%20older/insecure%20protocols%2C%20making%20some%20endpoints%20incompatible%C2%A0%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/blob/52af829f1fadf2afeb3c62e57137c160e8179050/api/extensions/storage/aliyun_oss_storage.py%23L10-L34%29%29.%0A%0AHere%E2%80%99s%20how%20to%20troubleshoot%20and%20resolve%20it%3A%0A%0A1.%20%2A%2ADouble-check%20your%20ALIYUN_OSS_ENDPOINT%2A%2A%3A%20It%20must%20start%20with%20%60https%3A//%60%20and%20match%20your%20bucket%E2%80%99s%20region%20and%20protocol.%20If%20you%20use%20an%20internal%20endpoint%2C%20make%20sure%20it%20supports%20TLS%201.2%2B.%0A%0A2.%20%2A%2ATest%20connectivity%20from%20inside%20the%20API%20container%2A%2A%3A%20%20%0A%20%20%20-%20Run%20%60docker%20compose%20exec%20api%20bash%60%20%20%0A%20%20%20-%20Try%20%60curl%20-v%20https%3A//your-oss-endpoint%60%20%20%0A%20%20%20-%20If%20curl%20fails%2C%20the%20problem%20is%20network/firewall%20or%20certificate%20trust.%0A%0A3.%20%2A%2AIf%20your%20OSS%20endpoint%20uses%20a%20custom%20or%20self-signed%20certificate%2A%2A%2C%20copy%20the%20root%20CA%20into%20the%20container%20and%20run%20%60update-ca-certificates%60%2C%20then%20restart%20the%20container%C2%A0%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/issues/20266%29%29.%0A%0A4.%20%2A%2AAs%20a%20diagnostic%20step%2A%2A%2C%20set%20%60HTTP_REQUEST_NODE_SSL_VERIFY%3DFalse%60%20in%20your%20%60.env%60%20and%20restart%20the%20API%20container.%20If%20uploads%20work%2C%20the%20issue%20is%20with%20SSL%20verification%20%28certificate%20trust%20or%20protocol%20mismatch%29.%20Only%20use%20this%20for%20testing%2C%20not%20production.%0A%0A5.%20%2A%2ACheck%20for%20proxy/firewall%20issues%2A%2A%3A%20Make%20sure%20no%20HTTP_PROXY%20or%20HTTPS_PROXY%20variables%20are%20set%20unless%20you%20intend%20to%20use%20a%20proxy%2C%20and%20that%20nothing%20is%20blocking%20outbound%20443%20traffic%20from%20the%20container.%0A%0A6.%20%2A%2AIf%20the%20above%20doesn%E2%80%99t%20help%2A%2A%2C%20create%20a%20minimal%20Python%20script%20using%20httpx%20or%20oss2%20inside%20the%20container%20to%20test%20direct%20uploads%20to%20OSS.%20This%20can%20help%20isolate%20whether%20the%20issue%20is%20with%20Dify%2C%20the%20OSS%20endpoint%2C%20or%20the%20underlying%20libraries%C2%A0%28%5Bsource%5D%28https%3A//github.com/langgenius/dify/discussions/27441%29%29.%0A%0A7.%20%2A%2AIf%20disabling%20SSL%20verification%20or%20using%20a%20minimal%20script%20works%2C%20but%20Dify%20still%20fails%20in%20production%20mode%2A%2A%2C%20consider%20downgrading%20to%20Dify%201.11.1%20to%20confirm%20if%20this%20is%20a%20regression%20in%20the%20new%20version%20or%20its%20dependencies.%0A%0ALet%20me%20know%20what%20you%20find%20after%20these%20steps%2C%20especially%20if%20curl%20or%20a%20minimal%20script%20also%20fails%20inside%20the%20container.%20This%20will%20help%20pinpoint%20whether%20it%E2%80%99s%20a%20Dify%20config%20issue%2C%20a%20dependency%20regression%2C%20or%20an%20OSS%20endpoint%20compatibility%20problem.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 15, 2026):

@dosu
hmmm....
It seems a mistake in updating the code... For several restarts, the error not showing again and I didn't change anything either, but another error occurs.

Here is the log:

api-1               | 2026-01-15 06:56:29.209 INFO [Dummy-28] [api.py:240] 2d099950382c5e74b9ffd36592320ebd - Exception: {'status': 400, 'x-oss-request-id': '69688F946DB0A83736BC082F', 'details': {'Code': 'InvalidArgument', 'Message': 'Invalid signing product in Authorization header.', 'RequestId': '69688F946DB0A83736BC082F', 'HostId': '[my-aliyun-host]', 'Authorization': 'OSS4-HMAC-SHA256 Credential=xxxxx/20260115/your-cloudbox-id/oss-cloudbox/aliyun_v4_request, Signature=e2fae441802969da2b13544063e10c8b899327f126ca0fb6065f52a9734854b3', 'EC': '0002-00000227', 'RecommendDoc': 'https://api.aliyun.com/troubleshoot?q=0002-00000227'}}
api-1               | 2026-01-15 06:56:29.210 ERROR [Dummy-28] [app.py:875] 2d099950382c5e74b9ffd36592320ebd - Exception on /console/api/files/upload [POST]
api-1               | Traceback (most recent call last):
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request
api-1               |     rv = self.dispatch_request()
api-1               |          ^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request
api-1               |     return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask_restx/api.py", line 404, in wrapper
api-1               |     resp = resource(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask/views.py", line 110, in view
api-1               |     return current_app.ensure_sync(self.dispatch_request)(**kwargs)  # type: ignore[no-any-return]
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/flask_restx/resource.py", line 41, in dispatch_request
api-1               |     resp = meth(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/controllers/console/wraps.py", line 230, in decorated
api-1               |     return view(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/libs/login.py", line 80, in decorated_view
api-1               |     return current_app.ensure_sync(func)(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/controllers/console/wraps.py", line 47, in decorated
api-1               |     return view(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/controllers/console/wraps.py", line 132, in decorated
api-1               |     return view(*args, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/controllers/console/files.py", line 83, in post
api-1               |     upload_file = FileService(db.engine).upload_file(
api-1               |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/services/file_service.py", line 85, in upload_file
api-1               |     storage.save(file_key, content)
api-1               |   File "/app/api/extensions/ext_storage.py", line 89, in save
api-1               |     self.storage_runner.save(filename, data)
api-1               |   File "/app/api/extensions/storage/aliyun_oss_storage.py", line 33, in save
api-1               |     self.client.put_object(self.__wrapper_folder_filename(filename), data)
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 669, in put_object
api-1               |     resp = self.__do_object('PUT', key, data=data, headers=headers)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 2984, in __do_object
api-1               |     return self._do(method, self.bucket_name, key, **kwargs)
api-1               |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
api-1               |   File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 241, in _do
api-1               |     raise e
api-1               | oss2.exceptions.InvalidArgument: {'status': 400, 'x-oss-request-id': '69688F946DB0A83736BC082F', 'details': {'Code': 'InvalidArgument', 'Message': 'Invalid signing product in Authorization header.', 'RequestId': '69688F946DB0A83736BC082F', 'HostId': '[my-aliyun-host]', 'Authorization': 'OSS4-HMAC-SHA256 Credential=xxxxxx/20260115/your-cloudbox-id/oss-cloudbox/aliyun_v4_request, Signature=e2fae441802969da2b13544063e10c8b899327f126ca0fb6065f52a9734854b3', 'EC': '0002-00000227', 'RecommendDoc': 'https://api.aliyun.com/troubleshoot?q=0002-00000227'}}
api-1               | 2026-01-15 06:56:29.216 INFO [Dummy-28] [ext_request_logging.py:73] 2d099950382c5e74b9ffd36592320ebd - POST /console/api/files/upload 500 9752.642

I have already commented out the cloudbox config, but it seems dify 1.11.3 will force to generate a cloudbox request? Mine is aliyun-oss, not cloudbox.

STORAGE_TYPE=aliyun-oss
ALIYUN_OSS_BUCKET_NAME=[my-bucket-name]
ALIYUN_OSS_ACCESS_KEY=[my-key]
ALIYUN_OSS_SECRET_KEY=[my-secret]
ALIYUN_OSS_ENDPOINT=[my-endpoint]
ALIYUN_OSS_REGION=[my-region]
ALIYUN_OSS_AUTH_VERSION=v4
ALIYUN_OSS_PATH=[my-file-path]
#ALIYUN_CLOUDBOX_ID=this line commented out

this is all the config of api, there is no configs in docker-compose-template.yaml I think.

@WilliamChen-luckbob commented on GitHub (Jan 15, 2026): @dosu hmmm.... It seems a mistake in updating the code... For several restarts, the error not showing again and I didn't change anything either, but another error occurs. Here is the log: ``` api-1 | 2026-01-15 06:56:29.209 INFO [Dummy-28] [api.py:240] 2d099950382c5e74b9ffd36592320ebd - Exception: {'status': 400, 'x-oss-request-id': '69688F946DB0A83736BC082F', 'details': {'Code': 'InvalidArgument', 'Message': 'Invalid signing product in Authorization header.', 'RequestId': '69688F946DB0A83736BC082F', 'HostId': '[my-aliyun-host]', 'Authorization': 'OSS4-HMAC-SHA256 Credential=xxxxx/20260115/your-cloudbox-id/oss-cloudbox/aliyun_v4_request, Signature=e2fae441802969da2b13544063e10c8b899327f126ca0fb6065f52a9734854b3', 'EC': '0002-00000227', 'RecommendDoc': 'https://api.aliyun.com/troubleshoot?q=0002-00000227'}} api-1 | 2026-01-15 06:56:29.210 ERROR [Dummy-28] [app.py:875] 2d099950382c5e74b9ffd36592320ebd - Exception on /console/api/files/upload [POST] api-1 | Traceback (most recent call last): api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request api-1 | rv = self.dispatch_request() api-1 | ^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request api-1 | return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask_restx/api.py", line 404, in wrapper api-1 | resp = resource(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask/views.py", line 110, in view api-1 | return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return] api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/flask_restx/resource.py", line 41, in dispatch_request api-1 | resp = meth(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/controllers/console/wraps.py", line 230, in decorated api-1 | return view(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/libs/login.py", line 80, in decorated_view api-1 | return current_app.ensure_sync(func)(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/controllers/console/wraps.py", line 47, in decorated api-1 | return view(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/controllers/console/wraps.py", line 132, in decorated api-1 | return view(*args, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/controllers/console/files.py", line 83, in post api-1 | upload_file = FileService(db.engine).upload_file( api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/services/file_service.py", line 85, in upload_file api-1 | storage.save(file_key, content) api-1 | File "/app/api/extensions/ext_storage.py", line 89, in save api-1 | self.storage_runner.save(filename, data) api-1 | File "/app/api/extensions/storage/aliyun_oss_storage.py", line 33, in save api-1 | self.client.put_object(self.__wrapper_folder_filename(filename), data) api-1 | File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 669, in put_object api-1 | resp = self.__do_object('PUT', key, data=data, headers=headers) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 2984, in __do_object api-1 | return self._do(method, self.bucket_name, key, **kwargs) api-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ api-1 | File "/app/api/.venv/lib/python3.12/site-packages/oss2/api.py", line 241, in _do api-1 | raise e api-1 | oss2.exceptions.InvalidArgument: {'status': 400, 'x-oss-request-id': '69688F946DB0A83736BC082F', 'details': {'Code': 'InvalidArgument', 'Message': 'Invalid signing product in Authorization header.', 'RequestId': '69688F946DB0A83736BC082F', 'HostId': '[my-aliyun-host]', 'Authorization': 'OSS4-HMAC-SHA256 Credential=xxxxxx/20260115/your-cloudbox-id/oss-cloudbox/aliyun_v4_request, Signature=e2fae441802969da2b13544063e10c8b899327f126ca0fb6065f52a9734854b3', 'EC': '0002-00000227', 'RecommendDoc': 'https://api.aliyun.com/troubleshoot?q=0002-00000227'}} api-1 | 2026-01-15 06:56:29.216 INFO [Dummy-28] [ext_request_logging.py:73] 2d099950382c5e74b9ffd36592320ebd - POST /console/api/files/upload 500 9752.642 ``` I have already commented out the cloudbox config, but it seems dify 1.11.3 will force to generate a cloudbox request? Mine is aliyun-oss, not cloudbox. ``` STORAGE_TYPE=aliyun-oss ALIYUN_OSS_BUCKET_NAME=[my-bucket-name] ALIYUN_OSS_ACCESS_KEY=[my-key] ALIYUN_OSS_SECRET_KEY=[my-secret] ALIYUN_OSS_ENDPOINT=[my-endpoint] ALIYUN_OSS_REGION=[my-region] ALIYUN_OSS_AUTH_VERSION=v4 ALIYUN_OSS_PATH=[my-file-path] #ALIYUN_CLOUDBOX_ID=this line commented out ``` this is all the config of api, there is no configs in docker-compose-template.yaml I think.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 15, 2026):

This error happens because Dify 1.11.3 always passes the ALIYUN_CLOUDBOX_ID environment variable to the OSS client—even if you comment it out in your .env. If ALIYUN_CLOUDBOX_ID is set (even to a placeholder like your-cloudbox-id from .env.example), Dify signs requests as a Cloudbox request, which standard Aliyun OSS endpoints reject with “Invalid signing product in Authorization header” see code.

To fix this, explicitly set ALIYUN_CLOUDBOX_ID= (empty value) in your .env file—do not just comment it out. Then restart all Dify containers to clear the environment. This will prevent Dify from using Cloudbox signing logic and restore standard OSS uploads.

If your OSS endpoint does not support v4 signing, try setting ALIYUN_OSS_AUTH_VERSION=v2 as a fallback.

After restarting, verify that uploads succeed and the Authorization header no longer references oss-cloudbox or your-cloudbox-id.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 15, 2026): <!-- Answer --> This error happens because Dify 1.11.3 always passes the ALIYUN_CLOUDBOX_ID environment variable to the OSS client—even if you comment it out in your `.env`. If ALIYUN_CLOUDBOX_ID is set (even to a placeholder like `your-cloudbox-id` from `.env.example`), Dify signs requests as a Cloudbox request, which standard Aliyun OSS endpoints reject with “Invalid signing product in Authorization header” [see code](https://github.com/langgenius/dify/blob/33e99f069bec8dfe6338597e47045bac7975d511/api/extensions/storage/aliyun_oss_storage.py#L17-L33). To fix this, explicitly set `ALIYUN_CLOUDBOX_ID=` (empty value) in your `.env` file—do not just comment it out. Then restart all Dify containers to clear the environment. This will prevent Dify from using Cloudbox signing logic and restore standard OSS uploads. If your OSS endpoint does not support v4 signing, try setting `ALIYUN_OSS_AUTH_VERSION=v2` as a fallback. After restarting, verify that uploads succeed and the Authorization header no longer references `oss-cloudbox` or `your-cloudbox-id`. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/1ed14e03-9fc5-4c1c-bfae-d4b897ef5c4a?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/1ed14e03-9fc5-4c1c-bfae-d4b897ef5c4a?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/1ed14e03-9fc5-4c1c-bfae-d4b897ef5c4a?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/1ed14e03-9fc5-4c1c-bfae-d4b897ef5c4a?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/1ed14e03-9fc5-4c1c-bfae-d4b897ef5c4a?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/1ed14e03-9fc5-4c1c-bfae-d4b897ef5c4a?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/1ed14e03-9fc5-4c1c-bfae-d4b897ef5c4a?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20error%20happens%20because%20Dify%201.11.3%20always%20passes%20the%20ALIYUN_CLOUDBOX_ID%20environment%20variable%20to%20the%20OSS%20client%E2%80%94even%20if%20you%20comment%20it%20out%20in%20your%20%60.env%60.%20If%20ALIYUN_CLOUDBOX_ID%20is%20set%20%28even%20to%20a%20placeholder%20like%20%60your-cloudbox-id%60%20from%20%60.env.example%60%29%2C%20Dify%20signs%20requests%20as%20a%20Cloudbox%20request%2C%20which%20standard%20Aliyun%20OSS%20endpoints%20reject%20with%20%E2%80%9CInvalid%20signing%20product%20in%20Authorization%20header%E2%80%9D%C2%A0%5Bsee%20code%5D%28https%3A//github.com/langgenius/dify/blob/33e99f069bec8dfe6338597e47045bac7975d511/api/extensions/storage/aliyun_oss_storage.py%23L17-L33%29.%0A%0ATo%20fix%20this%2C%20explicitly%20set%20%60ALIYUN_CLOUDBOX_ID%3D%60%20%28empty%20value%29%20in%20your%20%60.env%60%20file%E2%80%94do%20not%20just%20comment%20it%20out.%20Then%20restart%20all%20Dify%20containers%20to%20clear%20the%20environment.%20This%20will%20prevent%20Dify%20from%20using%20Cloudbox%20signing%20logic%20and%20restore%20standard%20OSS%20uploads.%0A%0AIf%20your%20OSS%20endpoint%20does%20not%20support%20v4%20signing%2C%20try%20setting%20%60ALIYUN_OSS_AUTH_VERSION%3Dv2%60%20as%20a%20fallback.%0A%0AAfter%20restarting%2C%20verify%20that%20uploads%20succeed%20and%20the%20Authorization%20header%20no%20longer%20references%20%60oss-cloudbox%60%20or%20%60your-cloudbox-id%60.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 15, 2026):

@dosu
Won't help

STORAGE_TYPE=aliyun-oss
ALIYUN_OSS_BUCKET_NAME=[my-bucket-name]
ALIYUN_OSS_ACCESS_KEY=[my-key]
ALIYUN_OSS_SECRET_KEY=[my-secret]
ALIYUN_OSS_ENDPOINT=[my-endpoint]
ALIYUN_OSS_REGION=[my-region]
ALIYUN_OSS_AUTH_VERSION=v4
ALIYUN_OSS_PATH=[my-file-path]
ALIYUN_CLOUDBOX_ID=

Set this config. Then restart all Dify containers to clear the environment.
the same error remains. Is there any caches in dify?
a "your-cloudbox-id" pattern is still in my error log.

@WilliamChen-luckbob commented on GitHub (Jan 15, 2026): @dosu Won't help ``` STORAGE_TYPE=aliyun-oss ALIYUN_OSS_BUCKET_NAME=[my-bucket-name] ALIYUN_OSS_ACCESS_KEY=[my-key] ALIYUN_OSS_SECRET_KEY=[my-secret] ALIYUN_OSS_ENDPOINT=[my-endpoint] ALIYUN_OSS_REGION=[my-region] ALIYUN_OSS_AUTH_VERSION=v4 ALIYUN_OSS_PATH=[my-file-path] ALIYUN_CLOUDBOX_ID= ``` Set this config. Then restart all Dify containers to clear the environment. the same error remains. Is there any caches in dify? a "your-cloudbox-id" pattern is still in my error log.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 15, 2026):

This happens because the Dify Docker stack uses the default value from .env.example (ALIYUN_CLOUDBOX_ID=your-cloudbox-id) unless you explicitly override it. If you set ALIYUN_CLOUDBOX_ID= (empty) only in your .env, but then regenerate docker-compose.yaml using the generate_docker_compose script, the script still injects the default unless you also clear it in .env.example. The ${KEY:-default} syntax treats an empty value as "unset" and falls back to the default from .env.example—so the placeholder persists in your containers even after a restart details.

How to fix:

  1. Edit both your .env and .env.example so that ALIYUN_CLOUDBOX_ID= (empty) in both files.
  2. Regenerate your docker-compose.yaml using the generate_docker_compose script.
  3. Inspect the generated docker-compose.yaml and confirm that ALIYUN_CLOUDBOX_ID is set to an empty value in the environment section for all relevant services.
  4. Run docker compose down to fully stop and remove all containers, then docker compose up -d to recreate them.
  5. Exec into the running api container and run printenv | grep ALIYUN_CLOUDBOX_ID to confirm the value is empty at runtime.

This will ensure the placeholder is not injected and OSS signing will use the correct logic reference. No additional cache or volume wipe is needed—just make sure the environment is correct before you restart the stack.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 15, 2026): <!-- Answer --> This happens because the Dify Docker stack uses the default value from `.env.example` (`ALIYUN_CLOUDBOX_ID=your-cloudbox-id`) unless you explicitly override it. If you set `ALIYUN_CLOUDBOX_ID=` (empty) only in your `.env`, but then regenerate `docker-compose.yaml` using the `generate_docker_compose` script, the script still injects the default unless you also clear it in `.env.example`. The `${KEY:-default}` syntax treats an empty value as "unset" and falls back to the default from `.env.example`—so the placeholder persists in your containers even after a restart [details](https://github.com/langgenius/dify/blob/4a197b94585a25c0f79bce4227b7ef3f89680ad0/docker/generate_docker_compose#L7-L74). **How to fix:** 1. Edit both your `.env` and `.env.example` so that `ALIYUN_CLOUDBOX_ID=` (empty) in both files. 2. Regenerate your `docker-compose.yaml` using the `generate_docker_compose` script. 3. Inspect the generated `docker-compose.yaml` and confirm that `ALIYUN_CLOUDBOX_ID` is set to an empty value in the environment section for all relevant services. 4. Run `docker compose down` to fully stop and remove all containers, then `docker compose up -d` to recreate them. 5. Exec into the running `api` container and run `printenv | grep ALIYUN_CLOUDBOX_ID` to confirm the value is empty at runtime. This will ensure the placeholder is not injected and OSS signing will use the correct logic [reference](https://github.com/langgenius/dify/blob/4a197b94585a25c0f79bce4227b7ef3f89680ad0/docker/.env.example#L483-L484). No additional cache or volume wipe is needed—just make sure the environment is correct before you restart the stack. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/f7fe42fb-050b-4d80-91e8-9a526efd13ca?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/f7fe42fb-050b-4d80-91e8-9a526efd13ca?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/f7fe42fb-050b-4d80-91e8-9a526efd13ca?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/f7fe42fb-050b-4d80-91e8-9a526efd13ca?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/f7fe42fb-050b-4d80-91e8-9a526efd13ca?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/f7fe42fb-050b-4d80-91e8-9a526efd13ca?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/f7fe42fb-050b-4d80-91e8-9a526efd13ca?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20happens%20because%20the%20Dify%20Docker%20stack%20uses%20the%20default%20value%20from%20%60.env.example%60%20%28%60ALIYUN_CLOUDBOX_ID%3Dyour-cloudbox-id%60%29%20unless%20you%20explicitly%20override%20it.%20If%20you%20set%20%60ALIYUN_CLOUDBOX_ID%3D%60%20%28empty%29%20only%20in%20your%20%60.env%60%2C%20but%20then%20regenerate%20%60docker-compose.yaml%60%20using%20the%20%60generate_docker_compose%60%20script%2C%20the%20script%20still%20injects%20the%20default%20unless%20you%20also%20clear%20it%20in%20%60.env.example%60.%20The%20%60%24%7BKEY%3A-default%7D%60%20syntax%20treats%20an%20empty%20value%20as%20%22unset%22%20and%20falls%20back%20to%20the%20default%20from%20%60.env.example%60%E2%80%94so%20the%20placeholder%20persists%20in%20your%20containers%20even%20after%20a%20restart%C2%A0%5Bdetails%5D%28https%3A//github.com/langgenius/dify/blob/4a197b94585a25c0f79bce4227b7ef3f89680ad0/docker/generate_docker_compose%23L7-L74%29.%0A%0A%2A%2AHow%20to%20fix%3A%2A%2A%0A1.%20Edit%20both%20your%20%60.env%60%20and%20%60.env.example%60%20so%20that%20%60ALIYUN_CLOUDBOX_ID%3D%60%20%28empty%29%20in%20both%20files.%0A2.%20Regenerate%20your%20%60docker-compose.yaml%60%20using%20the%20%60generate_docker_compose%60%20script.%0A3.%20Inspect%20the%20generated%20%60docker-compose.yaml%60%20and%20confirm%20that%20%60ALIYUN_CLOUDBOX_ID%60%20is%20set%20to%20an%20empty%20value%20in%20the%20environment%20section%20for%20all%20relevant%20services.%0A4.%20Run%20%60docker%20compose%20down%60%20to%20fully%20stop%20and%20remove%20all%20containers%2C%20then%20%60docker%20compose%20up%20-d%60%20to%20recreate%20them.%0A5.%20Exec%20into%20the%20running%20%60api%60%20container%20and%20run%20%60printenv%20%7C%20grep%20ALIYUN_CLOUDBOX_ID%60%20to%20confirm%20the%20value%20is%20empty%20at%20runtime.%0A%0AThis%20will%20ensure%20the%20placeholder%20is%20not%20injected%20and%20OSS%20signing%20will%20use%20the%20correct%20logic%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/blob/4a197b94585a25c0f79bce4227b7ef3f89680ad0/docker/.env.example%23L483-L484%29.%20No%20additional%20cache%20or%20volume%20wipe%20is%20needed%E2%80%94just%20make%20sure%20the%20environment%20is%20correct%20before%20you%20restart%20the%20stack.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 15, 2026):

@dosu
You really misled me a lot T_T
I tried several times, and finally found that commenting out ALIYUN_CLOUDBOX_ID was the right fix.
I didn't realize that the configurations in .env also need to be written into docker-compose.yaml — I just thought Docker would automatically reference the .env file at runtime....
That’s why I always felt like Dify’s configuration items had a caching effect — I didn’t realize that I also need to run generate_docker_compose every time I modify the .env file. Now everything works fine.

@WilliamChen-luckbob commented on GitHub (Jan 15, 2026): @dosu You really misled me a lot T_T I tried several times, and finally found that commenting out ALIYUN_CLOUDBOX_ID was the right fix. I didn't realize that the configurations in .env also need to be written into docker-compose.yaml — I just thought Docker would automatically reference the .env file at runtime.... That’s why I always felt like Dify’s configuration items had a caching effect — I didn’t realize that I also need to run generate_docker_compose every time I modify the .env file. Now everything works fine.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 15, 2026):

Totally understandable—this trips up a lot of people with Dify’s Docker setup. In this project, Docker Compose doesn’t read .env directly at runtime. Instead, you have to run the generate_docker_compose script after editing .env (or .env.example), which injects your environment variables into docker-compose.yaml. Docker Compose then uses only what’s in docker-compose.yaml when starting containers. If you skip the generation step, your changes won’t take effect, which feels like “caching” or stale config reference reference.

The correct workflow is: edit .env → run generate_docker_compose → restart containers. This ensures your containers get the updated environment. Don’t edit docker-compose.yaml directly, since it gets overwritten by the script.

If you ever change environment variables, just remember to regenerate and restart. Let me know if you want a quick checklist or have any other config questions!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 15, 2026): <!-- Answer --> Totally understandable—this trips up a lot of people with Dify’s Docker setup. In this project, Docker Compose doesn’t read .env directly at runtime. Instead, you have to run the generate_docker_compose script after editing .env (or .env.example), which injects your environment variables into docker-compose.yaml. Docker Compose then uses only what’s in docker-compose.yaml when starting containers. If you skip the generation step, your changes won’t take effect, which feels like “caching” or stale config [reference](https://github.com/langgenius/dify/discussions/22603#discussioncomment-13802141) [reference](https://github.com/langgenius/dify/discussions/30256#discussioncomment-15353344). The correct workflow is: edit .env → run generate_docker_compose → restart containers. This ensures your containers get the updated environment. Don’t edit docker-compose.yaml directly, since it gets overwritten by the script. If you ever change environment variables, just remember to regenerate and restart. Let me know if you want a quick checklist or have any other config questions! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/bf26f842-9755-4a91-b70c-036186b4d588?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/bf26f842-9755-4a91-b70c-036186b4d588?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/bf26f842-9755-4a91-b70c-036186b4d588?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/bf26f842-9755-4a91-b70c-036186b4d588?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/bf26f842-9755-4a91-b70c-036186b4d588?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/bf26f842-9755-4a91-b70c-036186b4d588?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/bf26f842-9755-4a91-b70c-036186b4d588?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Totally%20understandable%E2%80%94this%20trips%20up%20a%20lot%20of%20people%20with%20Dify%E2%80%99s%20Docker%20setup.%20In%20this%20project%2C%20Docker%20Compose%20doesn%E2%80%99t%20read%20.env%20directly%20at%20runtime.%20Instead%2C%20you%20have%20to%20run%20the%20generate_docker_compose%20script%20after%20editing%20.env%20%28or%20.env.example%29%2C%20which%20injects%20your%20environment%20variables%20into%20docker-compose.yaml.%20Docker%20Compose%20then%20uses%20only%20what%E2%80%99s%20in%20docker-compose.yaml%20when%20starting%20containers.%20If%20you%20skip%20the%20generation%20step%2C%20your%20changes%20won%E2%80%99t%20take%20effect%2C%20which%20feels%20like%20%E2%80%9Ccaching%E2%80%9D%20or%20stale%20config%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/discussions/22603%23discussioncomment-13802141%29%C2%A0%5Breference%5D%28https%3A//github.com/langgenius/dify/discussions/30256%23discussioncomment-15353344%29.%0A%0AThe%20correct%20workflow%20is%3A%20edit%20.env%20%E2%86%92%20run%20generate_docker_compose%20%E2%86%92%20restart%20containers.%20This%20ensures%20your%20containers%20get%20the%20updated%20environment.%20Don%E2%80%99t%20edit%20docker-compose.yaml%20directly%2C%20since%20it%20gets%20overwritten%20by%20the%20script.%0A%0AIf%20you%20ever%20change%20environment%20variables%2C%20just%20remember%20to%20regenerate%20and%20restart.%20Let%20me%20know%20if%20you%20want%20a%20quick%20checklist%20or%20have%20any%20other%20config%20questions%21)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Author
Owner

@WilliamChen-luckbob commented on GitHub (Jan 29, 2026):

@dosu
My PR has passed the automated review with no further updates. Could you advise if any action is required from my side, or if the plugin should be submitted to dify-plugin rather than dify-official-plugin?
#2469

@WilliamChen-luckbob commented on GitHub (Jan 29, 2026): @dosu My PR has passed the automated review with no further updates. Could you advise if any action is required from my side, or if the plugin should be submitted to dify-plugin rather than dify-official-plugin? #2469
Author
Owner

@dosubot[bot] commented on GitHub (Jan 29, 2026):

If your PR has passed automated review and there are no maintainer comments requesting changes, you don't need to take further action right now—just wait for human review and approval, which can take some time.

For plugin submissions: if your manifest.yaml author is set to "langgenius", your plugin is eligible for dify-official-plugins. If you want the author to be your own name, company, or brand, you should submit to the community plugins repo (dify-plugins) instead. Official plugins require the author to be "langgenius" to be accepted in dify-official-plugins (source).

If your PR meets the author requirement and only contains one plugin, you’re in the right place and just need to wait for maintainer review. If you want your own author name or brand, submit to dify-plugins instead.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Jan 29, 2026): <!-- Answer --> If your PR has passed automated review and there are no maintainer comments requesting changes, you don't need to take further action right now—just wait for human review and approval, which can take some time. For plugin submissions: if your manifest.yaml author is set to "langgenius", your plugin is eligible for dify-official-plugins. If you want the author to be your own name, company, or brand, you should submit to the community plugins repo (dify-plugins) instead. Official plugins require the author to be "langgenius" to be accepted in dify-official-plugins ([source](https://github.com/langgenius/dify-official-plugins/pull/2133)). If your PR meets the author requirement and only contains one plugin, you’re in the right place and just need to wait for maintainer review. If you want your own author name or brand, submit to dify-plugins instead. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/1bfdd9c2-890b-4c25-a941-e4ef52e51d3e?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/1bfdd9c2-890b-4c25-a941-e4ef52e51d3e?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/1bfdd9c2-890b-4c25-a941-e4ef52e51d3e?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/1bfdd9c2-890b-4c25-a941-e4ef52e51d3e?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/1bfdd9c2-890b-4c25-a941-e4ef52e51d3e?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/1bfdd9c2-890b-4c25-a941-e4ef52e51d3e?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/1bfdd9c2-890b-4c25-a941-e4ef52e51d3e?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=If%20your%20PR%20has%20passed%20automated%20review%20and%20there%20are%20no%20maintainer%20comments%20requesting%20changes%2C%20you%20don%27t%20need%20to%20take%20further%20action%20right%20now%E2%80%94just%20wait%20for%20human%20review%20and%20approval%2C%20which%20can%20take%20some%20time.%20%0A%0AFor%20plugin%20submissions%3A%20if%20your%20manifest.yaml%20author%20is%20set%20to%20%22langgenius%22%2C%20your%20plugin%20is%20eligible%20for%20dify-official-plugins.%20If%20you%20want%20the%20author%20to%20be%20your%20own%20name%2C%20company%2C%20or%20brand%2C%20you%20should%20submit%20to%20the%20community%20plugins%20repo%20%28dify-plugins%29%20instead.%20Official%20plugins%20require%20the%20author%20to%20be%20%22langgenius%22%20to%20be%20accepted%20in%20dify-official-plugins%C2%A0%28%5Bsource%5D%28https%3A//github.com/langgenius/dify-official-plugins/pull/2133%29%29.%0A%0AIf%20your%20PR%20meets%20the%20author%20requirement%20and%20only%20contains%20one%20plugin%2C%20you%E2%80%99re%20in%20the%20right%20place%20and%20just%20need%20to%20wait%20for%20maintainer%20review.%20If%20you%20want%20your%20own%20author%20name%20or%20brand%2C%20submit%20to%20dify-plugins%20instead.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-official-plugins/issues/2361)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify-official-plugins#913