diff --git a/src/features/team-mode/team-tasklist/list.test.ts b/src/features/team-mode/team-tasklist/list.test.ts
new file mode 100644
index 000000000..0541ff9ad
--- /dev/null
+++ b/src/features/team-mode/team-tasklist/list.test.ts
@@ -0,0 +1,63 @@
+///
+
+import { expect, test } from "bun:test"
+import { writeFile } from "node:fs/promises"
+import path from "node:path"
+
+import { getTasksDir, resolveBaseDir } from "../team-registry"
+import { createTask } from "./store"
+import { createTaskInput, createTasklistFixture } from "./test-support"
+import { updateTaskStatus } from "./update"
+import { listTasks } from "./list"
+
+test("listTasks returns tasks sorted ascending and honors filters", async () => {
+ // given
+ const fixture = await createTasklistFixture()
+
+ try {
+ const firstTask = await createTask(
+ fixture.teamRunId,
+ createTaskInput({ subject: "one", status: "claimed", owner: "member-a", claimedAt: Date.now() }),
+ fixture.config,
+ )
+ await createTask(fixture.teamRunId, createTaskInput({ subject: "two" }), fixture.config)
+ const thirdTask = await createTask(
+ fixture.teamRunId,
+ createTaskInput({ subject: "three", status: "claimed", owner: "member-a", claimedAt: Date.now() }),
+ fixture.config,
+ )
+ await updateTaskStatus(fixture.teamRunId, thirdTask.id, "in_progress", "member-a", fixture.config)
+
+ // when
+ const allTasks = await listTasks(fixture.teamRunId, fixture.config)
+ const claimedTasks = await listTasks(fixture.teamRunId, fixture.config, { status: "claimed", owner: "member-a" })
+
+ // then
+ expect(allTasks.map((task) => task.id)).toEqual([firstTask.id, "2", thirdTask.id])
+ expect(claimedTasks).toHaveLength(1)
+ expect(claimedTasks[0]?.id).toBe(firstTask.id)
+ } finally {
+ await fixture.cleanup()
+ }
+})
+
+test("listTasks skips malformed task files", async () => {
+ // given
+ const fixture = await createTasklistFixture()
+
+ try {
+ const validTask = await createTask(fixture.teamRunId, createTaskInput(), fixture.config)
+ const tasksDirectory = getTasksDir(resolveBaseDir(fixture.config), fixture.teamRunId)
+ await writeFile(path.join(tasksDirectory, "bad.json"), "{not-json")
+ await writeFile(path.join(tasksDirectory, ".highwatermark"), "1")
+
+ // when
+ const listedTasks = await listTasks(fixture.teamRunId, fixture.config)
+
+ // then
+ expect(listedTasks).toHaveLength(1)
+ expect(listedTasks[0]?.id).toBe(validTask.id)
+ } finally {
+ await fixture.cleanup()
+ }
+})
diff --git a/src/features/team-mode/team-tasklist/list.ts b/src/features/team-mode/team-tasklist/list.ts
new file mode 100644
index 000000000..d462a655e
--- /dev/null
+++ b/src/features/team-mode/team-tasklist/list.ts
@@ -0,0 +1,65 @@
+import type { Dirent } from "node:fs"
+import { readdir, readFile } from "node:fs/promises"
+import path from "node:path"
+
+import type { TeamModeConfig } from "../../../config/schema/team-mode"
+import { log } from "../../../shared/logger"
+import { getTasksDir, resolveBaseDir } from "../team-registry"
+import { TaskSchema } from "../types"
+import type { Task } from "../types"
+
+type TaskListFilter = {
+ status?: Task["status"]
+ owner?: string
+}
+
+export async function listTasks(
+ teamRunId: string,
+ config: TeamModeConfig,
+ filter?: TaskListFilter,
+): Promise {
+ const tasksDirectory = getTasksDir(resolveBaseDir(config), teamRunId)
+
+ let entries: Dirent[]
+ try {
+ entries = await readdir(tasksDirectory, { withFileTypes: true })
+ } catch {
+ return []
+ }
+
+ const parsedTasks: Task[] = []
+ for (const entry of entries) {
+ if (entry.isDirectory() || entry.name.startsWith(".") || !entry.name.endsWith(".json")) continue
+
+ const taskPath = path.join(tasksDirectory, entry.name)
+ try {
+ const taskContent = await readFile(taskPath, "utf8")
+ const parsedTask = TaskSchema.safeParse(JSON.parse(taskContent))
+ if (!parsedTask.success) {
+ log("team-tasklist skipped malformed task", {
+ event: "team-tasklist-malformed-task",
+ taskPath,
+ issues: parsedTask.error.issues,
+ })
+ continue
+ }
+ parsedTasks.push(parsedTask.data)
+ } catch (error) {
+ log("team-tasklist skipped malformed task", {
+ event: "team-tasklist-malformed-task",
+ taskPath,
+ error: error instanceof Error ? error.message : String(error),
+ })
+ }
+ }
+
+ return parsedTasks
+ .filter((task) => {
+ if (filter?.status !== undefined && task.status !== filter.status) {
+ return false
+ }
+
+ return filter?.owner === undefined || task.owner === filter.owner
+ })
+ .sort((leftTask, rightTask) => Number.parseInt(leftTask.id, 10) - Number.parseInt(rightTask.id, 10))
+}