Skip to content

fix: prevent temp file leak in document extractor on API failure#33246

Open
alvinttang wants to merge 1 commit intolanggenius:mainfrom
alvinttang:fix/document-extractor-temp-file-leak
Open

fix: prevent temp file leak in document extractor on API failure#33246
alvinttang wants to merge 1 commit intolanggenius:mainfrom
alvinttang:fix/document-extractor-temp-file-leak

Conversation

@alvinttang
Copy link
Copy Markdown

Summary

  • Fix temporary file leak in document extractor node when partition_via_api() raises an exception
  • Affects DOC, PPT, PPTX, and EPUB extraction via Unstructured API

Root Cause

The _extract_text_from_doc, _extract_text_from_ppt, _extract_text_from_pptx, and _extract_text_from_epub functions in api/dify_graph/nodes/document_extractor/node.py create temporary files with delete=False but only call os.unlink() on the success path.

If partition_via_api() raises an exception (e.g., network timeout, API error, malformed response), execution jumps to the outer except block and the os.unlink() call is skipped. The temporary file is never cleaned up, leaking disk space.

In long-running Dify deployments that process many documents, this can accumulate significant orphaned temp files in the system's temp directory.

Fix

Move os.unlink() into a finally block so the temporary file is always removed regardless of whether the API call succeeds or fails.

Before:

with tempfile.NamedTemporaryFile(suffix=".doc", delete=False) as temp_file:
    temp_file.write(file_content)
    temp_file.flush()
    with open(temp_file.name, "rb") as file:
        elements = partition_via_api(...)
    os.unlink(temp_file.name)  # skipped if partition_via_api raises

After:

with tempfile.NamedTemporaryFile(suffix=".doc", delete=False) as temp_file:
    temp_file.write(file_content)
    temp_file.flush()
try:
    with open(temp_file.name, "rb") as file:
        elements = partition_via_api(...)
finally:
    os.unlink(temp_file.name)  # always runs

Test plan

  • Verify DOC/PPT/PPTX/EPUB extraction still works normally when Unstructured API succeeds
  • Verify temp files are cleaned up when Unstructured API returns an error
  • Check no temp file accumulation in /tmp after processing documents with API failures

🤖 Generated with Claude Code

…api fails

The _extract_text_from_doc, _extract_text_from_ppt, _extract_text_from_pptx,
and _extract_text_from_epub functions create temporary files with delete=False
but only call os.unlink() on the success path. If partition_via_api() or the
file open raises an exception, the temporary file is never cleaned up, causing
a resource leak on disk.

Move os.unlink() into a finally block so the temp file is always removed
regardless of whether the API call succeeds or fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dosubot dosubot bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Mar 11, 2026
@github-actions github-actions bot added the needs revision for anti-slop label Mar 11, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical issue in the document extractor node where temporary files were not properly cleaned up when the Unstructured API encountered an error. By refactoring the file deletion logic into finally blocks, the system now ensures that all temporary files are removed, preventing disk space accumulation and improving system stability.

Highlights

  • Temporary File Leak Fix: Addressed a bug where temporary files were not deleted if partition_via_api() failed during document extraction for DOC, PPT, PPTX, and EPUB formats, preventing disk space accumulation.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • api/dify_graph/nodes/document_extractor/node.py
    • Refactored temporary file cleanup into try...finally blocks within _extract_text_from_doc, _extract_text_from_ppt, _extract_text_from_pptx, and _extract_text_from_epub functions to ensure files are always unlinked.
Activity
  • The pull request was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a critical temporary file leak issue that occurs when API calls fail during document extraction. This fix is crucial as the temporary file leak could have led to a Denial of Service. My review suggests simplifying the temporary file handling by leveraging Python's tempfile context manager more idiomatically. This approach would make the code cleaner, more robust, and consistently apply the improved pattern across all affected document types (DOC, PPT, PPTX, and EPUB).

Comment on lines 377 to 390
with tempfile.NamedTemporaryFile(suffix=".doc", delete=False) as temp_file:
temp_file.write(file_content)
temp_file.flush()
try:
with open(temp_file.name, "rb") as file:
elements = partition_via_api(
file=file,
metadata_filename=temp_file.name,
api_url=unstructured_api_config.api_url,
api_key=api_key,
)
finally:
os.unlink(temp_file.name)
return "\n".join([getattr(element, "text", "") for element in elements])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the try...finally block correctly fixes the temporary file leak, this can be simplified by using the tempfile.NamedTemporaryFile context manager with its default behavior (delete=True). This avoids manual file deletion and makes the code more concise and idiomatic.

