Skip to content

Build1 publisher3 min readPublished

The duplicate def that ate the trim, and the lint rule nobody was running

A 16,000-line Flask app shipped a GIF trimmer that accepted start and end times and threw them away. Python does not warn about redefinition. Pylint E0102 does, for free.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened

  • snipforge.video is an AI video editing SaaS run by a solo founder whose day job is Lead QE.
  • The application is a single-file Flask app of roughly 16,000 lines, deployed on Railway, with FFmpeg doing the media processing and Cloudflare R2 storing media.
  • The GIF tool lets users pick a start and end time so they can turn a short moment into a GIF instead of converting the whole video.
  • Users reported that clip selection did nothing: picking seconds 12 to 17 of a three minute video produced a GIF of the entire three minutes, giving wrong content, a bloated file, and a job that took far longer than it should.
  • The route handler read start and end from the request and passed them into the worker, launching convert_gif on a thread with fps, width, start and end arguments.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

A solo developer who runs snipforge.video, an AI video editing SaaS, spent a stretch of production time serving GIFs that ignored the clip range users asked for: pick seconds 12 to 17 of a three minute video and you got a GIF of the whole three minutes, with the wrong content, a bloated file, and a job that ran far longer than it should [3][4]. The cause was a second definition of `convert_gif` about 200 lines below the first in a single-file Flask app of roughly 16,000 lines, silently rebinding the name to an older implementation whose signature accepted `start` and `end` but whose body never referenced them [2][8].

That is a 36x overshoot on output duration for the reported case [1]. Everything upstream of the failure looked correct. The route handler read the start and end values from the request and passed them into the worker thread [5]. The surviving signature in the file was `def convert_gif(jid, src, dst, fps=10, width=480, start=0, end=0)` [6]. There were no errors, no exceptions, and no log lines [7]. Both definitions had identical signatures, and, as the author notes, nothing in the runtime behaviour distinguishes trim ignored from trim never requested [9].

This is why the class of defect matters more than the instance. Code review operates on diffs, and a diff of the correct implementation is correct; the shadowing copy sits 200 lines away, about 1.25 percent of the file [2], in a module large enough that nobody is holding both halves in working memory. Tests do not help either unless they assert on output duration, and the author had no FFmpeg test harness because unit testing FFmpeg pipelines is painful [12]. The broken version parses cleanly and runs happily [13]. Static typing does not catch it. The parameters are accepted, then discarded.

Python's module-level name binding is last-write-wins, and no diagnostic is emitted [8]. That behaviour is fine when a human writes a file top to bottom over months. It is a liability when code lands in bulk. The source says nothing about how this particular file was written [3], so treat the AI angle as mechanism rather than accusation: an assistant asked to add trim support to a function tends to emit a whole replacement function body, and if it is pasted rather than substituted, you have two defs and a green test run. The faster a file grows relative to how often it is read end to end, the higher the odds.

The remediation is unglamorous and cheap. The author deleted the duplicate, kept the complete implementation that trims via FFmpeg before the palette pass and cleans up temp files, and left a guard comment where the duplicate lived [10]. He declined the tempting fix of renaming one and keeping both, on the grounds that keeping things just in case is how the file reached 16,000 lines [11]. Verification was structural: an AST walk asserting exactly one `convert_gif` definition remains and that its body actually references the trim parameters [12]. That has since been generalised into a lint step failing on any duplicate top-level def [14]. Pylint's `function-redefined` (E0102) catches the same class for free, and he was not running it [15].

Two things to watch. First, whether the lint runs in CI or only on the author's machine; the source describes a lint step, not a pipeline gate [14]. Second, the limitation he knowingly kept: the trim uses `-ss` before `-i` with `-c copy`, so seeking is keyframe-bound and, depending on GOP size, the clip can start up to a couple of seconds early [16]. Accepted for GIFs today, that is the shape of next quarter's support ticket.

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories