Python tempfile module in practice: safer, cleaner scratch space

When I started building data-heavy scripts, my temp files were a mess: half-finished CSVs, intermediate JSON dumps, and “just for debugging” binaries that never went away. The program ran, but the filesystem slowly filled with junk. If you’ve ever had to clean out a temp directory after a long data-processing run, you already know the pain. Temporary files exist for exactly that scenario: you need a file for a short window, and you want it gone when the window closes.

The tempfile module is Python’s built-in answer. It creates files and directories that clean up after themselves, keeps names unguessable, and gives you a solid API for passing temporary data around without leaving clutter behind. In this post I’ll show you how I use tempfile in 2026-era workflows, how to avoid cross-platform surprises, and how to choose the right class or function for your case. You’ll see runnable examples, practical patterns, and a few edge cases that can bite you if you don’t plan for them.

Why temporary files exist (and why you should care)

I treat temp files like short-lived scratch space. Sometimes the data is too big for memory, sometimes a library only accepts file paths, and sometimes I need an intermediate artifact for another process. A temp file is like writing on a whiteboard instead of carving into stone: you keep the structure you need, but you don’t want it to survive longer than the session.

On modern systems, temp files also help with security. You can avoid predictable names, keep your data private, and reduce the risk of collisions when multiple processes run the same code. That matters a lot when you run jobs in parallel or in shared environments such as CI agents, containers, or serverless runtimes.

Here’s the rule of thumb I follow:

  • Use tempfile when the data is intermediate, private, or short-lived.
  • Use a regular file when the data should be kept after your process finishes.

If you keep those two points in mind, most API choices become straightforward.

How TemporaryFile works (and when I reach for it)

TemporaryFile() gives you an unnamed file object that disappears as soon as you close it. The exact behavior varies a bit across platforms, but the key idea is constant: you get a file handle without a public name, and when it closes, the file is removed.

I use TemporaryFile when I only need a file inside a single process. It’s perfect for buffering large data without risking name collisions.

import tempfile

Create a binary temp file (default is w+b)

temp = tempfile.TemporaryFile()

try:

temp.write(b"chunk-1\\n")

temp.write(b"chunk-2\\n")

# Rewind to read back

temp.seek(0)

print(temp.read())

finally:

temp.close()

Because the file is not meant to be shared, I don’t use it when a child process needs the path. In that case, I switch to NamedTemporaryFile().

Text mode with TemporaryFile

By default, TemporaryFile() opens in binary mode (w+b). If you want text, you can pass mode="w+" and encoding=... like you would with open().

import tempfile

with tempfile.TemporaryFile(mode="w+", encoding="utf-8") as temp:

temp.write("Hello, temp world!\\n")

temp.seek(0)

print(temp.read())

I still use seek(0) here, because the file cursor is at the end after writing. This is a common mistake: write, forget to rewind, and your read returns an empty string.

NamedTemporaryFile: when paths matter

NamedTemporaryFile() is for the moments when a path is required. Some libraries, CLIs, or external processes only accept file paths. NamedTemporaryFile gives you a real filename on disk, but you still get automatic cleanup.

import tempfile

with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8") as temp:

temp.write("export,ready\\n")

temp.seek(0)

print("Temp path:", temp.name)

print(temp.read())

On most Unix-like systems, the file can be opened by another process while the handle is still open. On Windows, the default behavior is stricter. If you want another process to open the file on Windows, you usually set delete=False and manage cleanup yourself.

Here’s a safe pattern that works across platforms:

import tempfile

import os

Create a named temp file that persists after closing

with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8", delete=False) as temp:

temp.write("data for another process\\n")

temp_path = temp.name

Do work with the path (e.g., pass to a CLI tool)

print("Use this path:", temp_path)

Manual cleanup

os.remove(temp_path)

I use this pattern when I call tools like ffmpeg, pandoc, or an external compiler that expects a file path and will open it on its own.

Prefixes, suffixes, and naming your temp files

Sometimes I want to identify temp files created by a given app, especially when debugging. NamedTemporaryFile supports prefix and suffix so you can leave hints without giving away predictable full names.

import tempfile

with tempfile.NamedTemporaryFile(prefix="report_", suffix=".csv", mode="w+") as temp:

temp.write("id,value\\n")

print("Temp file:", temp.name)

