diff --git a/api/agent_sessions.py b/api/agent_sessions.py index bc0e1431f..38f9f37a3 100644 --- a/api/agent_sessions.py +++ b/api/agent_sessions.py @@ -176,7 +176,11 @@ def is_cli_session_row(row: dict) -> bool: source_label = _safe_lower(row.get("source_label")) if "webui" in {source, source_tag, raw_source, source_name, source_label}: return False - non_cli_sources = MESSAGING_SOURCES | {"cron", "webhook", "tool", "api", "api_server"} + # 'subagent' is a delegated delegate_task child: view-only, owned by the + # runner, never a writable WebUI/CLI session (#5307). Classify it non-CLI so + # sidebar rows and every is_cli_session_row() consumer keep it out of the + # CLI/writable treatment. + non_cli_sources = MESSAGING_SOURCES | {"cron", "webhook", "tool", "api", "api_server", "subagent"} if {source, source_tag, raw_source, source_name, source_label} & non_cli_sources: return False if source == "messaging": diff --git a/api/routes.py b/api/routes.py index 58c4c3cc9..b3903898b 100644 --- a/api/routes.py +++ b/api/routes.py @@ -11290,6 +11290,15 @@ def handle_get(handler, parsed) -> bool: raw["model"] = effective_model if effective_provider: raw["model_provider"] = effective_provider + # A subagent child (#5307) is view-only regardless of what a stale + # sidecar stored: coerce the serialized flags so the browser never + # treats an existing subagent sidecar as writable / CLI-classified. + if ( + (str(raw.get("source_tag") or raw.get("raw_source") or raw.get("session_source") or "").strip().lower() == "subagent") + or _is_subagent_child_session_id(sid) + ): + raw["is_cli_session"] = False + raw["read_only"] = True redact = redact_session_data(raw) _t5 = _time.monotonic() resp = j(handler, {"session": redact})