Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Task } from "@posthog/shared/domain-types";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { renderHook, waitFor } from "@testing-library/react";
import type { PropsWithChildren } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useRefreshedTask } from "./useRefreshedTask";

const mocks = vi.hoisted(() => ({ getTask: vi.fn() }));

vi.mock("@posthog/ui/features/auth/authClientImperative", () => ({
getAuthenticatedClient: vi.fn(async () => ({ getTask: mocks.getTask })),
}));

function task(runId: string, status: "failed" | "in_progress"): Task {
return {
id: "task-123",
title: "Cloud task",
description: "Keep working",
repository: null,
latest_run: {
id: runId,
task: "task-123",
environment: "cloud",
status,
state: {},
},
} as Task;
}

describe("useRefreshedTask", () => {
beforeEach(() => {
mocks.getTask.mockReset();
});

it("replaces a cached failed run with the authoritative resumed run", async () => {
const failedParent = task("run-parent", "failed");
const resumedChild = task("run-child", "in_progress");
mocks.getTask.mockResolvedValue(resumedChild);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const wrapper = ({ children }: PropsWithChildren) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

const { result } = renderHook(
() => useRefreshedTask("task-123", failedParent),
{ wrapper },
);

expect(result.current.latest_run?.id).toBe("run-parent");
await waitFor(() => {
expect(result.current.latest_run?.id).toBe("run-child");
});
expect(mocks.getTask).toHaveBeenCalledWith("task-123");
});
});
13 changes: 13 additions & 0 deletions packages/ui/src/features/task-detail/hooks/useRefreshedTask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Task } from "@posthog/shared/domain-types";
import { useQuery } from "@tanstack/react-query";
import { taskDetailQuery } from "../../tasks/queries";

export function useRefreshedTask(taskId: string, initialTask: Task): Task {
const { data } = useQuery({
...taskDetailQuery(taskId),
initialData: initialTask,
refetchOnMount: "always",
});

return data;
}
10 changes: 2 additions & 8 deletions packages/ui/src/features/task-detail/hooks/useTaskData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import { getTaskRepository } from "@posthog/shared";
import type { Task } from "@posthog/shared/domain-types";
import { useWorkspaceTRPC } from "@posthog/workspace-client/trpc";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import { cloneStore } from "../../clone/cloneStore";
import { useTasks } from "../../tasks/useTasks";
import { useWorkspace } from "../../workspace/useWorkspace";
import { useRefreshedTask } from "./useRefreshedTask";

interface UseTaskDataParams {
taskId: string;
Expand All @@ -19,12 +18,7 @@ interface UseTaskDataParams {

export function useTaskData({ taskId, initialTask }: UseTaskDataParams) {
const trpcReact = useWorkspaceTRPC();
const { data: tasks = [] } = useTasks();

const task = useMemo(
() => tasks.find((t) => t.id === taskId) || initialTask,
[tasks, taskId, initialTask],
);
const task = useRefreshedTask(taskId, initialTask);

const workspace = useWorkspace(taskId);
const repoPath = workspace?.folderPath ?? null;
Expand Down
23 changes: 23 additions & 0 deletions packages/workspace-server/src/services/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,29 @@ describe("AgentService", () => {
expect(prompt).toContain("If the user names a folder or path");
});
});

describe("system prompt questions", () => {
it("requires blocking questions to use a structured user-input tool", () => {
const prompt = (
service as unknown as {
buildSystemPrompt: (
credentials: { apiHost: string; projectId: number },
taskId: string,
) => { append: string };
}
).buildSystemPrompt(
{ apiHost: "https://app.posthog.com", projectId: 1 },
"task-1",
).append;

expect(prompt).toContain(
"use the structured user-input tool available in your current mode",
);
expect(prompt).toContain(
"plain-text questions mark the task as finished",
);
});
});
});

describe("buildAutoApproveOutcome", () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/workspace-server/src/services/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,9 @@ When creating pull requests, add the following footer at the end of the PR descr

When you mention a pull request in any reply or summary, always hyperlink it to its full URL (e.g. a Markdown link like [#123](https://github.com/org/repo/pull/123)) rather than plain text, so readers can open it directly.

## Questions
When you need an answer from the user before you can continue, use the structured user-input tool available in your current mode. Never end a turn with a blocking question in a normal assistant message because plain-text questions mark the task as finished instead of waiting for the user's response.

## Shell efficiency
Optimize for the fewest shell round trips.
- Batch related commands into one Bash invocation using \`&&\` (e.g. \`npm run typecheck && npm run lint && npm test\`).
Expand Down
Loading