langchain-ai/deepagents · error · ClientHookStopError

Session start stopped by hook

Error message

Session start stopped by hook

What it means

At the start of `_run_agent_loop`, `on_session_start(SessionStartCause.STARTUP)` runs; if the hook outcome is not ok, the session is ended with `SessionEndCause.OTHER` and `ClientHookStopError` is raised with the hook's `stop_reason` or this default message. Client hooks therefore have veto power over starting a headless session.

Source

Thrown at libs/code/deepagents_code/client/non_interactive.py:2239

        # Project hooks require an explicit opt-in, matching `--trust-project-mcp`.
        # Persisted interactive trust deliberately does not carry into headless
        # runs, so CI never inherits a grant made at someone's terminal.
        trust=WorkspaceTrust.explicit_only(Path.cwd(), granted=trust_project_hooks),
    )
    state.hooks.attach_output(notice=present_hook_notice, status=hook_status)
    state.hooks.apply_graph_context(context)
    context["approval_mode"] = resolved_approval_mode.value
    context["auto_approve"] = resolved_approval_mode is ApprovalMode.YOLO
    state.active_model = runtime_state.model_name or None
    state.transcript = state.hooks.recorder(thread_id)

    start_outcome = await state.hooks.on_session_start(
        SessionStartCause.STARTUP,
        model=runtime_state.model_name or None,
    )
    if not start_outcome.ok:
        await _end_headless_session(state, SessionEndCause.OTHER)
        raise ClientHookStopError(
            start_outcome.stop_reason or "Session start stopped by hook"
        )
    session_context = state.hooks.take_pending_context()
    if session_context:
        stream_input["messages"].insert(
            0,
            {"role": "system", "content": "\n\n".join(session_context)},
        )

    try:
        state.transcript.append([HumanMessage(content=message)])
        if state.hooks.has_handlers(HookEvent.USER_PROMPT_SUBMIT):
            prompt_outcome = await state.hooks.on_user_prompt(message)
            if not prompt_outcome.ok:
                _raise_client_hook_stop(
                    prompt_outcome.stop_reason
                    or "User prompt submission stopped by hook"
                )

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Read `stop_reason` from the error; fix the condition in the hook that rejected startup.
  2. Adjust hook configuration (model allow-lists, trust settings for project hooks) so legitimate sessions pass.
  3. Catch `ClientHookStopError` in the caller to handle a deliberate stop gracefully (session state is already ended via `_end_headless_session`).
  4. If no hook should be active, check that hooks aren't being loaded unintentionally (project hook trust opt-in).

Example fix

// before: hook blocks everything not on an old list
if model not in ["old-model"]: return HookOutcome(ok=False, stop_reason="model blocked")
// after: keep list current
if model not in APPROVED_MODELS: return HookOutcome(ok=False, stop_reason=f"model {model} not approved")
Defensive patterns

Strategy: try-catch

Try / catch

from deepagents_code.hooks.client_lifecycle import ClientHookStopError
try:
    run_non_interactive(task)
except ClientHookStopError as exc:
    # session already ended with SessionEndCause.OTHER; surface the hook's reason
    report_session_blocked(exc)

Prevention

When it happens

Trigger: Calling `run_non_interactive` (which enters `_run_agent_loop`) while a registered client lifecycle hook rejects the startup cause — e.g. policy checks on model name, environment, or repository.

Common situations: Org guardrail hooks blocking untrusted project hooks or disallowed models; hooks failing environment validation in CI; tests asserting that a user-prompt stop or untrusted project hooks end the session once.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/ad9b5fc35a30e599. Report an issue: GitHub.