This is a great way to make temp artifacts recognizable in log output while still keeping them unique.

Temporary directories: the hidden superpower

A single temp file is often not enough. If you run a build step, a conversion pipeline, or a multi-stage data import, you might need a dedicated workspace. That’s where TemporaryDirectory() shines.

import tempfile

import pathlib

with tempfile.TemporaryDirectory(prefix="build") as tmpdir:

tmppath = pathlib.Path(tmpdir)

(tmppath / "input.txt").writetext("source data", encoding="utf-8")

(tmppath / "output.txt").writetext("processed data", encoding="utf-8")

print("Workspace:", tmp_path)

The directory and contents are removed here

I prefer temp directories when multiple files are involved or when I want a clean sandbox that a tool can write into.

Data safety and security considerations

Temporary files often store sensitive data, even if only for a short time. I’ve handled export files containing customer data, API keys embedded in config, and decrypted archives. Here’s how I keep temp data safer:

  • Keep file permissions tight (the default is already secure on most platforms).
  • Avoid writing secrets to temp files if an in-memory buffer is enough.
  • Remove files as soon as possible, not just at program exit.
  • Use a private temp directory when I can, especially in shared environments.

When you need more control, you can create a custom temp directory with restricted permissions and then set dir=... on NamedTemporaryFile or TemporaryDirectory.

import tempfile

import os

import stat

Create a private temp directory

basedir = tempfile.mkdtemp(prefix="private")

os.chmod(basedir, stat.SIRWXU) # Owner read/write/execute only

Create a temp file inside it

with tempfile.NamedTemporaryFile(dir=base_dir, delete=False) as temp:

temp.write(b"sensitive data")

temp_path = temp.name

Cleanup

os.remove(temp_path)

os.rmdir(base_dir)

I only do this when I have a clear need, because it adds manual cleanup steps, but it’s useful for high-risk workflows.

Common mistakes I see (and how I avoid them)

Here are the errors I see most often with tempfile, and how I prevent them in my own code.

1) Forgetting to rewind

After writing, the file pointer sits at the end. If you read immediately without seek(0), you’ll get an empty result.

Fix:

temp.write(b"hello")

temp.seek(0)

print(temp.read())

2) Using NamedTemporaryFile with another process on Windows

On Windows, the default delete=True can prevent other processes from opening the file. Use delete=False and clean up manually.

Fix:

with tempfile.NamedTemporaryFile(delete=False) as temp:

temp_path = temp.name

pass temp_path to another process

os.remove(temp_path)

3) Relying on the temp directory for permanence

The system temp directory can be cleared at any time, especially in containers or managed environments. Never rely on it for long-term storage.

Fix: If you need persistence, use a normal project directory or a data store.

4) Using temp files for tiny data

For small data that fits easily in memory, I prefer io.BytesIO or io.StringIO. It avoids filesystem I/O entirely.

Fix:

from io import BytesIO

buf = BytesIO()

buf.write(b"small payload")

buf.seek(0)

print(buf.read())

5) Leaving delete=False files around

I keep a strict cleanup rule: if I set delete=False, I delete in a finally block or after the external process completes.

Fix:

try:

# work with temp_path

pass

finally:

if os.path.exists(temp_path):

os.remove(temp_path)

When to use temporary files vs in-memory buffers

I make this choice all the time. Here’s the decision table I use in practice.

Scenario

Best Choice

Why —

— Small data, single process

BytesIO / StringIO

No disk I/O, simple cleanup Large data, single process

TemporaryFile

Avoid memory pressure Needs a file path

NamedTemporaryFile

External tools can open it Multiple related files

TemporaryDirectory

Isolated workspace Multi-process on Windows

NamedTemporaryFile(delete=False)

Avoid locked file issues

I’m strict about this: if I don’t need a filename, I don’t create one.

Performance expectations in real workloads

Temp files are usually “fast enough,” but they are still disk I/O. On modern SSDs, I see small temp file writes complete in the 1–5ms range for typical payloads, while larger files can scale into tens of milliseconds or more. In containerized environments, the temp directory can be backed by a memory filesystem, which makes temp files fast but volatile.

I keep these points in mind:

  • For tiny data, in-memory buffers usually beat temp files.
  • For large data, a temp file avoids memory spikes and garbage collector pauses.
  • Temp files are often good enough for pipeline steps, even when performance is tight.

