diff --git a/client/src/components/cos/tabs/RelaunchAgentModal.jsx b/client/src/components/cos/tabs/RelaunchAgentModal.jsx
index 842834b40b..44820877bd 100644
--- a/client/src/components/cos/tabs/RelaunchAgentModal.jsx
+++ b/client/src/components/cos/tabs/RelaunchAgentModal.jsx
@@ -6,6 +6,7 @@ import Modal from '../../ui/Modal';
import AppContextPicker from '../../AppContextPicker';
import ProviderModelSelector from '../../ProviderModelSelector';
import { FormField } from '../../ui/FormField';
+import CollapsibleText from '../../ui/CollapsibleText';
import { useAsyncAction } from '../../../hooks/useAsyncAction';
import { effortAwareModelOptions, seedModelEffort } from '../../../utils/providers';
@@ -104,7 +105,20 @@ export default function RelaunchAgentModal({ agent, providers, apps, onDone, onC
Current task
-
{taskDescription}
+ {/* A task description is the agent's whole prompt — routinely hundreds of
+ lines. Rendered in full it pushes the provider/model selects and the
+ Relaunch button off a phone screen, so it opens clamped. Expanding
+ swaps in a height-capped scroll box rather than unclamping in place:
+ the point of the dialog is the controls below it, and an expanded
+ prompt must not bury them again. */}
+
This stops the running agent and requeues the same task on the worktree it leaves
behind — no second agent, and nothing to clean up afterward.
diff --git a/client/src/components/cos/tabs/RelaunchAgentModal.test.jsx b/client/src/components/cos/tabs/RelaunchAgentModal.test.jsx
index 67c6466100..1c3c314acf 100644
--- a/client/src/components/cos/tabs/RelaunchAgentModal.test.jsx
+++ b/client/src/components/cos/tabs/RelaunchAgentModal.test.jsx
@@ -1,4 +1,4 @@
-import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
@@ -43,6 +43,12 @@ beforeEach(() => {
api.relaunchCosAgent.mockResolvedValue({ success: true, taskId: 'task-abc', mode: 'requeued' });
});
+// A prototype getter spy would otherwise survive a failing assertion and leak
+// into every test that runs after it.
+afterEach(() => {
+ vi.restoreAllMocks();
+});
+
describe('RelaunchAgentModal', () => {
it('submits the stalled run\'s own settings when the user changes nothing', async () => {
const user = userEvent.setup();
@@ -92,6 +98,31 @@ describe('RelaunchAgentModal', () => {
expect(toast.success).not.toHaveBeenCalledWith(expect.stringMatching(/queued again/i));
});
+ // A task description is the agent's whole prompt. Rendered in full it pushes
+ // the provider/model selects and the Relaunch button off a phone screen, so
+ // the dialog must open on a clamped preview and cap the expanded body's height.
+ it('opens the task prompt collapsed and caps it in a scroll box when expanded', async () => {
+ // jsdom reports 0 for scrollHeight and clientHeight alike, so nothing ever
+ // measures as overflowing without this.
+ vi.spyOn(HTMLElement.prototype, 'scrollHeight', 'get').mockReturnValue(500);
+ const user = userEvent.setup();
+ const prompt = Array.from({ length: 200 }, (_, i) => `step ${i}`).join('\n');
+ renderModal({ agent: { ...STALLED_AGENT, metadata: { ...STALLED_AGENT.metadata, taskDescription: prompt } } });
+
+ const preview = document.getElementById('relaunch-task-agent-live');
+ expect(preview.className).toContain('line-clamp-3');
+
+ await user.click(screen.getByRole('button', { name: /show more/i }));
+
+ const expanded = document.getElementById('relaunch-task-agent-live');
+ expect(expanded.className).not.toContain('line-clamp-3');
+ expect(expanded.className).toContain('max-h-48');
+ expect(expanded.className).toContain('overflow-y-auto');
+ // The way back matters most on a phone — expanding must not strand the user
+ // in the prompt with the form below it out of reach.
+ expect(screen.getByRole('button', { name: /show less/i })).toBeInTheDocument();
+ });
+
it('keeps the dialog open and surfaces the error when the relaunch fails', async () => {
const user = userEvent.setup();
const onClose = vi.fn();