Commit 3cb18d0

2026-09-12 20:43:35
fix: return ok:false instead of raising KeyError on unset ELELEM_SD_* env vars
generate/img2img/video/upscale used bare ENV.fetch, so a missing model path env var crashed the agent loop instead of surfacing the same {ok: false, error: ...} shape every other failure path already uses.
1 parent d253053
Changed files (2)
lib
spec
lib/elelem/diffusion.rb
@@ -71,9 +71,15 @@ module Elelem
 
     def self.video(prompt:, output_dir:, negative_prompt: "", steps: 20, width: 512, height: 512, seed: 42,
                     video_frames: 16, fps: 8, n_threads: nil)
-      diffusion_model = ENV.fetch("ELELEM_SD_VIDEO_MODEL")
-      vae = ENV.fetch("ELELEM_SD_VIDEO_VAE")
-      t5xxl = ENV.fetch("ELELEM_SD_VIDEO_T5XXL")
+      diffusion_model = ENV["ELELEM_SD_VIDEO_MODEL"]
+      return { ok: false, error: "ELELEM_SD_VIDEO_MODEL is not set" } unless diffusion_model
+
+      vae = ENV["ELELEM_SD_VIDEO_VAE"]
+      return { ok: false, error: "ELELEM_SD_VIDEO_VAE is not set" } unless vae
+
+      t5xxl = ENV["ELELEM_SD_VIDEO_T5XXL"]
+      return { ok: false, error: "ELELEM_SD_VIDEO_T5XXL is not set" } unless t5xxl
+
       handle = video_context_for(diffusion_model, vae, t5xxl, n_threads: n_threads)
       return { ok: false, error: "failed to load video model at #{diffusion_model}" } if handle.null?
 
@@ -85,7 +91,9 @@ module Elelem
     end
 
     def self.upscale(input_path:, path:, n_threads: nil)
-      esrgan = ENV.fetch("ELELEM_SD_ESRGAN_MODEL")
+      esrgan = ENV["ELELEM_SD_ESRGAN_MODEL"]
+      return { ok: false, error: "ELELEM_SD_ESRGAN_MODEL is not set" } unless esrgan
+
       ok = MUTEX.synchronize do
         functions[:upscale].call(esrgan, input_path, n_threads || Etc.nprocessors, path)
       end
@@ -93,7 +101,9 @@ module Elelem
     end
 
     def self.with_context(n_threads:)
-      model = ENV.fetch("ELELEM_SD_IMAGE_MODEL")
+      model = ENV["ELELEM_SD_IMAGE_MODEL"]
+      return { ok: false, error: "ELELEM_SD_IMAGE_MODEL is not set" } unless model
+
       handle = context_for(model, n_threads: n_threads)
       return { ok: false, error: "failed to load model at #{model}" } if handle.null?
 
spec/elelem/diffusion_spec.rb
@@ -80,6 +80,14 @@ RSpec.describe Elelem::Diffusion do
 
       expect(close_fn).not_to have_received(:call)
     end
+
+    it "returns ok: false instead of raising when ELELEM_SD_IMAGE_MODEL is unset" do
+      ENV.delete("ELELEM_SD_IMAGE_MODEL")
+
+      result = described_class.generate(prompt: "a cat", path: "/tmp/cat.png")
+
+      expect(result).to eq(ok: false, error: "ELELEM_SD_IMAGE_MODEL is not set")
+    end
   end
 
   describe ".img2img" do
@@ -169,6 +177,30 @@ RSpec.describe Elelem::Diffusion do
       expect(result).to eq(ok: false, error: "failed to load video model at /models/wan.safetensors")
       expect(video_fn).not_to have_received(:call)
     end
+
+    it "returns ok: false instead of raising when ELELEM_SD_VIDEO_MODEL is unset" do
+      ENV.delete("ELELEM_SD_VIDEO_MODEL")
+
+      result = described_class.video(prompt: "a bird flying", output_dir: "/tmp/vid")
+
+      expect(result).to eq(ok: false, error: "ELELEM_SD_VIDEO_MODEL is not set")
+    end
+
+    it "returns ok: false instead of raising when ELELEM_SD_VIDEO_VAE is unset" do
+      ENV.delete("ELELEM_SD_VIDEO_VAE")
+
+      result = described_class.video(prompt: "a bird flying", output_dir: "/tmp/vid")
+
+      expect(result).to eq(ok: false, error: "ELELEM_SD_VIDEO_VAE is not set")
+    end
+
+    it "returns ok: false instead of raising when ELELEM_SD_VIDEO_T5XXL is unset" do
+      ENV.delete("ELELEM_SD_VIDEO_T5XXL")
+
+      result = described_class.video(prompt: "a bird flying", output_dir: "/tmp/vid")
+
+      expect(result).to eq(ok: false, error: "ELELEM_SD_VIDEO_T5XXL is not set")
+    end
   end
 
   describe ".upscale" do
@@ -203,6 +235,14 @@ RSpec.describe Elelem::Diffusion do
 
       expect(result).to eq(ok: false, error: "upscale failed")
     end
+
+    it "returns ok: false instead of raising when ELELEM_SD_ESRGAN_MODEL is unset" do
+      ENV.delete("ELELEM_SD_ESRGAN_MODEL")
+
+      result = described_class.upscale(input_path: "/tmp/in.png", path: "/tmp/out.png")
+
+      expect(result).to eq(ok: false, error: "ELELEM_SD_ESRGAN_MODEL is not set")
+    end
   end
 
   describe ".available?" do