If performance is critical, I measure. The API is the same, so switching between memory and temp files is easy once the code is structured well.

Real-world pattern: safe CSV export pipeline

Here’s a pattern I use when I generate a CSV, run it through a compressor, and then hand it off to another system. I want cleanup and cross-platform safety.

import tempfile

import csv

import os

import gzip

Build a CSV into a temp file

with tempfile.NamedTemporaryFile(mode="w+", newline="", encoding="utf-8", delete=False, suffix=".csv") as temp_csv:

writer = csv.writer(temp_csv)

writer.writerow(["id", "name", "status"])

writer.writerow([101, "Amina", "active"])

writer.writerow([102, "Jules", "inactive"])

csvpath = tempcsv.name

Compress into a second temp file

with tempfile.NamedTemporaryFile(delete=False, suffix=".csv.gz") as temp_gz:

gzpath = tempgz.name

with open(csvpath, "rb") as src, gzip.open(gzpath, "wb") as dst:

dst.write(src.read())

print("Compressed file:", gz_path)

Cleanup

os.remove(csv_path)

os.remove(gz_path)

Notice the explicit cleanup. I use delete=False because I need the paths outside the with blocks, and I always remove them manually.

Real-world pattern: temp workspace for multi-step conversions

This is how I set up a temp directory when I have multiple intermediate files.

import tempfile

from pathlib import Path

with tempfile.TemporaryDirectory(prefix="convert") as tmpdir:

tmp = Path(tmp_dir)

source = tmp / "source.txt"

intermediate = tmp / "intermediate.txt"

final = tmp / "final.txt"

source.write_text("raw data", encoding="utf-8")

intermediate.writetext(source.readtext(encoding="utf-8").upper(), encoding="utf-8")

final.writetext(intermediate.readtext(encoding="utf-8") + "\\nDONE", encoding="utf-8")

print(final.read_text(encoding="utf-8"))

All files removed here

It’s a clean sandbox: no clutter, and the structure is obvious.

What to avoid: misuse and anti-patterns

I’ve seen temp files used for long-term caches or for storing user downloads. That’s a misuse. Temp directories can be cleared by the OS or a container runtime at any time. If the data matters after the program ends, don’t leave it in temp storage.

I also avoid naming temp files with predictable names like temp.txt or output.tmp. That defeats the safety benefits of the module. Let Python generate the unique names for you.

Modern workflows in 2026: AI tools and temp files

When I work with AI-assisted workflows—think local LLMs, code generation pipelines, or dataset preparation—I use temp files as “hand-off points” between steps. For example, a prompt builder might dump a JSON config to a temp file for a CLI inference tool, then clean it up right after the call finishes.

I also use temp directories for reproducible build steps in CI. The best way to keep builds predictable is to isolate intermediate files in a short-lived workspace. It also makes logs easier to read because you can output the temp path when debugging.

The pattern I recommend:

  • Build a temp workspace per job.
  • Keep a single log line with the workspace path when you need to debug.
  • Clean it automatically on success.
  • Allow a “debug mode” that keeps the temp folder around for inspection.

Here’s a simple debug-mode switch:

import tempfile

import os

from pathlib import Path

DEBUG = os.getenv("KEEP_TEMP") == "1"

if DEBUG:

tmpdir = tempfile.mkdtemp(prefix="debug")

print("Keeping temp dir:", tmp_dir)

tmppath = Path(tmpdir)

# cleanup left to the developer

else:

with tempfile.TemporaryDirectory(prefix="job") as tmpdir:

tmppath = Path(tmpdir)

# work inside tmp_path

I’ve found this pattern extremely useful during development without making production workflows messy.

Temporary files vs database staging tables

For data pipelines, a question comes up: should I store intermediate data in temp files or in a staging database table? I choose based on scope and lifetime:

  • If the data only exists inside one process or job run, temp files are simpler.
  • If multiple services need to access the intermediate data, a staging table is better.
  • If I need retries, temp files are easier; just regenerate and rerun.

The main risk with temp files is local storage limits. If you’re processing huge data volumes, a database or object storage layer can be more robust.

Edge cases: cleanup timing and process crashes

