Bug
rlm/utils/parsing.py:18 calls re.finditer(pattern, text, re.DOTALL) on the response from lm_handler.completion() without validating that text is a string. When the LM client returns None (which we've observed intermittently from the Cerebras OpenAI-compatible API at scale on zai-glm-4.7), the regex chokes and the whole RLM run dies with a TypeError.
Stack trace (verified against current main, commit 8467a58)
File "rlm/core/rlm.py", line 605, in _completion_turn
code_block_strs = find_code_blocks(response)
File "rlm/utils/parsing.py", line 18, in find_code_blocks
for match in re.finditer(pattern, text, re.DOTALL):
File ".../python3.14/re/__init__.py", line 285, in finditer
return _compile(pattern, flags).finditer(string)
TypeError: expected string or bytes-like object, got 'NoneType'
Repro
- Environment: Python 3.14,
rlms installed from git+main@8467a58
- Backend: Cerebras API (
base_url=https://api.cerebras.ai/v1), model_name=zai-glm-4.7, OpenAI-compatible
- RLM config:
environment="local", compaction=True, compaction_threshold_pct=0.80, max_depth=2, max_iterations=50, max_timeout=240.0
- Corpus: 25 Python files, ~100K input tokens
- Behavior: intermittent — re-running the same input sometimes succeeds (with the model honestly reporting it can't read the corpus) and sometimes hits this
TypeError mid-iteration. The crash typically happens 3-6 iterations in, after several search() / parse_structure() tool calls.
Why it's a bug rather than caller error
lm_handler.completion() is expected to return a string for downstream parsing. The upstream API (Cerebras / GLM 4.7) is occasionally returning a response object whose content field deserializes to None instead of empty-string — likely under tool-use pressure or near context limits. Whether the client should normalize None → "" is one option; whether find_code_blocks should defensively check text is None is another. Either way, the current code raises a hard TypeError that aborts an entire RLM run rather than letting the iteration continue with empty content (which would let compaction / fallback paths handle the missing response).
Suggested fix (one of)
Option A — defensive guard in find_code_blocks:
# rlm/utils/parsing.py
def find_code_blocks(text):
if not text:
return []
for match in re.finditer(pattern, text, re.DOTALL):
...
Option B — normalize at the LM-client boundary (e.g., in BaseLM.completion post-processing): coerce None to "" before returning, so every code path downstream sees a string. Logs a warning when the coercion fires so providers returning malformed responses are visible.
Option A is the smaller change; Option B is the more principled fix because the same None would also break any other downstream regex in the codebase that consumes response.
Context
This is distinct from PR #127 (Claude 4.6 text-FINAL hallucination, superseded by #162 new_final_answer_format). PR #162's protocol change handles the case where the LM emits trailing text-FINAL; it doesn't help when the LM returns None at all.
Happy to send a PR for Option A if useful. Discovered while writing a downstream verification harness for KCE — the LM was almost certainly under near-context-limit pressure when this fired.
Bug
rlm/utils/parsing.py:18callsre.finditer(pattern, text, re.DOTALL)on the response fromlm_handler.completion()without validating thattextis a string. When the LM client returnsNone(which we've observed intermittently from the Cerebras OpenAI-compatible API at scale onzai-glm-4.7), the regex chokes and the whole RLM run dies with aTypeError.Stack trace (verified against current main, commit 8467a58)
Repro
rlmsinstalled from git+main@8467a58base_url=https://api.cerebras.ai/v1),model_name=zai-glm-4.7, OpenAI-compatibleenvironment="local",compaction=True,compaction_threshold_pct=0.80,max_depth=2,max_iterations=50,max_timeout=240.0TypeErrormid-iteration. The crash typically happens 3-6 iterations in, after severalsearch()/parse_structure()tool calls.Why it's a bug rather than caller error
lm_handler.completion()is expected to return a string for downstream parsing. The upstream API (Cerebras / GLM 4.7) is occasionally returning a response object whose content field deserializes toNoneinstead of empty-string — likely under tool-use pressure or near context limits. Whether the client should normalizeNone → ""is one option; whetherfind_code_blocksshould defensively checktext is Noneis another. Either way, the current code raises a hardTypeErrorthat aborts an entire RLM run rather than letting the iteration continue with empty content (which would let compaction / fallback paths handle the missing response).Suggested fix (one of)
Option A — defensive guard in
find_code_blocks:Option B — normalize at the LM-client boundary (e.g., in
BaseLM.completionpost-processing): coerceNoneto""before returning, so every code path downstream sees a string. Logs a warning when the coercion fires so providers returning malformed responses are visible.Option A is the smaller change; Option B is the more principled fix because the same
Nonewould also break any other downstream regex in the codebase that consumesresponse.Context
This is distinct from PR #127 (Claude 4.6 text-FINAL hallucination, superseded by #162
new_final_answer_format). PR #162's protocol change handles the case where the LM emits trailing text-FINAL; it doesn't help when the LM returnsNoneat all.Happy to send a PR for Option A if useful. Discovered while writing a downstream verification harness for KCE — the LM was almost certainly under near-context-limit pressure when this fired.