diff --git a/server/appRuntime.test.ts b/server/appRuntime.test.ts index c269b3e..9e9499d 100644 --- a/server/appRuntime.test.ts +++ b/server/appRuntime.test.ts @@ -752,8 +752,10 @@ function makePlanIssue( repo: overrides.repo, number: overrides.number, author: overrides.author ?? "alice", + isWorked: overrides.isWorked ?? false, workStatus: overrides.workStatus ?? "idle", workPrUrl: overrides.workPrUrl ?? null, + externalWorkPrUrl: overrides.externalWorkPrUrl ?? null, autoWorkEligible: overrides.autoWorkEligible ?? false, evaluationStatus: overrides.evaluationStatus ?? null, updatedAt: overrides.updatedAt ?? "2026-05-01T00:00:00.000Z", @@ -877,6 +879,32 @@ test("planAutomaticIssueQueueActions queues work for approved+eligible issues wh assert.deepEqual(plan.work, [{ repo: "acme/widgets", number: 1, id: "acme/widgets#1" }]); }); +test("planAutomaticIssueQueueActions does not requeue worked issues", () => { + const plan = planAutomaticIssueQueueActions({ + repoSettings: [{ repo: "acme/widgets", issueAutoEvaluate: true, issueAutoWork: true }], + issues: [ + makePlanIssue({ + repo: "acme/widgets", + number: 1, + isWorked: true, + evaluationStatus: "approved", + autoWorkEligible: true, + }), + makePlanIssue({ + repo: "acme/widgets", + number: 2, + isWorked: true, + }), + ], + activeEvaluationTargets: new Set(), + activeWorkCount: 0, + maxConcurrentIssueEvaluations: 2, + maxConcurrentIssueWork: 1, + }); + + assert.deepEqual(plan, { evaluations: [], work: [] }); +}); + test("planAutomaticIssueQueueActions respects per-repo single-flight on work", () => { // Goal: don't pile multiple work jobs onto one repo even if budget allows — the agent // runs one at a time per repo, so queueing more just creates lag. diff --git a/server/appRuntime.ts b/server/appRuntime.ts index d668b20..a71cba7 100644 --- a/server/appRuntime.ts +++ b/server/appRuntime.ts @@ -316,7 +316,7 @@ export type PlanAutomaticIssueQueueInput = { repoSettings: Pick[]; issues: Pick< Issue, - "id" | "repo" | "number" | "author" | "workStatus" | "workPrUrl" | "externalWorkPrUrl" | "autoWorkEligible" | "evaluationStatus" | "updatedAt" + "id" | "repo" | "number" | "author" | "isWorked" | "workStatus" | "workPrUrl" | "externalWorkPrUrl" | "autoWorkEligible" | "evaluationStatus" | "updatedAt" >[]; activeEvaluationTargets: Set; activeWorkCount: number; @@ -357,7 +357,7 @@ export function planAutomaticIssueQueueActions( continue; } const candidate = repoIssues - .filter((issue) => issue.workStatus === "idle" && !issue.workPrUrl && !issue.externalWorkPrUrl && issue.autoWorkEligible) + .filter((issue) => !issue.isWorked && issue.workStatus === "idle" && !issue.workPrUrl && !issue.externalWorkPrUrl && issue.autoWorkEligible) .sort((a, b) => compareIssueQueuePriority(a, b, priorityIssueAuthors))[0]; if (!candidate) continue; result.work.push({ repo: candidate.repo, number: candidate.number, id: candidate.id }); @@ -372,6 +372,7 @@ export function planAutomaticIssueQueueActions( const pending = input.issues .filter((issue) => issue.repo === repo + && !issue.isWorked && issue.workStatus === "idle" && !issue.workPrUrl && !issue.externalWorkPrUrl