Temp files are cleaned up when the file object closes. That’s great unless the process crashes. If the process is killed, the OS doesn’t necessarily clean up all temp artifacts immediately. That’s why I keep temp usage disciplined and avoid leaving huge files behind.

If you run a long-lived service, consider a periodic cleanup job to remove stale entries in your app-specific temp folder. I like to create a unique prefix (like myapp_) so cleanup scripts can target only the files I own and avoid messing with system temp usage.

Here’s a simple age-based cleanup snippet that I use in batch jobs (not on the system temp root):

import os

import time

from pathlib import Path

STALE_SECONDS = 60 60 12 # 12 hours

base = Path("/tmp/myapp")

if base.exists():

now = time.time()

for p in base.iterdir():

try:

if now - p.stat().stmtime > STALESECONDS:

if p.is_dir():

# remove only empty directories to be safe

p.rmdir()

else:

p.unlink()

except OSError:

# ignore files that are in use or race conditions

pass

I keep this narrow on purpose. If I need to delete directories recursively, I use a dedicated workspace under my control and track ownership with a prefix or a marker file.

A quick map of the tempfile API

The tempfile module has more than the three functions most people memorize. Here’s how I mentally organize it:

Tool

Type

Best for —

TemporaryFile()

File object

Single-process, no filename needed NamedTemporaryFile()

File object with name

When another tool needs a path TemporaryDirectory()

Context manager

Multi-file workspace mkstemp()

Low-level fd + path

Custom flags, manual control mkdtemp()

Directory path

Manual temp dirs SpooledTemporaryFile()

In-memory then file

Hybrid memory/disk buffering gettempdir()

Path string

Discover temp directory gettempprefix()

String

Discover default prefix

I reach for the high-level APIs first (TemporaryFile, NamedTemporaryFile, TemporaryDirectory). If I need to control the file descriptor or flags, I drop down to mkstemp.

SpooledTemporaryFile: my favorite hybrid

SpooledTemporaryFile() starts as an in-memory buffer and “spools” to disk once it exceeds a size threshold. It’s great when you don’t know how big the data will get. For small inputs, you stay in memory. For large inputs, it behaves like a temporary file.

import tempfile

Spool in memory up to 1MB, then roll to disk

with tempfile.SpooledTemporaryFile(maxsize=1000_000, mode="w+") as sp:

sp.write("small data")

sp.seek(0)

print(sp.read())

What I like about this is that I can treat it like a file object without prematurely deciding on memory or disk. In parsing pipelines or file upload handlers, it keeps code simple and efficient.

mkstemp and mkdtemp: lower-level control

Sometimes I need lower-level control. mkstemp() returns a file descriptor and a path. That’s useful when I need to pass specific open flags or want to control permissions more tightly than the higher-level wrappers allow.

import tempfile

import os

fd, path = tempfile.mkstemp(prefix="raw_", suffix=".bin")

try:

with os.fdopen(fd, "wb") as f:

f.write(b"binary data")

# use the path here

finally:

os.remove(path)

mkdtemp() similarly returns a directory path. It doesn’t manage cleanup, so you must remove it yourself. I use it when I need to keep the directory around beyond a context manager, or when I’m integrating with legacy code that expects a path string rather than a context manager.

Choosing a temp directory: dir, gettempdir(), and environment variables

By default, tempfile chooses the system temp directory. But you can override it per call using dir=..., or globally by setting environment variables.

If I want all temp artifacts under a specific root, I set TMPDIR (Unix-like) or TEMP/TMP (Windows) at process launch. For app-specific control, I prefer passing dir explicitly so the behavior is clear from the code.

import tempfile

from pathlib import Path

app_tmp = Path("/var/tmp/myapp")

apptmp.mkdir(parents=True, existok=True)

with tempfile.TemporaryDirectory(dir=apptmp, prefix="run") as td:

print("App temp:", td)

When I do this, I also consider cleanup. A custom temp root makes it easier to manage lifecycle, log temp usage, and control permissions.

File permissions, umask, and shared environments

In shared environments, file permissions matter. tempfile creates files with secure default permissions, but your system’s umask can still affect them. I don’t override permissions unless I need to, because the defaults are already geared toward privacy.

If I need to ensure a specific permission set, I create the temp file, then chmod it explicitly. This is common in integrations with tools that require group-read or group-write access.

import tempfile

import os

import stat

with tempfile.NamedTemporaryFile(delete=False) as temp:

