From ccf38bd9fd592eadd598f25ce84b5c186b848cb7 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Tue, 18 Aug 2026 20:20:39 +0200 Subject: [PATCH] Do not let a response header take the API down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The version header was set from WarpEngine::VERSION_HEADER, a constant introduced in the same commit. The deploy that followed ran these controllers with an older `lib/`, so the before_action raised NameError on every request and every engine endpoint answered 500 — the catalog, the images, the file server and the config extension Woodpecker calls, which is how it surfaced: a pipeline could no longer fetch its own configuration. The controller now spells the header name out. A response header is not worth a dependency that can take the API down when one half of a deploy is older than the other, and the constant remains the documented name with a spec holding the two in step. Confirmed in production mode against the same code that failed: 200 with the header. Co-Authored-By: Claude Opus 5 (1M context) --- app/controllers/warp_engine/api_controller.rb | 8 +++++++- spec/requests/version_header_spec.rb | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/controllers/warp_engine/api_controller.rb b/app/controllers/warp_engine/api_controller.rb index 97577fb..78a93af 100644 --- a/app/controllers/warp_engine/api_controller.rb +++ b/app/controllers/warp_engine/api_controller.rb @@ -31,7 +31,13 @@ module WarpEngine private def set_version_header - response.headers[WarpEngine::VERSION_HEADER] = WarpEngine::VERSION + # The name is spelled out here rather than taken from WarpEngine::VERSION_HEADER on + # purpose. A deployed process can end up with these controllers and an older + # `lib/` — it happened on the first deploy of this feature — and a controller that + # needs a constant from the newer half answers 500 to every request instead of + # serving the catalog. A response header is not worth that fragility. The constant + # is still the documented name, and a spec holds the two together. + response.headers["WarpEngine-Version"] = WarpEngine::VERSION end def resolve_mime(path) diff --git a/spec/requests/version_header_spec.rb b/spec/requests/version_header_spec.rb index a2818d5..f74304c 100644 --- a/spec/requests/version_header_spec.rb +++ b/spec/requests/version_header_spec.rb @@ -3,6 +3,12 @@ require "rails_helper" # Every response the engine serves carries the version that served it, so a client can # branch on the engine's age without asking a separate endpoint for it. RSpec.describe "the WarpEngine-Version header", type: :request do + # The controller writes the name as a literal so that it cannot depend on a constant a + # half-updated deploy might not have. This is what keeps the two in step. + it "is the name the constant documents" do + expect(WarpEngine::VERSION_HEADER).to eq("WarpEngine-Version") + end + it "is on a normal response" do create(:software)