diff --git a/lib/task-managers/github.ts b/lib/task-managers/github.ts index 4529542..704e4af 100644 --- a/lib/task-managers/github.ts +++ b/lib/task-managers/github.ts @@ -151,11 +151,38 @@ export class GitHubProvider implements TaskManager { from: StateLabel, to: StateLabel, ): Promise { - await this.gh([ + // Fetch current issue to get all labels + const issue = await this.getIssue(issueId); + + // Find all state labels currently on the issue + const currentStateLabels = issue.labels.filter((label) => + STATE_LABELS.includes(label as StateLabel), + ); + + // If no state labels to remove, just add the new one + if (currentStateLabels.length === 0) { + await this.gh([ + "issue", "edit", String(issueId), + "--add-label", to, + ]); + return; + } + + // Remove all state labels and add the new one in a single operation + // This ensures clean transitions: "removed X, added Y" instead of messy multi-label operations + const args = [ "issue", "edit", String(issueId), - "--remove-label", from, - "--add-label", to, - ]); + ]; + + // Add all current state labels to remove + for (const label of currentStateLabels) { + args.push("--remove-label", label); + } + + // Add the new state label + args.push("--add-label", to); + + await this.gh(args); } async closeIssue(issueId: number): Promise { diff --git a/lib/task-managers/gitlab.ts b/lib/task-managers/gitlab.ts index 8a6c6be..6d9ee44 100644 --- a/lib/task-managers/gitlab.ts +++ b/lib/task-managers/gitlab.ts @@ -116,11 +116,38 @@ export class GitLabProvider implements TaskManager { from: StateLabel, to: StateLabel, ): Promise { - await this.glab([ + // Fetch current issue to get all labels + const issue = await this.getIssue(issueId); + + // Find all state labels currently on the issue + const currentStateLabels = issue.labels.filter((label) => + STATE_LABELS.includes(label as StateLabel), + ); + + // If no state labels to remove, just add the new one + if (currentStateLabels.length === 0) { + await this.glab([ + "issue", "update", String(issueId), + "--label", to, + ]); + return; + } + + // Remove all state labels and add the new one in a single operation + // This ensures clean transitions: "removed X, added Y" instead of messy multi-label operations + const args = [ "issue", "update", String(issueId), - "--unlabel", from, - "--label", to, - ]); + ]; + + // Add all current state labels to remove + for (const label of currentStateLabels) { + args.push("--unlabel", label); + } + + // Add the new state label + args.push("--label", to); + + await this.glab(args); } async closeIssue(issueId: number): Promise {