headroomlabs-ai/headroom · warning · TimeoutError
native detect_content_type exceeded {timeout:.1f}s watchdog
Error message
native detect_content_type exceeded {timeout:.1f}s watchdog What it means
Raised by the watchdog wrapper around the native (Rust/PyO3) detect_content_type in content_router.py: a daemon thread runs rust_detect(content) and if it is still alive after the timeout, the call is declared hung and TimeoutError is raised — you cannot interrupt a stuck native call from Python, so the thread is abandoned and the caller degrades instead of blocking forever. Any exception the thread did raise is re-raised verbatim afterwards.
Source
Thrown at headroom/transforms/content_router.py:863
compression-executor worker — see #575's executor-saturation report).
# ponytail: can't kill a GIL-released native call; the watchdog frees the
# caller and the stuck daemon thread is left to die with the process. The
# upgrade path is the Rust-side fix that makes first-call init non-blocking.
"""
box: dict[str, Any] = {}
def _run() -> None:
try:
box["result"] = rust_detect(content)
except BaseException as exc: # noqa: BLE001 — relayed to the caller's degrade path
box["error"] = exc
worker = threading.Thread(target=_run, name="headroom-detect-watchdog", daemon=True)
worker.start()
worker.join(timeout)
if worker.is_alive():
raise TimeoutError(f"native detect_content_type exceeded {timeout:.1f}s watchdog")
if "error" in box:
raise box["error"]
return box["result"]
# Coding agents commonly wrap each tool result in an envelope such as
# ``<returncode>0</returncode>\n<output>...</output>`` (or <stdout>/<stderr>/
# <tool_result>). Those wrapper tags make the native detector read the whole
# payload as markup (HTML/XML) even though the inner content is source code, a
# grep result, or a log. That misroutes to the HTML article-extractor, which
# blanks or corrupts code (dropping identifiers and route converters). Detect on
# the inner payload so the real content type wins; compression still runs on the
# original content.
_DETECTION_ENVELOPE_RE = re.compile(
r"\A\s*(?:<returncode>\s*-?\d+\s*</returncode>\s*)?"
r"<(?P<tag>output|stdout|stderr|tool_result|result)>\n?"
r"(?P<body>.*?)"
r"\n?</(?P=tag)>\s*\Z",View on GitHub (pinned to 322425c43b)
Solutions
- Catch TimeoutError and fall back to the pure-Python detector or treat content as plain text (this is the designed degrade path).
- If it reproduces, minimize the input and report it — a detector exceeding the watchdog indicates a native bug worth filing.
- Raise the watchdog timeout only if profiling shows legitimate large inputs, not a hang.
Example fix
# before
ctype = detect_content_type_wrapped(content) # may hang -> TimeoutError
# after
try:
ctype = detect_content_type_wrapped(content)
except TimeoutError:
ctype = "text/plain" # designed degrade path Defensive patterns
Strategy: fallback
Try / catch
try:
ctype = detect_wrapped(content)
except TimeoutError:
logger.warning("native detect timed out; degrading to text/plain")
ctype = "text/plain" Prevention
- Always keep a non-native fallback wired for content detection.
- Log the payload size when the watchdog fires — oversized inputs may need pre-truncation.
- Report reproducible hangs upstream; the watchdog is a safety net, not a fix.
When it happens
Trigger: Calling the native content detection path with input that makes the Rust detector loop or take longer than the watchdog timeout — e.g. pathological regex-ish payloads, huge markup-nested tool envelopes, or adversarial content.
Common situations: Compressing very large or deeply nested tool outputs through the content router; a Rust detector regression on a new input shape; slow/loaded machines pushing a normally-fast parse past the timeout.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- upstream request failed: {0}
- `{' '.join(cmd)}` exceeded the {hard_cap}s hard cap. Raise H
- bedrock_upstream_exception
- bedrock_eventstream_missing_event_type
- bedrock_sigv4_failed
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/1023813d3cb83f92.
Report an issue: GitHub.