os.chmod(temp.name, stat.SIRUSR stat.SIWUSR stat.S_IRGRP)

I don’t do this often. When I do, I include a comment about why those permissions are required, because it’s a security-sensitive change.

Subprocess integration: reliable temp files across platforms

A big reason to use NamedTemporaryFile(delete=False) is subprocess compatibility. This pattern works reliably on Windows and Unix-like systems:

import tempfile

import subprocess

import os

with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".txt") as temp:

temp.write("payload for tool\\n")

temp_path = temp.name

try:

# Example: run a tool that expects a file path

result = subprocess.run(["cat", temppath], captureoutput=True, text=True)

print(result.stdout)

finally:

os.remove(temp_path)

I explicitly close the file before running the subprocess to avoid file locking on Windows. The explicit delete=False keeps it available.

Asynchronous and threaded workflows

Temp files are straightforward in sync code, but async tasks can be trickier because cleanup can happen after the event loop moves on. My rule is: if the temp file crosses task boundaries, keep the path and clean it explicitly.

In threaded code, I’m careful to avoid sharing a single temp file object across threads unless I wrap access with a lock. Temp files are just files; they don’t add concurrency control by themselves.

If you need thread-safe staging, use separate temp files per worker and merge results later.

Using tempfile in tests

For testing, tempfile is gold. I use TemporaryDirectory to isolate fixtures and avoid leaking files into the repo. This keeps tests hermetic and easy to parallelize.

import tempfile

from pathlib import Path

def testpipelinewrites_output():

with tempfile.TemporaryDirectory() as td:

out = Path(td) / "result.txt"

out.write_text("ok", encoding="utf-8")

assert out.read_text(encoding="utf-8") == "ok"

I avoid using the global temp directory directly in tests because it can make cleanup unpredictable in parallel runs. A local temp directory per test keeps things reliable.

Debug-friendly temp workflows

Sometimes you want temp files to persist so you can inspect them after a failure. I usually implement a small “keep temp” toggle so debugging doesn’t require code edits.

import os

import tempfile

from pathlib import Path

KEEP = os.getenv("KEEP_TEMP") == "1"

if KEEP:

tmpdir = tempfile.mkdtemp(prefix="keep")

tmp = Path(tmp_dir)

else:

with tempfile.TemporaryDirectory(prefix="keep") as tmpdir:

tmp = Path(tmp_dir)

# work here

If I’m going to keep temp files, I include them in logs and a cleanup guide in the README or developer docs. Nothing is worse than a “temporary” directory that quietly lives for a year.

Traditional vs modern approaches

Here’s a quick comparison of how I used to handle temp files versus the more deliberate approach I use now.

Old habit

Modern habit

Why it’s better —

— Hardcoded temp.txt in CWD

NamedTemporaryFile()

Avoid collisions and accidental commits Manual cleanup by memory

Context managers

Cleanup is automatic and reliable One global temp folder

Per-job temp directories

Isolation and easier debugging Temp files as caches

Proper cache storage

Avoid eviction surprises

It’s not about being fancy; it’s about reducing surprises.

Practical scenario: image processing pipeline

Here’s a more realistic example where I need multiple temp files and a temp directory because the tools require paths.

import tempfile

from pathlib import Path

import subprocess

import os

with tempfile.TemporaryDirectory(prefix="img_") as td:

tmp = Path(td)

raw = tmp / "raw.png"

resized = tmp / "resized.png"

# Imagine raw already exists or was written by another step

raw.write_bytes(b"fake image bytes")

# Placeholder: call an external image tool

# subprocess.run(["convert", str(raw), "-resize", "200x200", str(resized)], check=True)

# For demo purposes, just copy

resized.writebytes(raw.readbytes())

print("Final image at", resized)

temp directory removed here

The important part is the lifecycle: the files are created inside a temporary directory, used for a short window, and then cleaned up automatically.

Practical scenario: streaming upload with a size cap

For upload handlers, SpooledTemporaryFile is a lifesaver. You can keep small uploads in memory and spill large ones to disk without changing your API.

import tempfile

def handle_upload(chunks):

with tempfile.SpooledTemporaryFile(maxsize=2000_000, mode="w+b") as buf:

for chunk in chunks:

buf.write(chunk)

buf.seek(0)

# process the file-like object

