Skip to content

fix(api): upload empty files with an explicit zero length - #167

Merged
ethanwharris merged 5 commits into
mainfrom
fix/empty-file-upload-chunked-encoding
Sep 21, 2026
Merged

ethanwharris merged 5 commits into
mainfrom
fix/empty-file-upload-chunked-encoding

Conversation

@codexceed

@codexceed codexceed commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Uploading an empty file to Lightning storage always failed, and failed slowly. Empty __init__.py
files are normal in Python packages, so this hit most projects on their first upload or deploy.

It affects everything that uploads: lightning cp (single file and -r), teamspace and studio
uploads, and deployment export. They all share one uploader.

Motivation

An empty file went out without declaring its size. Storage refuses those. The uploader then read
that refusal as a temporary glitch and kept retrying something that could never work.

flowchart LR
    A[Empty file] --> B[Sent without<br/>declaring a size]
    B --> C[Storage refuses it]
    C --> D[Refusal mistaken for<br/>a passing glitch]
    D --> E[Retried 10 times,<br/>up to 8 minutes]
    E --> F[Fails anyway]
Loading

From the user's side this looked like a hang with no explanation, for an unpredictable number of
minutes, before the upload gave up. A folder upload died on the first empty file it reached.

Changes

Before After
Uploading an empty file Fails, after minutes of stall Succeeds, right away
A refusal that can never work Retried for minutes Fails immediately
  • Empty files now declare a size of zero, which storage accepts.
  • One upload path instead of two, so every file is sent and checked the same way.
  • Refusals that retrying cannot fix are no longer treated as temporary.

Progress bars are unchanged: a folder upload still prints one per file, empty ones included.

Testing

  • New upload tests inspect the request that would actually go over the wire. The old tests only
    checked the arguments handed to a stand-in, which is why they passed on empty files while the
    real thing was broken. Undoing the fix fails them.
  • Review of this PR asked where upload errors surface. They reach a single handler at the top of
    the CLI, which prints an error panel rather than a traceback. That was untested, so this PR also
    covers it: that a failing command reaches the handler, and end to end that it prints the panel
    and still gives a traceback under LIGHTNING_DEBUG=1. Removing the handler previously left the
    suite green while every command printed raw tracebacks; it now fails three tests.
  • Full test suite: 1677 passed. CLI suite: 793 passed. The CLI suite's 10 failures need network
    access and fail the same way on main.
  • Linting, formatting, and type checks are clean.

requests decides a body's length with a truth test, so a zero-byte file is
indistinguishable from a body whose length it cannot determine and falls back
to "Transfer-Encoding: chunked". Presigned storage PUTs reject chunked bodies
with 501, which the uploader treats as transient and retries ten times with
exponential backoff — minutes of apparent hang per empty file, then a failure.

Send the empty body directly so requests takes its no-body path and sets
"Content-Length: 0". Also extract the PUT status handling so both the empty
and streaming paths share it.
Comment thread python/lightning_sdk/api/utils.py
Sarthak Joshi added 3 commits September 18, 2026 17:08
Review follow-ups on the empty-file upload fix:

- Build the request body once and issue one PUT with one status check, instead
  of an early return with its own call site. The progress bar is now created
  for empty files too, which the early return had skipped.
- Stop classifying 501 and 505 as transient. They reject the request itself,
  so retrying only burns the ten-attempt ladder before failing anyway.
- Fold the new tests into one parametrized length assertion, and cover the
  empty body in the existing no-retry test rather than duplicating it.
The CLI turns an unhandled error into a titled panel with a LIGHTNING_DEBUG
hint, but only because the error travels out of Click's main() to the
interpreter's excepthook. Existing tests call the formatter and the hook
directly, so nothing covered that last link, and removing the hook's
installation left the suite green while every command started printing raw
tracebacks.

Add coverage for the link itself: that running a command installs the hook,
that Click lets a plain error escape, and end to end in a real interpreter
that a failing command prints the panel rather than a traceback - and still
prints the traceback under LIGHTNING_DEBUG=1.

The end-to-end driver runs from a temp directory, so it pins PYTHONPATH to the
package pytest imported; otherwise an installed copy shadows the checkout and
the test silently passes against the wrong code.
@codexceed codexceed added the bug Something isn't working label Sep 21, 2026

@dhedey dhedey left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great spot & fix, thanks!

@ethanwharris
ethanwharris merged commit 121f034 into main Sep 21, 2026
28 of 30 checks passed
@ethanwharris
ethanwharris deleted the fix/empty-file-upload-chunked-encoding branch September 21, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants