forem/forem · error · StandardError

Invalid Stack Exchange site: {% %{tag} %{input} %}

Error message

Invalid Stack Exchange site: {% %{tag} %{input} %}

What it means

Forem's {% stackexchange %} tag resolves the target site before fetching data: if the tag name is 'stackoverflow' or the input matches the stackoverflow.com post URL pattern, the site is 'stackoverflow'; otherwise the input must match one of the tag's regexps AND expose a named 'subdomain' capture group, or the tag raises 'Invalid Stack Exchange site' with the tag name and raw input interpolated. Bare numeric ids match SITE_REGEXP (?<subdomain>\b[a-zA-Z]+\b) only when letters are present, so a bare id with the stackexchange tag fails here, not at the id check.

Source

Thrown at app/liquid_tags/stackexchange_tag.rb:54

        created_at: @json_content["creation_date"],
        score: @json_content["score"],
        comment_count: @json_content["comment_count"],
        answer_count: @json_content["answer_count"],
        post_type: @post_type,
        post_url: @json_content["link"] || default_link,
        body: @json_content["body"]
      },
    )
  end

  private

  def parse_site(input)
    return "stackoverflow" if tag_name == "stackoverflow" || input.match?(STACKOVERFLOW_REGEXP)

    match = pattern_match_for(input, REGEXP_OPTIONS)
    # rubocop:disable Layout/LineLength
    raise StandardError, I18n.t("liquid_tags.stackexchange_tag.invalid_site", tag: tag_name, input: input) unless match && match_group_present?(match, "subdomain")
    # rubocop:enable Layout/LineLength

    match[:subdomain].downcase
  end

  def match_group_present?(match, group_name)
    match.names.include?(group_name)
  end

  def get_data(input)
    id = input.split.first
    match = pattern_match_for(id, REGEXP_OPTIONS)
    unless match && match_group_present?(match, "id")
      raise StandardError, I18n.t("liquid_tags.stackexchange_tag.invalid_id", tag: tag_name, input: input)
    end

    url = "#{API_URL}posts/#{match[:id]}?site=#{@site}&filter=#{FILTERS['post']}" \
          "&key=#{ApplicationConfig['STACK_EXCHANGE_APP_KEY']}"

View on GitHub (pinned to f354c376a7)

Solutions

  1. With a bare id, name the site explicitly via URL or use the subdomain form: {% stackexchange https://crypto.stackexchange.com/q/12345 %}.
  2. For stackoverflow.com posts, use {% stackoverflow https://stackoverflow.com/q/12345 %} or the bare id with the stackoverflow tag name.
  3. Match the URL shape: <subdomain>.stackexchange.com/(q|a|questions)/<digits>.

Example fix

// before
{% stackexchange 12345 %}

// after
{% stackexchange https://crypto.stackexchange.com/q/12345 %}
Defensive patterns

Strategy: validation

Validate before calling

SE_SITE = %r{https://(?<subdomain>\w+)\.(?:stackexchange\.com|stackoverflow\.com)/(?:q|a|questions)/\d{1,20}}
def stackexchange_site_resolvable?(input)
  return true if input.match?(SE_SITE)
  input.match?(STACKOVERFLOW ||= %r{https://stackoverflow\.com/(q|a|questions)/\d{1,20}}) || input =~ /\b[a-zA-Z]+/
end

Try / catch

begin
  Liquid::Template.parse(markdown).render
rescue StandardError => e
  show_editor_error(e.message) # interpolates tag name and raw input
end

Prevention

When it happens

Trigger: {% stackexchange 12345 %} (digits contain no letters, so no subdomain group), {% stackexchange https://stackoverflow.com/questions/123 %} with a URL shape that misses STACKOVERFLOW_REGEXP (e.g. wrong path segment like /q/ without digits or /documentation/), or any non-URL, non-site string raise at app/liquid_tags/stackexchange_tag.rb:54.

Common situations: Using the generic stackexchange tag with a bare numeric post id (the tag cannot infer the site from digits alone); typos in the site subdomain; URLs from meta.* or area51 that do not fit the (?<subdomain>\w+\.)?stackexchange\.com pattern with a captured group.

Related errors


AI-assisted analysis of forem/forem@f354c376a7 (2026-08-21). Data as JSON: /api/errors/e61b150ec61bc981. Report an issue: GitHub.