diff --git a/orchestrate/skills/orchestrate/scripts/__tests__/kickoff-dedupe.test.ts b/orchestrate/skills/orchestrate/scripts/__tests__/kickoff-dedupe.test.ts index bc7cc2f..0814586 100644 --- a/orchestrate/skills/orchestrate/scripts/__tests__/kickoff-dedupe.test.ts +++ b/orchestrate/skills/orchestrate/scripts/__tests__/kickoff-dedupe.test.ts @@ -75,6 +75,30 @@ describe("kickoff dedupe", () => { expect(active).toBeNull(); }); + test("does not adopt a planner whose slug only shares a prefix", async () => { + const now = Date.parse("2026-05-01T16:00:00.000Z"); + const active = await findActiveRootPlanner( + { + async list() { + return { + items: [ + { + agentId: "bc-refactor-ui", + name: "refactor-ui-root", + createdAt: now - 1_000, + latestRun: { id: "run-refactor-ui", status: "running" }, + }, + ], + }; + }, + }, + "refactor", + now + ); + + expect(active).toBeNull(); + }); + test("falls back to listRuns when list omits latest run", async () => { const now = Date.parse("2026-05-01T16:00:00.000Z"); const active = await findActiveRootPlanner( diff --git a/orchestrate/skills/orchestrate/scripts/cli/task.ts b/orchestrate/skills/orchestrate/scripts/cli/task.ts index cd3284e..b23c115 100644 --- a/orchestrate/skills/orchestrate/scripts/cli/task.ts +++ b/orchestrate/skills/orchestrate/scripts/cli/task.ts @@ -478,10 +478,13 @@ export async function findActiveRootPlanner( nowMs: number = Date.now() ): Promise { const list = await agentApi.list({ runtime: "cloud", limit: 50 }); + // Exact match, not startsWith: a prefix slug would otherwise adopt another + // goal's `-root` planner (e.g. "auth" adopting "auth-ui-root"). + const rootPlannerName = `${rootSlug}-root`; for (const item of listItems(list)) { const name = stringField(item, "name"); if (!name) continue; - if (!name.startsWith(rootSlug)) continue; + if (name !== rootPlannerName) continue; const createdAt = timeMs(field(item, "createdAt")); if (createdAt === null || nowMs - createdAt > MAX_BOOT_MS) continue; const agentId =