<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gaurav Batra</title>
    <description>The latest articles on DEV Community by Gaurav Batra (@batra79).</description>
    <link>https://dev.to/batra79</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4139232%2F4f17b615-bb40-4244-88a1-4cdc81ba7ce5.png</url>
      <title>DEV Community: Gaurav Batra</title>
      <link>https://dev.to/batra79</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/batra79"/>
    <language>en</language>
    <item>
      <title>VHS 0.12.0 exits successfully but creates no GIF or video: a canceled context is why</title>
      <dc:creator>Gaurav Batra</dc:creator>
      <pubDate>Wed, 23 Sep 2026 10:40:54 +0000</pubDate>
      <link>https://dev.to/batra79/vhs-0120-exits-successfully-but-creates-no-gif-or-video-a-canceled-context-is-why-33hh</link>
      <guid>https://dev.to/batra79/vhs-0120-exits-successfully-but-creates-no-gif-or-video-a-canceled-context-is-why-33hh</guid>
      <description>&lt;p&gt;Last week I set out to learn demo-video production, starting with the obvious tool for a CLI developer: VHS. My very first render printed &lt;code&gt;Creating v1.mp4...&lt;/code&gt; and exited 0. There was no MP4. My first demo video was going great.&lt;/p&gt;

&lt;p&gt;The tape's commands had executed. Repeating the run with GIF and WebM outputs produced the same result. &lt;code&gt;Output frames/&lt;/code&gt; worked, which narrowed the problem to encoding rather than terminal capture.&lt;/p&gt;

&lt;p&gt;This affects VHS &lt;strong&gt;v0.12.0&lt;/strong&gt;, the latest release as of September 23, 2026. It is tracked upstream in &lt;a href="https://github.com/charmbracelet/vhs/issues/787" rel="noopener noreferrer"&gt;issue #787&lt;/a&gt;. The issue has reproductions on Linux, macOS, and Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small reproduction
&lt;/h2&gt;

&lt;p&gt;With v0.12.0 installed, try a tape like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output demo.mp4
Set Width 600
Set Height 300
Type "echo hello"
Enter
Sleep 2s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On affected builds, &lt;code&gt;vhs demo.tape&lt;/code&gt; reaches the &lt;code&gt;Creating demo.mp4...&lt;/code&gt; message and exits 0, but &lt;code&gt;demo.mp4&lt;/code&gt; is absent. Switching the output to &lt;code&gt;.gif&lt;/code&gt; or &lt;code&gt;.webm&lt;/code&gt; does not help. A frames-only output can still be written.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens in the code
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://github.com/charmbracelet/vhs/blob/v0.12.0/evaluator.go#L116" rel="noopener noreferrer"&gt;&lt;code&gt;evaluator.go&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;Evaluate&lt;/code&gt; creates a context for recording and assigns it back to &lt;code&gt;ctx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the end of the tape, &lt;code&gt;teardown()&lt;/code&gt; calls &lt;code&gt;cancel()&lt;/code&gt; to stop recording. The next call is &lt;a href="https://github.com/charmbracelet/vhs/blob/v0.12.0/evaluator.go#L186-L187" rel="noopener noreferrer"&gt;&lt;code&gt;v.Render(ctx)&lt;/code&gt;&lt;/a&gt;. That is the same context, now canceled.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Render&lt;/code&gt; builds the GIF, MP4, and WebM commands using that context. Go's &lt;code&gt;exec.CommandContext&lt;/code&gt; will not start ffmpeg with an already canceled context. Then &lt;a href="https://github.com/charmbracelet/vhs/blob/v0.12.0/vhs.go#L564-L574" rel="noopener noreferrer"&gt;&lt;code&gt;Render&lt;/code&gt; logs the command output but returns nil&lt;/a&gt;. When the process never starts, that output is empty, so VHS can print a blank line and report success without creating a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked for me
&lt;/h2&gt;

&lt;p&gt;I built VHS from the v0.12.0 source with a small local change: use a separate &lt;code&gt;recordingCtx&lt;/code&gt; for &lt;code&gt;Record&lt;/code&gt;, cancel only that context during teardown, and pass the still-live parent &lt;code&gt;ctx&lt;/code&gt; to &lt;code&gt;Render&lt;/code&gt;. The same tape then produced a valid MP4. The frames-only result before the patch helped confirm that recording itself was healthy.&lt;/p&gt;

&lt;p&gt;The relevant change is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; // Begin recording frames as we are now in a recording state.
&lt;span class="gd"&gt;-ctx, cancel := context.WithCancel(ctx)
-ch := v.Record(ctx)
&lt;/span&gt;&lt;span class="gi"&gt;+recordingCtx, cancel := context.WithCancel(ctx)
+ch := v.Record(recordingCtx)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, &lt;code&gt;teardown()&lt;/code&gt; cancels only &lt;code&gt;recordingCtx&lt;/code&gt;, while &lt;code&gt;Render&lt;/code&gt; receives the live parent &lt;code&gt;ctx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'm not proposing this as &lt;em&gt;the&lt;/em&gt; fix — that conversation is happening in &lt;a href="https://github.com/charmbracelet/vhs/pull/788" rel="noopener noreferrer"&gt;#788–791&lt;/a&gt;. This is just what unblocked me.&lt;/p&gt;

&lt;p&gt;If you need an unpatched release today, &lt;a href="https://github.com/charmbracelet/vhs/releases/tag/v0.11.0" rel="noopener noreferrer"&gt;v0.11.0&lt;/a&gt; is the practical rollback reported in the upstream issue. Check which executable your shell actually runs with &lt;code&gt;command -v vhs&lt;/code&gt; and &lt;code&gt;vhs --version&lt;/code&gt;; an older binary on disk will not help if a v0.12.0 installation appears earlier on &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upstream status
&lt;/h2&gt;

&lt;p&gt;The bug is documented in &lt;a href="https://github.com/charmbracelet/vhs/issues/787" rel="noopener noreferrer"&gt;#787&lt;/a&gt;, and fix proposals are open in &lt;a href="https://github.com/charmbracelet/vhs/pull/788" rel="noopener noreferrer"&gt;#788&lt;/a&gt;, &lt;a href="https://github.com/charmbracelet/vhs/pull/789" rel="noopener noreferrer"&gt;#789&lt;/a&gt;, &lt;a href="https://github.com/charmbracelet/vhs/pull/790" rel="noopener noreferrer"&gt;#790&lt;/a&gt;, and &lt;a href="https://github.com/charmbracelet/vhs/pull/791" rel="noopener noreferrer"&gt;#791&lt;/a&gt;. The separate recording context addresses the canceled render. Returning encoder errors to the caller would also prevent future failures from looking like successful runs.&lt;/p&gt;

&lt;p&gt;Until a fix is merged and released, treat exit code 0 from v0.12.0 as insufficient proof that an output file was created. The exit code tells you the process thinks it finished, not that the artifact exists. Check the file.&lt;/p&gt;

&lt;p&gt;This was day 1 of learning demo videos in public. &lt;/p&gt;

&lt;p&gt;Next: making the video this bug almost prevented.&lt;/p&gt;

</description>
      <category>go</category>
      <category>cli</category>
      <category>opensource</category>
      <category>terminal</category>
    </item>
  </channel>
</rss>
