diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx
index c71634cd4edce..28a3c573eb1f0 100644
--- a/apps/web/src/components/chat/ChatComposer.tsx
+++ b/apps/web/src/components/chat/ChatComposer.tsx
@@ -236,7 +236,7 @@ import { observeResize } from "~/lib/observeResize";
const isEnvironmentRequestInvalidError = Schema.is(EnvironmentRequestInvalidError);
import { toastManager } from "../ui/toast";
-import { BuildingIcon, PlayIcon, ReplyIcon } from "lucide-react";
+import { BuildingIcon, EyeIcon, PlayIcon, ReplyIcon } from "lucide-react";
import { useNavigate } from "@tanstack/react-router";
import { assistantCitationLabel } from "@t3tools/shared/assistantCitations";
import { assistantCitationNavigation } from "../../lib/assistantCitationNavigation";
@@ -2506,11 +2506,27 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
},
]
: [];
+ // Read scope also proves the session has loaded, so this doesn't flash while connecting.
+ const viewOnly =
+ useEnvironmentScope(environmentId, AuthOrchestrationReadScope) && !canOperateThread;
+ const viewOnlyBannerItems: readonly ComposerBannerStackItem[] = viewOnly
+ ? [
+ {
+ id: "view-only",
+ variant: "warning",
+ group: "environment",
+ icon: