Summary
The exact symptom that closed issue #103 fixed is back on a clean pip install langstage-cli (0.6.30). On the headline HITL path, every time a user answers a plain-string interrupt() with free text, an internal ag_ui_langgraph WARNING leaks straight into the conversation, right above the correct reply:
failed to parse legacy resume_input as JSON, treating as string (thread_id='…', run_id='…', error=Expecting value: line 1 column 1 (char 0)): 'yes go ahead'
Root cause: #103 was fixed by installing a surgical logging filter (_DropResumeJSONWarning, cli.py:152) that drops any record whose message contains the substring "failed to parse resume_input as JSON" (_AGUI_RESUME_JSON_WARNING, cli.py:142). But the bundled ag-ui-langgraph has since bumped 0.0.42 → 0.0.43 and reworded the message — it now reads failed to parse legacy resume_input as JSON, … (the word legacy was inserted after parse). The filter substring is no longer a substring of the emitted message, so the record passes the filter and reaches the console again. The #103 fix silently no longer does anything.
This is a brittle-string-match regression: the CLI pins ag-ui-langgraph>=0.0.41 (no upper bound) and langstage-core[agui], and the warning text drifted underneath the hard-coded matcher.
Severity: minor (cosmetic noise; the resumed value is correct) — same impact class as #103/#126 — but it is a regression of a shipped, closed fix on the primary HITL feature, and it will keep silently re-breaking whenever the upstream wording moves.
Distinct from the still-open #126, which is about a different line on the same resume (forwardedProps.command.resume is deprecated …). This issue is specifically the JSON-parse WARNING that #103 was closed to suppress.
Environment
Minimal repro
hitl.py — a canonical plain-string interrupt (no API key, no model, no own checkpointer):
# hitl.py
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import MessagesState
from langgraph.types import interrupt
from langchain_core.messages import AIMessage
def ask(state):
answer = interrupt({"question": "Approve?"})
return {"messages": [AIMessage(content=f"Decision was: {answer!r}")]}
g = StateGraph(MessagesState)
g.add_node("ask", ask); g.add_edge(START, "ask"); g.add_edge("ask", END)
graph = g.compile()
Deterministic, non-interactive repro (the --no-interactive empty auto-resume is a non-JSON '', which always trips the same warning):
langstage-cli -a hitl.py:graph --no-interactive "go" 2>&1 >/dev/null
Prints (to stderr):
Auto-resuming generic interrupt with an empty value (--no-interactive)
forwardedProps.command.resume is deprecated; please send RunAgentInput.resume[] (thread_id='…', run_id='…')
failed to parse legacy resume_input as JSON, treating as string (thread_id='…', run_id='…', error=Expecting value: line 1 column 1 (char 0)): ''
Interactive repro (the user-visible one — same as #103): langstage-cli -a hitl.py:graph, type do it, select Provide a response, type yes go ahead. Literal output (ANSI stripped):
⠋ Thinking... 0sforwardedProps.command.resume is deprecated; please send RunAgentInput.resume[] (thread_id='ec4998db-…', run_id='4e789e94-…')
failed to parse legacy resume_input as JSON, treating as string (thread_id='ec4998db-…', run_id='4e789e94-…', error=Expecting value: line 1 column 1 (char 0)): 'yes go ahead'
⏺ Decision was: 'yes go ahead'
Proof the installed filter no longer matches
import logging
from langstage_cli.cli import _DropResumeJSONWarning, _AGUI_RESUME_JSON_WARNING
flt = _DropResumeJSONWarning()
# The message ag_ui_langgraph 0.0.43 now emits:
new = logging.LogRecord("ag_ui_langgraph.agent", logging.WARNING, "", 0,
"failed to parse legacy resume_input as JSON, treating as string "
"(thread_id=%r, run_id=%r, error=%s): %r",
("tid","rid","Expecting value: line 1 column 1 (char 0)",""), None)
# The message the filter was written for (0.0.42):
old = logging.LogRecord("ag_ui_langgraph.agent", logging.WARNING, "", 0,
"failed to parse resume_input as JSON, treating as string", None, None)
print(_AGUI_RESUME_JSON_WARNING) # 'failed to parse resume_input as JSON'
print(flt.filter(new)) # True -> ALLOWED THROUGH (leaks)
print(flt.filter(old)) # False -> suppressed (the case #103 handled)
Output on 0.6.30:
failed to parse resume_input as JSON
True
False
The upstream source now reads (ag_ui_langgraph/agent.py:660):
logger.warning(
"failed to parse legacy resume_input as JSON, treating as string "
"(thread_id=%r, run_id=%r, error=%s): %r", ...)
Expected vs actual
Suggested fix
Filed by the nightly dogfood routine. Confirmed reproducible on a clean pip install langstage-cli (0.6.30, ag-ui-langgraph 0.0.43).
Summary
The exact symptom that closed issue #103 fixed is back on a clean
pip install langstage-cli(0.6.30). On the headline HITL path, every time a user answers a plain-stringinterrupt()with free text, an internalag_ui_langgraphWARNING leaks straight into the conversation, right above the correct reply:Root cause: #103 was fixed by installing a surgical logging filter (
_DropResumeJSONWarning,cli.py:152) that drops any record whose message contains the substring"failed to parse resume_input as JSON"(_AGUI_RESUME_JSON_WARNING,cli.py:142). But the bundledag-ui-langgraphhas since bumped 0.0.42 → 0.0.43 and reworded the message — it now readsfailed to parse legacy resume_input as JSON, …(the wordlegacywas inserted afterparse). The filter substring is no longer a substring of the emitted message, so the record passes the filter and reaches the console again. The #103 fix silently no longer does anything.This is a brittle-string-match regression: the CLI pins
ag-ui-langgraph>=0.0.41(no upper bound) andlangstage-core[agui], and the warning text drifted underneath the hard-coded matcher.Severity: minor (cosmetic noise; the resumed value is correct) — same impact class as #103/#126 — but it is a regression of a shipped, closed fix on the primary HITL feature, and it will keep silently re-breaking whenever the upstream wording moves.
Distinct from the still-open #126, which is about a different line on the same resume (
forwardedProps.command.resume is deprecated …). This issue is specifically the JSON-parse WARNING that #103 was closed to suppress.Environment
langstage-cli0.6.30 (cleanpip install langstage-clifrom PyPI, fresh venv)langstage-core1.0.34,ag-ui-langgraph0.0.43 (was 0.0.42 in HITL: answering a plain-stringinterrupt()leaks anag_ui_langgraphWARNING (failed to parse resume_input as JSON, treating as string ...) onto the console on every free-text response #103),langgraph1.2.11Minimal repro
hitl.py— a canonical plain-string interrupt (no API key, no model, no own checkpointer):Deterministic, non-interactive repro (the
--no-interactiveempty auto-resume is a non-JSON'', which always trips the same warning):Prints (to stderr):
Interactive repro (the user-visible one — same as #103):
langstage-cli -a hitl.py:graph, typedo it, select Provide a response, typeyes go ahead. Literal output (ANSI stripped):Proof the installed filter no longer matches
Output on 0.6.30:
The upstream source now reads (
ag_ui_langgraph/agent.py:660):Expected vs actual
interrupt()prints only the agent's reply — the guarantee HITL: answering a plain-stringinterrupt()leaks anag_ui_langgraphWARNING (failed to parse resume_input as JSON, treating as string ...) onto the console on every free-text response #103 was closed to deliver.failed to parse legacy resume_input as JSON …WARNING is printed above the reply on every free-text HITL response (and on every--no-interactiveinterrupt auto-resume).Suggested fix
forwardedProps.command.resume is deprecated ...into the conversation — self-inflicted, and the CLI already filters its sibling warning (#103) but not this one #126): stop routing resume through the deprecatedforwarded_props.command.resumeinlangstage-coreand send the AG-UI-standardRunAgentInput.resume[]. That eliminates both leaked lines at the source and removes the forward-compat risk Every HITL approve/reject leaksforwardedProps.command.resume is deprecated ...into the conversation — self-inflicted, and the CLI already filters its sibling warning (#103) but not this one #126 describes — for every LangStage surface — instead of chasing upstream wording changes with substrings._DropResumeJSONWarningrobust to the wording drift — match a stable fragment such as"resume_input as JSON"(covers bothparse resume_inputandparse legacy resume_input), or key the filter off the logger name + line rather than an exact prefix. A hard-coded full-phrase substring on a third-party log string is inherently fragile; this is the second time its exact text has mattered.Filed by the nightly dogfood routine. Confirmed reproducible on a clean
pip install langstage-cli(0.6.30, ag-ui-langgraph 0.0.43).