data = buf.read()

return len(data)

I like this because it keeps memory usage under control without forcing everything onto disk.

Practical scenario: config handoff to a CLI tool

Sometimes the tool you’re calling expects a config file, not a JSON string. Here’s how I handle that without leaking files:

import tempfile

import json

import os

import subprocess

config = {"model": "xlarge", "batch": 8}

with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".json") as temp:

json.dump(config, temp)

temp_path = temp.name

try:

# subprocess.run(["tool", "--config", temp_path], check=True)

print("Would run tool with", temp_path)

finally:

os.remove(temp_path)

This is the sort of workflow that becomes safe and repeatable once you commit to tempfile.

Subtle edge cases that matter

Here are some edge cases that aren’t obvious until they bite you:

1) Encoding mismatch in text mode

If you open a temp file in text mode with an encoding but another process expects a different encoding, you can end up with garbage. I keep encodings explicit and consistent, usually utf-8.

2) Newlines in CSV files on Windows

When writing CSVs in text mode, use newline="" so the csv module can manage newlines correctly across platforms.

3) Temp directory on a noexec mount

Some systems mount /tmp with noexec, which can break tools that expect to execute files placed there. If your workflow needs to run temp binaries, choose a different directory with dir=....

4) File descriptor leakage

With mkstemp, you own the file descriptor. If you forget to close it, you can exhaust file descriptors in long-running processes. Always wrap it in os.fdopen or close it explicitly.

5) Cleanup order in multi-step pipelines

If you chain temp files, make sure you don’t delete the source before the consumer has finished reading. This can happen when you mix async tasks with context managers. I prefer manual cleanup in those cases.

Building a safe temp strategy for production

When I move from scripts to production services, I formalize temp usage:

  • I choose a temp root that’s specific to the service.
  • I enforce a maximum size per job (and handle overages gracefully).
  • I log the temp workspace path when I need to debug.
  • I add a cleanup job for stale directories.
  • I use environment variables to switch between “keep” and “clean” modes.

This makes temp usage predictable and reduces operational surprises.

A reusable helper: context-managed temp path

Sometimes I want a temp path that always cleans up, but I need it to work across Unix and Windows. I use a small helper that prefers NamedTemporaryFile but closes it immediately so the path is reusable.

import tempfile

import os

from contextlib import contextmanager

@contextmanager

def temppath(suffix="", prefix="tmp"):

fd, path = tempfile.mkstemp(suffix=suffix, prefix=prefix)

os.close(fd)

try:

yield path

finally:

if os.path.exists(path):

os.remove(path)

This gives me a simple, safe interface that works well with subprocesses and tools that need to create the file themselves.

Monitoring temp usage in long-running services

For services that run 24/7, temp storage can be a hidden risk. If jobs can create large temp files, I track temp usage per request or per job ID. A minimal approach is to log the temp directory and size at the end of each job. A more robust approach is to add a simple quota check before creating new temp files.

I keep it lightweight: total size per job, number of files, and a cleanup event count. That’s usually enough to catch leaks.

When not to use tempfile

Here are cases where I avoid tempfile entirely:

  • The output is a user-facing artifact that should persist.
  • I need durable caching across restarts.
  • The data needs to be shared across multiple machines.
  • The file must survive OS reboots.

In those cases, I choose a proper storage location: a project directory, a database, or object storage.

Final checklist I keep in my head

Before I write temp code, I run through this quick mental checklist:

  • Do I really need a file path, or can I use an in-memory buffer?
  • Will another process open the file? If yes, am I on Windows?
  • Do I need a directory or just a file?
  • How and when will cleanup happen if the process crashes?
  • Is there any sensitive data that should avoid temp storage?

If I can answer those five questions, the implementation is usually smooth.

Wrap-up: a disciplined way to avoid filesystem clutter

The tempfile module makes temporary files boring, and that’s a good thing. It gives you safe defaults, helps you avoid name collisions, and makes cleanup predictable. The real value is that it nudges you into a cleaner workflow: temporary data stays temporary, and your long-lived artifacts are stored intentionally.

My advice is simple: default to tempfile, choose the smallest surface area you need (file vs directory), and be explicit when you must keep paths around. If you do that, you’ll avoid the most common pitfalls and your systems will stay lean, secure, and easier to debug.

Scroll to Top