jdx/mise · error
Tag closing loop reached loop limit (%d). Consider either in
Error message
Tag closing loop reached loop limit (%d). Consider either increasing it or checking HTML-code for syntax errors
What it means
This error is thrown by the embedded htmlparser Lua library (used by vfox plugins that parse HTML, e.g. to scrape version lists from a website) when its TagCloseLoop, the loop that matches closing tags back to their opened counterparts, iterates `limit` times without finishing. It is a safety valve against runaway parsing caused by malformed or pathological HTML: unclosed tags, mismatched close names, or documents far larger than the loop budget allow. The parser aborts rather than looping forever, so any plugin call that triggers an HTML parse of such input fails with this message.
Source
Thrown at crates/vfox/lua/htmlparser.lua:228
end
-- }}}
if voidelements[tag.name:lower()] then -- {{{
descend = false
tag:close()
else
descend = true
opentags[tag.name] = opentags[tag.name] or {}
table.insert(opentags[tag.name], tag)
end
-- }}}
local closeend = tpos
local closingloop
while true do -- TagCloseLoop {{{
-- Can't remember why did I add that, so comment it for now (and not remove), in case it will be needed again
-- (although, it causes #59 and #60, so it will anyway be needed to rework)
-- if voidelements[tag.name:lower()] then break end -- already closed
if closingloop == limit then
err("Tag closing loop reached loop limit (%d). Consider either increasing it or checking HTML-code for syntax errors", limit)
break
end
local closestart, closing, closename
closestart, closeend, closing, closename = root._text:find("[^<]*<(/?)([%w-]+)", closeend)
dbg("[TagCloseLoop]:#LINE# closestart=%s || closeend=%s || closing=%s || closename=%s",str(closestart),str(closeend),str(closing),str(closename))
if not closing or closing == "" then break end
tag = table.remove(opentags[closename] or {}) or tag -- kludges for the cases of closing void or non-opened tags
closestart = root._text:find("<", closestart)
dbg("[TagCloseLoop]:#LINE# closestart=%s",str(closestart))
tag:close(closestart, closeend + 1)
node = tag.parent
descend = true
closingloop = (closingloop or 0) + 1
end -- }}}
end -- }}}View on GitHub (pinned to afd2eddd3a)
Solutions
- Inspect the HTML being parsed (log `root._text` or fetch the page yourself) and fix the malformed markup: close opened tags, remove stray `</...>` closers, ensure tag-name case matches.
- Increase the `limit` argument passed to the HTML parser call (the loop budget) if the input is legitimately large but well-formed.
- Update the vfox plugin to a newer version that tolerates the upstream page's current HTML, or patch the plugin's parsing logic/regex to match the changed markup.
- If the page markup is beyond repair, replace HTML scraping in the plugin with a stable API/JSON endpoint or a stricter parsing approach.
- Report the issue to the plugin maintainer (or htmlparser upstream, issues #59/#60 discuss this loop) with the failing URL/input.
Example fix
// before (plugin calls parser with default limit on a huge page) local root = htmlparser.parse(body) // after (raise the loop limit for large documents) local root = htmlparser.parse(body, nil, nil, 5000) -- limit high enough for page size
Defensive patterns
Strategy: validation
Validate before calling
-- validate before parsing: balance check for the tags the parser will consume
local function count_balance(html)
local opens, closes = 0, 0
for _, sl, name in html:gmatch("<(%/?)([%w-]+)") do
if sl == "/" then closes = closes + 1 else opens = opens + 1 end
end
return opens == closes, opens, closes
end
local ok = count_balance(body)
if not ok then error("unbalanced HTML tags; refusing to parse") end Type guard
-- Lua has no static types; assert parseability instead
local function is_parseable(html, limit)
if type(html) ~= "string" or #html == 0 then return false end
local closes = 0
for _ in html:gmatch("<(/)([%w-]+)") do closes = closes + 1 end
return closes < (limit or 100) -- under the TagCloseLoop budget
end Try / catch
-- wrap the parse call; htmlparser's err() aborts the chunk, so guard at the caller
local ok, root = pcall(function() return htmlparser.parse(body, nil, nil, larger_limit) end)
if not ok then
log("HTML parse failed: " .. tostring(root))
return fallback_versions
end Prevention
- Validate fetched HTML for balanced open/close tags before parsing.
- Raise the parser's limit argument proportionally to document size.
- Pin and periodically update plugins so scraping logic tracks upstream page changes.
- Prefer JSON/API endpoints over HTML scraping in vfox plugins.
- Test plugins against cached snapshots of the real pages they scrape to catch upstream markup changes early.
When it happens
Trigger: Calling a vfox plugin function that parses HTML (via htmlparser.lua's `parse`) where the document contains many consecutive closing tags without matching opens, mismatched tag names (`<Div>` closed by `</div>` in a way the kludge path mishandles), stray `</tag>` closers for void or never-opened tags, or simply more closing-tag iterations than the parser's `limit` argument permits.
Common situations: Plugin authors scraping a tool's download/releases page whose HTML changed upstream (new markup, broken or unclosed tags); malformed snippets fed to the parser in tests; very large HTML pages where the default loop limit is too small; copying HTML that contains comments or text with `</`-looking sequences the simplistic regex `[^<]*<(/?)([%w-]+)` misreads as closers.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- cloned request builder should remain cloneable
- Impossible to find at least two unused byte codes in this HT
- Consider enabling 'keep_danger_placeholders' option (to sile
- Main loop reached loop limit (%d). Consider either increasin
- Tag parsing loop reached loop limit (%d). Consider either in
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/ba870f0bf35e2b39.
Report an issue: GitHub.