langchain-ai/deepagents · error · RuntimeError
Manual mode callback is unavailable
Error message
Manual mode callback is unavailable
What it means
When the model emits a 'switch_manual' HITL decision, execute_task_textual invokes adapter._on_switch_to_manual. If that callback is None (the UI never wired a manual-mode switch handler), RuntimeError is raised instead of silently ignoring the decision, and the shared turn-error rendering reports the failure.
Source
Thrown at libs/code/deepagents_code/tui/textual_adapter.py:3660
_set_running_unless_deferred(tool_msg)
adapter._sync_tool_widget(tool_msg)
for action_request in action_requests:
tool_name = action_request.get("name")
if tool_name in {
"write_file",
"edit_file",
"delete",
}:
args = action_request.get("args", {})
if isinstance(args, dict):
file_op_tracker.mark_hitl_approved(
tool_name, args
)
elif decision_type == "switch_manual":
if adapter._on_switch_to_manual is None:
msg = "Manual mode callback is unavailable"
raise RuntimeError(msg) # noqa: TRY301 # shared turn error rendering owns this failure
callback_result = adapter._on_switch_to_manual()
switched = (
await callback_result
if inspect.isawaitable(callback_result)
else callback_result
)
if not switched:
msg = "Manual mode could not be persisted"
raise RuntimeError(msg) # noqa: TRY301 # shared turn error rendering owns this failure
decisions = [
cast("HITLDecision", {"type": "switch_manual"})
for _ in action_requests
]
elif decision_type == "approve":
decisions = [
ApproveDecision(type="approve")
for _ in action_requestsView on GitHub (pinned to a1af029e6e)
Solutions
- Register adapter._on_switch_to_manual when constructing the adapter for sessions that can enter manual approval mode.
- Handle the 'switch_manual' decision differently (e.g. reject with a message) when the callback is absent.
- Update the TUI/adapter integration so the callback is always provided for interactive sessions.
Example fix
// before adapter = TextualAdapter(..., _on_switch_to_manual=None) // after adapter = TextualAdapter(..., _on_switch_to_manual=self._switch_to_manual)
Defensive patterns
Strategy: type-guard
Validate before calling
if adapter._on_switch_to_manual is None:
decisions = [{"type": "reject", "message": "manual switch unsupported"}]
else:
decisions = [{"type": "switch_manual"}] Type guard
def supports_manual_switch(adapter: object) -> TypeGuard[Adapter]:
return getattr(adapter, "_on_switch_to_manual", None) is not None Try / catch
try:
handle_hitl_decision(adapter, decision)
except RuntimeError as exc:
render_turn_error(f"cannot switch to manual: {exc}") Prevention
- Always register _on_switch_to_manual when constructing adapters for interactive sessions.
- Add an adapter self-check at startup that required HITL callbacks are present.
- Cover 'switch_manual' decisions in integration tests.
When it happens
Trigger: An interrupt decision of type 'switch_manual' arrives from the agent (e.g. a request to switch out of auto-approve), but the adapter was constructed without _on_switch_to_manual being set.
Common situations: Embedding the textual adapter headlessly or in tests without the full TUI wiring; a UI refactor that dropped the callback registration; running auto-approve flows where manual switching was never expected.
Related errors
- Manual mode could not be persisted
- interpreter_ptc='all' exposes every host tool to PTC calls t
- Provide exactly one of mode or auto_approve
- Invalid approval mode: {mode!r}
- {message}
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/fcd7c7c07ad2c30e.
Report an issue: GitHub.