>
diff --git a/apps/web/e2e/tests/integrations/linear-settings.spec.ts b/apps/web/e2e/tests/integrations/linear-settings.spec.ts
index a08beaaa73..904b677766 100644
--- a/apps/web/e2e/tests/integrations/linear-settings.spec.ts
+++ b/apps/web/e2e/tests/integrations/linear-settings.spec.ts
@@ -1,5 +1,6 @@
import { test, expect } from "../../fixtures/test-base";
import { LinearSettingsPage } from "../../pages/linear-settings-page";
+import { assertWatcherAgentProfileResetsToStepDefault } from "./watcher-profile-default-flow";
test.describe("Linear settings", () => {
test("empty workspace shows form with disabled save/test until secret is filled", async ({
@@ -135,4 +136,8 @@ test.describe("Linear settings", () => {
// the " • " label format.
await expect(testPage.getByRole("option", { name: new RegExp(passthroughName) })).toBeVisible();
});
+
+ test("watcher dialog resets the agent profile back to the step default", async ({ testPage }) => {
+ await assertWatcherAgentProfileResetsToStepDefault(testPage);
+ });
});
diff --git a/apps/web/e2e/tests/integrations/mobile-linear-watcher-profile.spec.ts b/apps/web/e2e/tests/integrations/mobile-linear-watcher-profile.spec.ts
new file mode 100644
index 0000000000..c61acdb386
--- /dev/null
+++ b/apps/web/e2e/tests/integrations/mobile-linear-watcher-profile.spec.ts
@@ -0,0 +1,8 @@
+import { test } from "../../fixtures/test-base";
+import { assertWatcherAgentProfileResetsToStepDefault } from "./watcher-profile-default-flow";
+
+test.describe("Linear watcher dialog (mobile)", () => {
+ test("resets the agent profile back to the step default", async ({ testPage }) => {
+ await assertWatcherAgentProfileResetsToStepDefault(testPage);
+ });
+});
diff --git a/apps/web/e2e/tests/integrations/watcher-profile-default-flow.ts b/apps/web/e2e/tests/integrations/watcher-profile-default-flow.ts
new file mode 100644
index 0000000000..ff2ddab3d0
--- /dev/null
+++ b/apps/web/e2e/tests/integrations/watcher-profile-default-flow.ts
@@ -0,0 +1,46 @@
+import { type Page, expect } from "@playwright/test";
+import { LinearSettingsPage } from "../../pages/linear-settings-page";
+
+// Drives the Linear watcher create dialog: picks a real agent profile, then
+// re-selects "(use step default)" and asserts the field clears back. Shared by
+// the desktop (linear-settings.spec.ts) and mobile (mobile-*.spec.ts) suites so
+// both viewports exercise the same sentinel-reset behavior.
+export async function assertWatcherAgentProfileResetsToStepDefault(testPage: Page) {
+ const settings = new LinearSettingsPage(testPage);
+ await settings.goto();
+
+ await testPage.getByRole("button", { name: /new watcher/i }).click();
+ const dialog = testPage.getByRole("dialog");
+ await expect(dialog).toBeVisible();
+
+ // Scope to the Agent Profile field; the label text is unique within the form.
+ const agentTrigger = dialog
+ .locator("div.space-y-1\\.5")
+ .filter({ hasText: "Agent Profile" })
+ .getByRole("combobox");
+
+ // The select defaults to the sentinel option.
+ await expect(agentTrigger).toContainText("(use step default)");
+
+ // Pick the first real profile — option 0 is the "(use step default)" sentinel.
+ // Radix renders the open list in a portal; scope to its listbox so the option
+ // count can't drift across other (closed) selects in the dialog.
+ await agentTrigger.click();
+ const options = testPage.getByRole("listbox").getByRole("option");
+ // Sentinel + at least one real profile. A bare sentinel means the settings
+ // fixture seeded no agent profiles — fail with a clear count, not a vague
+ // "element not found" timeout on nth(1) below.
+ await expect.poll(() => options.count()).toBeGreaterThan(1);
+ const profileOption = options.nth(1);
+ await expect(profileOption).toBeVisible();
+ const profileLabel = ((await profileOption.textContent()) ?? "").trim();
+ expect(profileLabel.length).toBeGreaterThan(0);
+ await profileOption.click();
+ await expect(agentTrigger).toContainText(profileLabel);
+
+ // Re-select "(use step default)" and confirm the field reverts.
+ await agentTrigger.click();
+ await testPage.getByRole("listbox").getByRole("option", { name: "(use step default)" }).click();
+ await expect(agentTrigger).toContainText("(use step default)");
+ await expect(agentTrigger).not.toContainText(profileLabel);
+}
diff --git a/apps/web/lib/watcher-profile-default.test.ts b/apps/web/lib/watcher-profile-default.test.ts
new file mode 100644
index 0000000000..be289a68e1
--- /dev/null
+++ b/apps/web/lib/watcher-profile-default.test.ts
@@ -0,0 +1,17 @@
+import { describe, it, expect } from "vitest";
+import { STEP_DEFAULT, resolveProfileId } from "./watcher-profile-default";
+
+describe("resolveProfileId", () => {
+ it("collapses the sentinel to an empty string so the payload signals 'use step default'", () => {
+ expect(resolveProfileId(STEP_DEFAULT)).toBe("");
+ });
+
+ it("passes a real profile id through unchanged", () => {
+ expect(resolveProfileId("agent-profile-123")).toBe("agent-profile-123");
+ expect(resolveProfileId("exec-profile-abc")).toBe("exec-profile-abc");
+ });
+
+ it("leaves an already-empty value empty (initial/reset state)", () => {
+ expect(resolveProfileId("")).toBe("");
+ });
+});
diff --git a/apps/web/lib/watcher-profile-default.ts b/apps/web/lib/watcher-profile-default.ts
new file mode 100644
index 0000000000..118f1e102a
--- /dev/null
+++ b/apps/web/lib/watcher-profile-default.ts
@@ -0,0 +1,14 @@
+// Shared sentinel for the "(use step default)" option in the Agent/Executor
+// Profile selects across the four watcher dialogs (Linear, Jira, GitHub issue,
+// GitHub review). The form stores "" to mean "fall back to the workflow step's
+// default profile", and the create/update payloads pass "" through. Radix
+// disallows , so the dropdown item carries this sentinel
+// value and maps back to "" on change.
+export const STEP_DEFAULT = "__step_default__";
+export const STEP_DEFAULT_LABEL = "(use step default)";
+
+// Map a select value back to the stored profile id, collapsing the sentinel to
+// "" so the payload keeps signalling "use step default".
+export function resolveProfileId(value: string): string {
+ return value === STEP_DEFAULT ? "" : value;
+}