{"record":{"id":"ff28eea7bf3194ff","repo":"github-linguist/linguist","slug":"can-t-convert-obj-inspect-into-string","errorCode":null,"errorMessage":"can't convert #{obj.inspect} into String","messagePattern":"can't convert #(.+?) into String","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/linguist/sha256.rb","lineNumber":32,"sourceCode":"\n      case obj\n      when String, Symbol, Integer, Float\n        digest.update \"#{obj.class}\"\n        digest.update \"#{obj}\"\n      when TrueClass, FalseClass, NilClass\n        digest.update \"#{obj.class}\"\n      when Array\n        digest.update \"#{obj.class}\"\n        for e in obj\n          digest.update(hexdigest(e))\n        end\n      when Hash\n        digest.update \"#{obj.class}\"\n        for e in obj.map { |(k, v)| hexdigest([k, v]) }.sort\n          digest.update(e)\n        end\n      else\n        raise TypeError, \"can't convert #{obj.inspect} into String\"\n      end\n\n      digest.hexdigest\n    end\n  end\nend\n","sourceCodeStart":14,"sourceCodeEnd":39,"githubUrl":"https://github.com/github-linguist/linguist/blob/b45dbe9b2825a43285bcd035861be91cc0a7299e/lib/linguist/sha256.rb#L14-L39","documentation":"Linguist::SHA256.hexdigest builds a stable content digest by case-dispatching on the object's type: String, Symbol, Integer, Float, the singleton classes true/false/nil, Array (recursed per element), and Hash (each pair sorted then digested). Any other leaf type falls into the else branch and raises TypeError with \"can't convert <inspect> into String\" — there is no to_s coercion. The digest is used for object comparison/cache keys, so unsupported values must be converted to primitives by the caller before hashing.","triggerScenarios":"1) `Linguist::SHA256.hexdigest(Time.now)` — Time is not in the case list. 2) Passing Struct, OpenStruct, Date, BigDecimal, or any custom class instance. 3) A Hash whose keys or values (at any nesting depth) contain unsupported objects — recursion eventually reaches the else branch. 4) Passing a Class/Module object itself.","commonSituations":"Using the helper to fingerprint payloads that include timestamps or model objects; feeding ActiveRecord/Struct attributes straight in; assuming the method calls to_s like Digest does — it intentionally does not, to keep digests type-stable.","solutions":["Convert unsupported values to supported primitives before hashing: `SHA256.hexdigest(obj.to_s)` for scalars, or map model objects to Arrays/Hashes of strings.","For timestamps use an Integer epoch (`time.to_i`) so digests stay stable across runs.","If you control the helper, extend the case statement with the types you need rather than relying on inspect output.","Check nested Hash/Array contents — the raise often comes from a leaf several levels deep, and obj.inspect in the message tells you which value it was."],"exampleFix":"# before\nLinguist::SHA256.hexdigest(user: user, last_login: user.last_login)\n# => TypeError: can't convert #<User ...> into String\n\n# after\nLinguist::SHA256.hexdigest(user: user.slice(:id, :login), last_login: user.last_login.to_i)","handlingStrategy":"type-guard","validationCode":"def sha256_digestable?(obj)\n  case obj\n  when String, Symbol, Integer, Float, TrueClass, FalseClass, NilClass then true\n  when Array then obj.all? { |e| sha256_digestable?(e) }\n  when Hash then obj.all? { |k, v| sha256_digestable?(k) && sha256_digestable?(v) }\n  else false\n  end\nend\n\npayload = { id: 1, at: Time.now.to_i } unless sha256_digestable?(payload)","typeGuard":"def sha256_digestable?(obj)\n  case obj\n  when String, Symbol, Integer, Float, TrueClass, FalseClass, NilClass then true\n  when Array then obj.all? { |e| sha256_digestable?(e) }\n  when Hash then obj.all? { |k, v| sha256_digestable?(k) && sha256_digestable?(v) }\n  else false\n  end\nend","tryCatchPattern":"begin\n  Linguist::SHA256.hexdigest(payload)\nrescue TypeError => e\n  raise unless e.message =~ /can't convert .* into String/\n  Linguist::SHA256.hexdigest(payload.to_s) # last-resort lossy fallback\nend","preventionTips":["The helper never calls to_s — convert values (Time#to_i, model#slice) before hashing.","Check nested containers: Hash/Array elements are recursed, so a bad leaf several levels deep is the real culprit.","Standardize on primitives (String/Symbol/Integer/Float/true/false/nil/Array/Hash) in anything you plan to digest."],"tags":["ruby","type-error","hashing","serialization"],"backgroundTag":"unsupported-type-conversion","analyzedSha":"b45dbe9b2825a43285bcd035861be91cc0a7299e","analyzedAt":"2026-08-21T15:30:05.145Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}