opf/openproject · error · Import::JiraClient::ParseError

Failed to parse Jira API response: %{message}

Error message

Failed to parse Jira API response: %{message}

What it means

handle_response parses every 2xx body with JSON.parse; a JSON::ParserError is re-raised as Import::JiraClient::ParseError with this message. It means the HTTP request itself succeeded but the body is not JSON — almost always an HTML page (SSO login form, proxy error page) or an empty body arriving with a 200 status.

Source

Thrown at app/services/import/jira_client.rb:320

    end

    def handle_response(response)
      status = response.code.to_i
      if response.is_a?(Net::HTTPSuccess)
        parse_json(response)
      else
        raise ApiError.new(
          I18n.t("admin.jira.client.#{status}_error", status:, default: :"admin.jira.client.api_error"),
          status:,
          response_body: response.body.to_s
        )
      end
    end

    def parse_json(response)
      JSON.parse(response.body)
    rescue JSON::ParserError => e
      raise ParseError, I18n.t("admin.jira.client.parse_error", message: e.message)
    end
  end
end

View on GitHub (pinned to d9742c43f3)

Solutions

  1. Verify the base URL directly: curl -H 'Authorization: Bearer <token>' https://jira.example.com/rest/api/2/serverInfo must return JSON
  2. If SSO fronts Jira, configure it to bypass authentication for /rest/api/* (e.g. basic auth or anonymous REST access) so API calls are not redirected to an HTML login
  3. Inspect %{message}: JSON::ParserError includes the offending text/offset — '<html' fragments confirm an interceptor page
  4. Correct the saved URL to the bare Jira base URL and re-test the connection from the import settings

Example fix

# before — import settings URL points at an HTML page
#   Jira URL: https://jira.example.com/login
#   → GET /rest/api/2/serverInfo returns login HTML → ParseError

# after
#   Jira URL: https://jira.example.com
#   curl -H "Authorization: Bearer $TOKEN" \
#     https://jira.example.com/rest/api/2/serverInfo   # returns JSON
Defensive patterns

Strategy: try-catch

Validate before calling

# smoke-test that the configured base URL speaks JSON before importing
resp = OpenProject::SsrfProtection.get("#{jira_url}/rest/api/2/serverInfo",
                                       headers: { "Authorization" => "Bearer #{token}" })
abort "base URL does not serve the Jira REST API" unless resp.is_a?(Net::HTTPSuccess) && resp["Content-Type"].to_s.include?("application/json")

Try / catch

begin
  data = client.issues(jql: "project = X")
rescue Import::JiraClient::ParseError => e
  # 2xx + non-JSON body: almost always an SSO login/proxy page in front of Jira
  Rails.logger.error("Jira returned non-JSON (SSO/proxy interceptor?): #{e.message}")
  raise
end

Prevention

When it happens

Trigger: Any Import::JiraClient API call where @url points at a non-Jira page (e.g. https://jira.example.com/login so /rest/api/2/... returns the login HTML), an SSO/SAML layer in front of Jira redirects REST calls to an HTML login page, an intermediary (proxy, captive portal, WAF) returns an HTML block page with 200, or the URL scheme/host is wrong so the response comes from an unrelated server.

Common situations: Saving the Jira login page URL instead of the base URL in the import settings; SAML/OIDC web SSO in front of Jira that does not exempt /rest/api; reverse proxies serving custom error pages; typo'd domains resolving to a parked page; API token invalid so an HTML error page is returned instead of JSON.

Understand the failure class

Related errors


AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21). Data as JSON: /api/errors/b31303c44a7d87c5. Report an issue: GitHub.