By using the with statement for the temporary file to encompass the API call, the file will be automatically closed and deleted upon exiting the block, whether it's successful or an exception occurs.

        with tempfile.NamedTemporaryFile(suffix=".doc") as temp_file:
            temp_file.write(file_content)
            temp_file.flush()
            temp_file.seek(0)
            elements = partition_via_api(
                file=temp_file,
                metadata_filename=temp_file.name,
                api_url=unstructured_api_config.api_url,
                api_key=api_key,
            )
        return "\n".join([getattr(element, "text", "") for element in elements])

Comment on lines 626 to 638
with tempfile.NamedTemporaryFile(suffix=".ppt", delete=False) as temp_file:
temp_file.write(file_content)
temp_file.flush()
try:
with open(temp_file.name, "rb") as file:
elements = partition_via_api(
file=file,
metadata_filename=temp_file.name,
api_url=unstructured_api_config.api_url,
api_key=api_key,
)
finally:
os.unlink(temp_file.name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the try...finally block correctly fixes the temporary file leak, this can be simplified by using the tempfile.NamedTemporaryFile context manager with its default behavior (delete=True). This avoids manual file deletion and makes the code more concise and idiomatic.

By using the with statement for the temporary file to encompass the API call, the file will be automatically closed and deleted upon exiting the block, whether it's successful or an exception occurs.

            with tempfile.NamedTemporaryFile(suffix=".ppt") as temp_file:
                temp_file.write(file_content)
                temp_file.flush()
                temp_file.seek(0)
                elements = partition_via_api(
                    file=temp_file,
                    metadata_filename=temp_file.name,
                    api_url=unstructured_api_config.api_url,
                    api_key=api_key,
                )

Comment on lines 656 to 668
with tempfile.NamedTemporaryFile(suffix=".pptx", delete=False) as temp_file:
temp_file.write(file_content)
temp_file.flush()
try:
with open(temp_file.name, "rb") as file:
elements = partition_via_api(
file=file,
metadata_filename=temp_file.name,
api_url=unstructured_api_config.api_url,
api_key=api_key,
)
finally:
os.unlink(temp_file.name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the try...finally block correctly fixes the temporary file leak, this can be simplified by using the tempfile.NamedTemporaryFile context manager with its default behavior (delete=True). This avoids manual file deletion and makes the code more concise and idiomatic.

By using the with statement for the temporary file to encompass the API call, the file will be automatically closed and deleted upon exiting the block, whether it's successful or an exception occurs.

            with tempfile.NamedTemporaryFile(suffix=".pptx") as temp_file:
                temp_file.write(file_content)
                temp_file.flush()
                temp_file.seek(0)
                elements = partition_via_api(
                    file=temp_file,
                    metadata_filename=temp_file.name,
                    api_url=unstructured_api_config.api_url,
                    api_key=api_key,
                )

Comment on lines 685 to 697
with tempfile.NamedTemporaryFile(suffix=".epub", delete=False) as temp_file:
temp_file.write(file_content)
temp_file.flush()
try:
with open(temp_file.name, "rb") as file:
elements = partition_via_api(
file=file,
metadata_filename=temp_file.name,
api_url=unstructured_api_config.api_url,
api_key=api_key,
)
finally:
os.unlink(temp_file.name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the try...finally block correctly fixes the temporary file leak, this can be simplified by using the tempfile.NamedTemporaryFile context manager with its default behavior (delete=True). This avoids manual file deletion and makes the code more concise and idiomatic.

By using the with statement for the temporary file to encompass the API call, the file will be automatically closed and deleted upon exiting the block, whether it's successful or an exception occurs.

            with tempfile.NamedTemporaryFile(suffix=".epub") as temp_file:
                temp_file.write(file_content)
                temp_file.flush()
                temp_file.seek(0)
                elements = partition_via_api(
                    file=temp_file,
                    metadata_filename=temp_file.name,
                    api_url=unstructured_api_config.api_url,
                    api_key=api_key,
                )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs revision for anti-slop size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant