{"record":{"id":"c5ee82361df77b6a","repo":"jdx/mise","slug":"could-not-infer-formula-version-add-an-explicit-v","errorCode":null,"errorMessage":"could not infer formula version; add an explicit version declaration","messagePattern":"could not infer formula version; add an explicit version declaration","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/tap_formula_metadata.rb","lineNumber":220,"sourceCode":"    def test(*) = nil\n    def method_missing(*) = nil\n    def respond_to_missing?(*) = true\n  end\nend\n\ndef inferred_version(url)\n  basename = File.basename(url.to_s).sub(/\\.(tar\\.(gz|xz|bz2|zst)|tgz|txz|zip|gz)\\z/i, \"\")\n  match = basename.match(/(?:^|[-_v])([0-9]+(?:\\.[0-9A-Za-z]+)+(?:[-_.][0-9A-Za-z]+)*)/)\n  match && match[1]\nend\n\neval(STDIN.read.force_encoding(\"UTF-8\"), TOPLEVEL_BINDING, FORMULA_FILE, 1)\nklass = Formula.instance_variable_get(:@subclass)\nraise \"no Formula subclass found\" unless klass\nraise \"formula has no stable URL\" if klass.source_url.to_s.empty?\nraise \"formula has no stable sha256\" if klass.source_sha256.to_s.empty?\nversion = (klass.explicit_version || inferred_version(klass.source_url)).to_s\nraise \"could not infer formula version; add an explicit version declaration\" if version.to_s.empty?\n\nmetadata = {\n  \"name\" => FORMULA_NAME,\n  \"tap\" => ENV.fetch(\"MISE_BREW_TAP\"),\n  \"versions\" => { \"stable\" => version },\n  \"revision\" => klass.revision_value || 0,\n  \"keg_only\" => klass.keg_only_value || false,\n  \"dependencies\" => klass.runtime_dependencies || [],\n  \"build_dependencies\" => klass.build_dependencies || [],\n  \"bottle\" => {},\n  \"urls\" => { \"stable\" => { \"url\" => klass.source_url, \"checksum\" => klass.source_sha256 } },\n  \"ruby_source_path\" => ENV.fetch(\"MISE_BREW_SOURCE_PATH\"),\n  \"ruby_source_checksum\" => { \"sha256\" => ENV.fetch(\"MISE_BREW_SOURCE_CHECKSUM\") },\n  \"tap_git_head\" => ENV.fetch(\"MISE_BREW_TAP_COMMIT\")\n}\nputs JSON.generate(metadata)\n","sourceCodeStart":202,"sourceCodeEnd":237,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/tap_formula_metadata.rb#L202-L237","documentation":"mise extracts the stable version of a formula from an explicit `version ...` declaration, or falls back to heuristically parsing the version out of the source URL basename (inferred_version). If neither yields a version — no explicit version declaration and the URL's filename doesn't match the version regex — this raise fires, instructing the formula author to add an explicit version declaration.","triggerScenarios":"Formula lacks `version \"x.y.z\"` and its url basename has no recognizable version token (regex requires digit-led dotted segments after start or a [-_v] separator); url points at a repo archive like `main.tar.gz`, `latest.zip`, or `releases/current/download/foo.tgz`; url basename has only a single undotted number (e.g. `foo-2.tar.gz`) which fails the multi-segment pattern.","commonSituations":"GitHub `archive/refs/heads/main.tar.gz` or `releases/latest/download/...` URLs used by third-party taps; download URLs with opaque names (`foo-nightly.tgz`, `foo.tar.gz?param=1`); formulas relying on Homebrew's automatic version inference from the url where mise's simpler regex can't; renamed artifacts without version substrings.","solutions":["Add an explicit `version \"1.2.3\"` declaration to the formula's stable spec (most reliable fix)","Use a versioned download URL whose basename contains the dotted version (e.g. foo-1.2.3.tar.gz)","Point the url at a stable versioned release asset instead of latest/main archive endpoints","Check the basename against the extractor's regex `(?:^|[-_v])([0-9]+(\\.[0-9A-Za-z]+)+...)` and adjust the artifact naming"],"exampleFix":"// before\n//   class Foo < Formula\n//     url \"https://example.com/releases/latest/download/foo.tgz\"\n//     sha256 \"abc...\"\n//   end\n// after\n//   class Foo < Formula\n//     version \"1.2.3\"\n//     url \"https://example.com/releases/download/1.2.3/foo.tgz\"\n//     sha256 \"abc...\"\n//   end","handlingStrategy":"validation","validationCode":"url = File.read(formula_path)[/^\\s*url\\s+[\"']([^\"']+)[\"']/, 1]\nversion = File.read(formula_path)[/^\\s*version\\s+[\"']([^\"']+)[\"']/, 1]\nversion ||= url.to_s.split(\"/\").last.sub(/\\.(tar\\.(gz|xz|bz2|zst)|tgz|txz|zip|gz)\\z/i, \"\")[/(?:^|[-_v])([0-9]+(?:\\.[0-9A-Za-z]+)+)/, 1]\nraise \"cannot determine formula version\" if version.to_s.empty?","typeGuard":"def version_inferable?(formula_source)\n  return true if formula_source.match?(/^\\s*version\\s+[\"']/)\n  url = formula_source[/^\\s*url\\s+[\"']([^\"']+)[\"']/, 1].to_s\n  basename = File.basename(url).sub(/\\.(tar\\.(gz|xz|bz2|zst)|tgz|txz|zip|gz)\\z/i, \"\")\n  !basename.match(/(?:^|[-_v])([0-9]+(?:\\.[0-9A-Za-z]+)+)/).nil?\nend","tryCatchPattern":"begin\n  extract_formula_metadata(formula_path)\nrescue RuntimeError => e\n  raise unless e.message.include?(\"could not infer formula version\")\n  warn \"Add an explicit `version` declaration to #{formula_path} or use a versioned URL\"\nend","preventionTips":["Always include an explicit `version \"x.y.z\"` when authoring or pinning formulas","Avoid latest/main archive URLs (releases/latest/download, archive/refs/heads/main.tar.gz) that carry no version","Use download URLs whose filename contains a dotted version token","Verify the URL basename matches the extractor's version regex before adding the formula"],"tags":["homebrew","formula","version","inference"],"backgroundTag":"missing-required-config-field","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}