{"record":{"id":"4944cf50344cacda","repo":"we-promise/sure","slug":"malformed-kraken-api-response","errorCode":null,"errorMessage":"Malformed Kraken API response","messagePattern":"Malformed Kraken API response","errorType":"exception","errorClass":"Provider::Kraken::ApiError","httpStatus":null,"severity":"error","filePath":"app/models/provider/kraken.rb","lineNumber":127,"sourceCode":"    end\n\n    def sign(path, params)\n      encoded_payload = URI.encode_www_form(params)\n      nonce = params.fetch(\"nonce\").to_s\n      digest = OpenSSL::Digest::SHA256.digest(nonce + encoded_payload)\n      hmac = OpenSSL::HMAC.digest(\"sha512\", Base64.decode64(api_secret), path + digest)\n      Base64.strict_encode64(hmac)\n    end\n\n    def handle_response(response)\n      parsed = response.parsed_response\n\n      unless response.code.between?(200, 299)\n        raise ApiError, \"Kraken API request failed: #{response.code}\"\n      end\n\n      unless parsed.is_a?(Hash)\n        raise ApiError, \"Malformed Kraken API response\"\n      end\n\n      unless parsed.key?(\"error\")\n        raise ApiError, \"Malformed Kraken API response: missing error\"\n      end\n\n      errors = Array(parsed[\"error\"]).reject(&:blank?)\n      raise classified_error(errors) if errors.any?\n\n      unless parsed.key?(\"result\")\n        raise ApiError, \"Malformed Kraken API response: missing result\"\n      end\n\n      parsed[\"result\"]\n    end\n\n    def classified_error(errors)\n      message = errors.join(\", \")","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/kraken.rb#L109-L145","documentation":"Raised as Provider::Kraken::ApiError when a successful (2xx) Kraken response body does not parse to a Hash. Kraken's REST API always returns a JSON object {error: [...], result: ...}; a non-Hash parsed body means the payload was an array, scalar, or an empty body (HTTParty's parsed_response can be nil when the body is blank). This guard runs after the status check and before Kraken's in-band error handling.","triggerScenarios":"Any Kraken call where the body is empty (connection closed mid-response, 204) or JSON that is not an object — e.g. a proxy serving a bare JSON array/string, or an upstream change in the response envelope. parsed_response returning nil on blank bodies is the most common concrete case.","commonSituations":"Load balancers or CDNs truncating responses, Kraken maintenance pages returning non-JSON-Object payloads with 2xx, HTTParty version changes in how blank bodies parse, and corporate proxies rewriting bodies.","solutions":["Reproduce the exact call with curl -i and inspect Content-Length/Content-Type of the response","Check Kraken status page — truncated/empty 2xx bodies often coincide with incidents","Retry once; truncated responses are usually transient network artifacts","If a proxy is in path, bypass it to confirm the raw Kraken response","Consider treating nil parsed_response (blank body) as retryable distinctly from wrong-shape bodies"],"exampleFix":"# before: nil parsed_response (empty body) hits the same opaque guard\nparsed = response.parsed_response\nraise ApiError, \"Malformed Kraken API response\" unless parsed.is_a?(Hash)\n\n# after: distinguish empty (retryable) from wrong-shape (fatal)\nparsed = response.parsed_response\nif parsed.nil?\n  raise ApiError, \"Kraken returned an empty body (HTTP #{response.code})\"\nend\nraise ApiError, \"Malformed Kraken API response: #{parsed.class}\" unless parsed.is_a?(Hash)","handlingStrategy":"retry","validationCode":null,"typeGuard":"# Narrow a parsed Kraken response before use\nmodule KrakenGuard\n  Envelope = Struct.new(:error, :result)\n\n  def self.parse(response)\n    parsed = response.parsed_response\n    return nil unless parsed.is_a?(Hash) && parsed.key?(\"error\") && parsed.key?(\"result\")\n    Envelope.new(Array(parsed[\"error\"]), parsed[\"result\"])\n  end\nend","tryCatchPattern":"begin\n  result = provider.get_trades_history\nrescue Provider::Kraken::ApiError => e\n  raise unless e.message == \"Malformed Kraken API response\"\n  # empty/truncated bodies are usually transient — single retry with backoff\n  sleep 2\n  retry\nend","preventionTips":["Retry malformed/empty-body responses once with backoff before failing a sync","Ensure no proxy between the app and api.kraken.com rewrites or truncates JSON bodies","Stub complete Kraken envelopes in tests ({error: [...], result: ...}) so the guard is exercised deliberately","Alert when malformed responses persist — that signals gateway or upstream contract problems"],"tags":["kraken","schema-validation","json","crypto","api-contract"],"backgroundTag":"schema-validation-failed","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}