diff --git a/desktop/src/features/channels/ui/ChannelScreen.tsx b/desktop/src/features/channels/ui/ChannelScreen.tsx
index 094fc65679..d8b8bda8d1 100644
--- a/desktop/src/features/channels/ui/ChannelScreen.tsx
+++ b/desktop/src/features/channels/ui/ChannelScreen.tsx
@@ -94,6 +94,7 @@ export function ChannelScreen({
     setTopbarSearchHidden,
   } = useAppShell();
   const {
+    clearMessageRouteTarget,
     openAgentSessionPubkey,
     openThreadHeadId,
     profilePanelPubkey,
@@ -493,6 +494,9 @@ export function ChannelScreen({
   const handleThreadScrollTargetResolved = React.useCallback(() => {
     setThreadScrollTargetId(null);
   }, []);
+  const handleTargetReached = React.useCallback(() => {
+    clearMessageRouteTarget({ replace: true });
+  }, [clearMessageRouteTarget]);
   React.useEffect(() => {
     resetComposerTargets(activeChannelId);
   }, [activeChannelId, resetComposerTargets]);
@@ -693,6 +697,7 @@ export function ChannelScreen({
                     handleThreadScrollTargetResolved
                   }
                   onThreadPanelResizeStart={handleThreadPanelResizeStart}
+                  onTargetReached={handleTargetReached}
                   onToggleReaction={effectiveToggleReaction}
                   openAgentSessionPubkey={openAgentSessionPubkey}
                   openThreadHeadId={openThreadHeadId}
diff --git a/desktop/src/features/channels/ui/useChannelPanelHistoryState.ts b/desktop/src/features/channels/ui/useChannelPanelHistoryState.ts
index 15df8fd591..53d236f432 100644
--- a/desktop/src/features/channels/ui/useChannelPanelHistoryState.ts
+++ b/desktop/src/features/channels/ui/useChannelPanelHistoryState.ts
@@ -23,11 +23,13 @@ export type PanelValueSetter = (
   options?: PanelSetterOptions,
 ) => void;
 
-const PANEL_SEARCH_KEYS = [
+const CHANNEL_SEARCH_KEYS = [
   "agentSession",
+  "messageId",
   "profile",
   "profileView",
   "thread",
+  "threadRootId",
 ] as const;
 
 function asProfilePanelView(value: string | null): ProfilePanelView {
@@ -35,7 +37,7 @@ function asProfilePanelView(value: string | null): ProfilePanelView {
 }
 
 export function useChannelPanelHistoryState() {
-  const { applyPatch, values } = useHistorySearchState(PANEL_SEARCH_KEYS);
+  const { applyPatch, values } = useHistorySearchState(CHANNEL_SEARCH_KEYS);
 
   const setOpenThreadHeadId = React.useCallback<PanelValueSetter>(
     (value, options) => applyPatch({ thread: value }, options),
@@ -61,7 +63,14 @@ export function useChannelPanelHistoryState() {
     [applyPatch],
   );
 
+  const clearMessageRouteTarget = React.useCallback(
+    (options?: PanelSetterOptions) =>
+      applyPatch({ messageId: null, threadRootId: null }, options),
+    [applyPatch],
+  );
+
   return {
+    clearMessageRouteTarget,
     openAgentSessionPubkey: values.agentSession,
     openThreadHeadId: values.thread,
     profilePanelPubkey: values.profile,
diff --git a/desktop/src/features/channels/ui/useChannelRouteTarget.ts b/desktop/src/features/channels/ui/useChannelRouteTarget.ts
index fcae0b0cf5..0dc4b0e4d6 100644
--- a/desktop/src/features/channels/ui/useChannelRouteTarget.ts
+++ b/desktop/src/features/channels/ui/useChannelRouteTarget.ts
@@ -110,10 +110,27 @@ export function useChannelRouteTarget({
     }
 
     const targetMessage = timelineMessageById.get(targetMessageId) ?? null;
-    if (
-      !targetMessage?.parentId ||
-      isBroadcastReply(targetMessage.tags ?? [])
-    ) {
+    if (!targetMessage) {
+      return;
+    }
+
+    if (!targetMessage.parentId) {
+      closeAgentSession();
+      // Root message links should open the reply panel for that root. The
+      // timeline scroll/highlight target alone is not enough: root links have
+      // no parent/thread metadata, so the reply-only branch below cannot infer
+      // a thread head.
+      setProfilePanelPubkey(null, { replace: true });
+      setEditTargetId(null);
+      setOpenThreadHeadId(targetMessage.id, { replace: true });
+      setThreadReplyTargetId(targetMessage.id);
+      setThreadScrollTargetId(null);
+      setExpandedThreadReplyIds(new Set());
+      handledThreadRouteTargetRef.current = targetKey;
+      return;
+    }
+
+    if (isBroadcastReply(targetMessage.tags ?? [])) {
       return;
     }
 
diff --git a/desktop/src/features/messages/ui/MessageTimeline.tsx b/desktop/src/features/messages/ui/MessageTimeline.tsx
index f1b78cbe5a..230784f7af 100644
--- a/desktop/src/features/messages/ui/MessageTimeline.tsx
+++ b/desktop/src/features/messages/ui/MessageTimeline.tsx
@@ -324,7 +324,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({
                 showMessageList && !showIntro && "mt-auto",
               )}
               contentClassName={cn(
-                "flex flex-col gap-2",
+                "flex min-w-0 flex-col gap-2",
                 (showIntro || showGenericEmpty) && "min-h-full",
               )}
               loading={isLoading}
diff --git a/desktop/tests/e2e/messaging.spec.ts b/desktop/tests/e2e/messaging.spec.ts
index 9666a37db4..81e1f7f838 100644
--- a/desktop/tests/e2e/messaging.spec.ts
+++ b/desktop/tests/e2e/messaging.spec.ts
@@ -1046,3 +1046,50 @@ test("ArrowUp edits your last thread reply right after sending it", async ({
   await expect(editBanner).toContainText(reply);
   await expect(threadInput).toHaveText(reply);
 });
+
+test("action bar stays within the timeline when the thread panel is open", async ({
+  page,
+}) => {
+  // Narrow viewport + open thread panel => the timeline shrinks to a column.
+  // A long unbreakable token must not widen message rows past that column,
+  // or the right-anchored action bar is pushed offscreen (regression: #1081
+  // fixed the wrap but rows still expanded to content min-width).
+  await page.setViewportSize({ width: 1024, height: 800 });
+  await page.goto("/");
+  await page.getByTestId("channel-general").click();
+  await expect(page.getByTestId("chat-title")).toHaveText("general");
+
+  const timeline = page.getByTestId("message-timeline");
+  const input = page.getByTestId("message-input").first();
+  const send = page.getByTestId("send-message").first();
+
+  const longUrl = `https://example.com/${"a".repeat(180)}/path`;
+  await input.fill(longUrl);
+  await send.click();
+  await expect(timeline).toContainText("example.com");
+
+  const rootMessage = timeline.getByTestId("message-row").first();
+  await rootMessage.hover();
+  await rootMessage.getByRole("button", { name: "Reply" }).click();
+  await expect(page.getByTestId("message-thread-panel")).toBeVisible();
+
+  const wideRow = timeline.getByTestId("message-row").last();
+  await wideRow.scrollIntoViewIfNeeded();
+  await wideRow.hover();
+  const bar = wideRow.locator('[data-testid^="message-action-bar-"]');
+  await expect(bar).toBeVisible();
+
+  const timelineBox = await timeline.boundingBox();
+  const rowBox = await wideRow.boundingBox();
+  const barBox = await bar.boundingBox();
+  if (!timelineBox || !rowBox || !barBox) {
+    throw new Error("Expected timeline, row, and action bar to have geometry.");
+  }
+
+  expect(rowBox.x + rowBox.width).toBeLessThanOrEqual(
+    timelineBox.x + timelineBox.width + 1,
+  );
+  expect(barBox.x + barBox.width).toBeLessThanOrEqual(
+    timelineBox.x + timelineBox.width + 1,
+  );
+});
diff --git a/desktop/tests/e2e/navigation.spec.ts b/desktop/tests/e2e/navigation.spec.ts
index 6ebcf03b67..04f39de919 100644
--- a/desktop/tests/e2e/navigation.spec.ts
+++ b/desktop/tests/e2e/navigation.spec.ts
@@ -266,6 +266,77 @@ test("settings is a route: section survives reload, closing returns to the previ
   await expect(threadPanel).toBeVisible();
 });
 
+test("message links to visible root messages open the thread panel", async ({
+  page,
+}) => {
+  await page.goto("/");
+  await page.getByTestId("channel-general").click();
+  await expect(page.getByTestId("chat-title")).toHaveText("general");
+  await expect(page.getByTestId("message-timeline")).toContainText(
+    "Welcome to #general",
+  );
+
+  const link =
+    "buzz://message?channel=9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50&id=mock-general-welcome";
+  await page.getByTestId("message-input").fill(`Root link repro ${link}`);
+  await page.getByTestId("send-message").click();
+
+  const linkMessage = page
+    .getByTestId("message-row")
+    .filter({ hasText: "Root link repro" })
+    .last();
+  await expect(linkMessage).toBeVisible();
+  await linkMessage
+    .getByRole("button", { name: "Open message in general" })
+    .click();
+
+  const threadPanel = page.getByTestId("message-thread-panel");
+  await expect(threadPanel).toBeVisible();
+  await expect(page).toHaveURL(/thread=mock-general-welcome/);
+  await expect(threadPanel.getByTestId("message-thread-head")).toContainText(
+    "Welcome to #general",
+  );
+});
+
+test("message links reopen a closed thread when the same messageId is already in the URL", async ({
+  page,
+}) => {
+  await page.goto(
+    "/#/channels/9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50?messageId=mock-general-welcome",
+  );
+  await expect(page.getByTestId("chat-title")).toHaveText("general");
+
+  const threadPanel = page.getByTestId("message-thread-panel");
+  await expect(threadPanel).toBeVisible();
+  await expect(threadPanel.getByTestId("message-thread-head")).toContainText(
+    "Welcome to #general",
+  );
+
+  await threadPanel.getByRole("button", { name: "Close thread" }).click();
+  await expect(threadPanel).not.toBeVisible();
+
+  const link =
+    "buzz://message?channel=9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50&id=mock-general-welcome";
+  await page
+    .getByTestId("message-input")
+    .fill(`Reopen same root link repro ${link}`);
+  await page.getByTestId("send-message").click();
+
+  const linkMessage = page
+    .getByTestId("message-row")
+    .filter({ hasText: "Reopen same root link repro" })
+    .last();
+  await expect(linkMessage).toBeVisible();
+  await linkMessage
+    .getByRole("button", { name: "Open message in general" })
+    .click();
+
+  await expect(threadPanel).toBeVisible();
+  await expect(threadPanel.getByTestId("message-thread-head")).toContainText(
+    "Welcome to #general",
+  );
+});
+
 test("message deep links survive reload", async ({ page }) => {
   await page.goto(
     `/#/channels/${ENGINEERING_CHANNEL_ID}?messageId=mock-engineering-shipped`,
